@bsv/sdk 1.7.7 → 1.8.1

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 (94) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/kvstore/GlobalKVStore.js +420 -0
  3. package/dist/cjs/src/kvstore/GlobalKVStore.js.map +1 -0
  4. package/dist/cjs/src/kvstore/LocalKVStore.js +6 -6
  5. package/dist/cjs/src/kvstore/LocalKVStore.js.map +1 -1
  6. package/dist/cjs/src/kvstore/kvStoreInterpreter.js +74 -0
  7. package/dist/cjs/src/kvstore/kvStoreInterpreter.js.map +1 -0
  8. package/dist/cjs/src/kvstore/types.js +11 -0
  9. package/dist/cjs/src/kvstore/types.js.map +1 -0
  10. package/dist/cjs/src/overlay-tools/Historian.js +153 -0
  11. package/dist/cjs/src/overlay-tools/Historian.js.map +1 -0
  12. package/dist/cjs/src/script/templates/PushDrop.js +2 -2
  13. package/dist/cjs/src/script/templates/PushDrop.js.map +1 -1
  14. package/dist/cjs/src/transaction/Transaction.js +4 -4
  15. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  16. package/dist/cjs/src/transaction/fee-models/LivePolicy.js +90 -0
  17. package/dist/cjs/src/transaction/fee-models/LivePolicy.js.map +1 -0
  18. package/dist/cjs/src/transaction/fee-models/index.js +3 -1
  19. package/dist/cjs/src/transaction/fee-models/index.js.map +1 -1
  20. package/dist/cjs/src/wallet/WalletClient.js +43 -52
  21. package/dist/cjs/src/wallet/WalletClient.js.map +1 -1
  22. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js +19 -0
  23. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  24. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js +18 -1
  25. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
  26. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  27. package/dist/esm/src/kvstore/GlobalKVStore.js +416 -0
  28. package/dist/esm/src/kvstore/GlobalKVStore.js.map +1 -0
  29. package/dist/esm/src/kvstore/LocalKVStore.js +6 -6
  30. package/dist/esm/src/kvstore/LocalKVStore.js.map +1 -1
  31. package/dist/esm/src/kvstore/kvStoreInterpreter.js +47 -0
  32. package/dist/esm/src/kvstore/kvStoreInterpreter.js.map +1 -0
  33. package/dist/esm/src/kvstore/types.js +8 -0
  34. package/dist/esm/src/kvstore/types.js.map +1 -0
  35. package/dist/esm/src/overlay-tools/Historian.js +155 -0
  36. package/dist/esm/src/overlay-tools/Historian.js.map +1 -0
  37. package/dist/esm/src/script/templates/PushDrop.js +2 -2
  38. package/dist/esm/src/script/templates/PushDrop.js.map +1 -1
  39. package/dist/esm/src/transaction/Transaction.js +4 -4
  40. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  41. package/dist/esm/src/transaction/fee-models/LivePolicy.js +85 -0
  42. package/dist/esm/src/transaction/fee-models/LivePolicy.js.map +1 -0
  43. package/dist/esm/src/transaction/fee-models/index.js +1 -0
  44. package/dist/esm/src/transaction/fee-models/index.js.map +1 -1
  45. package/dist/esm/src/wallet/WalletClient.js +43 -52
  46. package/dist/esm/src/wallet/WalletClient.js.map +1 -1
  47. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js +19 -0
  48. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  49. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js +18 -1
  50. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
  51. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  52. package/dist/types/src/kvstore/GlobalKVStore.d.ts +129 -0
  53. package/dist/types/src/kvstore/GlobalKVStore.d.ts.map +1 -0
  54. package/dist/types/src/kvstore/kvStoreInterpreter.d.ts +22 -0
  55. package/dist/types/src/kvstore/kvStoreInterpreter.d.ts.map +1 -0
  56. package/dist/types/src/kvstore/types.d.ts +106 -0
  57. package/dist/types/src/kvstore/types.d.ts.map +1 -0
  58. package/dist/types/src/overlay-tools/Historian.d.ts +92 -0
  59. package/dist/types/src/overlay-tools/Historian.d.ts.map +1 -0
  60. package/dist/types/src/script/templates/PushDrop.d.ts +6 -5
  61. package/dist/types/src/script/templates/PushDrop.d.ts.map +1 -1
  62. package/dist/types/src/transaction/Transaction.d.ts +2 -2
  63. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  64. package/dist/types/src/transaction/fee-models/LivePolicy.d.ts +41 -0
  65. package/dist/types/src/transaction/fee-models/LivePolicy.d.ts.map +1 -0
  66. package/dist/types/src/transaction/fee-models/index.d.ts +1 -0
  67. package/dist/types/src/transaction/fee-models/index.d.ts.map +1 -1
  68. package/dist/types/src/wallet/WalletClient.d.ts +1 -1
  69. package/dist/types/src/wallet/WalletClient.d.ts.map +1 -1
  70. package/dist/types/src/wallet/substrates/WalletWireProcessor.d.ts.map +1 -1
  71. package/dist/types/src/wallet/substrates/WalletWireTransceiver.d.ts.map +1 -1
  72. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  73. package/dist/umd/bundle.js +3 -3
  74. package/dist/umd/bundle.js.map +1 -1
  75. package/docs/reference/script.md +7 -19
  76. package/docs/reference/transaction.md +75 -6
  77. package/docs/reference/wallet.md +1 -1
  78. package/package.json +1 -1
  79. package/src/kvstore/GlobalKVStore.ts +478 -0
  80. package/src/kvstore/LocalKVStore.ts +7 -7
  81. package/src/kvstore/__tests/GlobalKVStore.test.ts +965 -0
  82. package/src/kvstore/__tests/LocalKVStore.test.ts +72 -0
  83. package/src/kvstore/kvStoreInterpreter.ts +49 -0
  84. package/src/kvstore/types.ts +114 -0
  85. package/src/overlay-tools/Historian.ts +195 -0
  86. package/src/overlay-tools/__tests/Historian.test.ts +690 -0
  87. package/src/script/templates/PushDrop.ts +6 -5
  88. package/src/transaction/Transaction.ts +4 -4
  89. package/src/transaction/fee-models/LivePolicy.ts +97 -0
  90. package/src/transaction/fee-models/__tests/LivePolicy.test.ts +148 -0
  91. package/src/transaction/fee-models/index.ts +1 -0
  92. package/src/wallet/WalletClient.ts +50 -51
  93. package/src/wallet/substrates/WalletWireProcessor.ts +21 -0
  94. package/src/wallet/substrates/WalletWireTransceiver.ts +22 -10
@@ -6,8 +6,9 @@ import {
6
6
  Signature,
7
7
  PublicKey
8
8
  } from '../../primitives/index.js'
9
- import { WalletInterface, SecurityLevel } from '../../wallet/Wallet.interfaces.js'
9
+ import { WalletInterface } from '../../wallet/Wallet.interfaces.js'
10
10
  import { Transaction } from '../../transaction/index.js'
11
+ import { WalletProtocol } from '../../wallet/Wallet.interfaces.js'
11
12
 
12
13
  function verifyTruthy<T>(v: T | undefined): T {
13
14
  if (v == null) throw new Error('must have value')
@@ -117,7 +118,7 @@ export default class PushDrop implements ScriptTemplate {
117
118
  * Creates a PushDrop locking script with arbitrary data fields and a public key lock.
118
119
  *
119
120
  * @param {number[][]} fields - The token fields to include in the locking script.
120
- * @param {[SecurityLevel, string]} protocolID - The protocol ID to use.
121
+ * @param {WalletProtocol} protocolID - The protocol ID to use.
121
122
  * @param {string} keyID - The key ID to use.
122
123
  * @param {string} counterparty - The counterparty involved in the transaction, "self" or "anyone".
123
124
  * @param {boolean} [forSelf=false] - Flag indicating if the lock is for the creator (default no).
@@ -126,7 +127,7 @@ export default class PushDrop implements ScriptTemplate {
126
127
  */
127
128
  async lock(
128
129
  fields: number[][],
129
- protocolID: [SecurityLevel, string],
130
+ protocolID: WalletProtocol,
130
131
  keyID: string,
131
132
  counterparty: string,
132
133
  forSelf = false,
@@ -177,7 +178,7 @@ export default class PushDrop implements ScriptTemplate {
177
178
  /**
178
179
  * Creates an unlocking script for spending a PushDrop token output.
179
180
  *
180
- * @param {[SecurityLevel, string]} protocolID - The protocol ID to use.
181
+ * @param {WalletProtocol} protocolID - The protocol ID to use.
181
182
  * @param {string} keyID - The key ID to use.
182
183
  * @param {string} counterparty - The counterparty involved in the transaction, "self" or "anyone".
183
184
  * @param {string} [sourceTXID] - The TXID of the source transaction.
@@ -188,7 +189,7 @@ export default class PushDrop implements ScriptTemplate {
188
189
  * @returns {Object} An object containing functions to sign the transaction and estimate the script length.
189
190
  */
190
191
  unlock(
191
- protocolID: [SecurityLevel, string],
192
+ protocolID: WalletProtocol,
192
193
  keyID: string,
193
194
  counterparty: string,
194
195
  signOutputs: 'all' | 'none' | 'single' = 'all',
@@ -6,7 +6,7 @@ import LockingScript from '../script/LockingScript.js'
6
6
  import { Reader, Writer, toHex, toArray } from '../primitives/utils.js'
7
7
  import { hash256 } from '../primitives/Hash.js'
8
8
  import FeeModel from './FeeModel.js'
9
- import SatoshisPerKilobyte from './fee-models/SatoshisPerKilobyte.js'
9
+ import LivePolicy from './fee-models/LivePolicy.js'
10
10
  import { Broadcaster, BroadcastResponse, BroadcastFailure } from './Broadcaster.js'
11
11
  import MerklePath from './MerklePath.js'
12
12
  import Spend from '../script/Spend.js'
@@ -411,7 +411,7 @@ export default class Transaction {
411
411
 
412
412
  /**
413
413
  * Computes fees prior to signing.
414
- * If no fee model is provided, uses a SatoshisPerKilobyte fee model that pays 1 sat/kb.
414
+ * If no fee model is provided, uses a LivePolicy fee model that fetches current rates from ARC.
415
415
  * If fee is a number, the transaction uses that value as fee.
416
416
  *
417
417
  * @param modelOrFee - The initialized fee model to use or fixed fee for the transaction
@@ -420,7 +420,7 @@ export default class Transaction {
420
420
  *
421
421
  */
422
422
  async fee (
423
- modelOrFee: FeeModel | number = new SatoshisPerKilobyte(1),
423
+ modelOrFee: FeeModel | number = LivePolicy.getInstance(),
424
424
  changeDistribution: 'equal' | 'random' = 'equal'
425
425
  ): Promise<void> {
426
426
  this.cachedHash = undefined
@@ -774,7 +774,7 @@ export default class Transaction {
774
774
  *
775
775
  * @returns Whether the transaction is valid according to the rules of SPV.
776
776
  *
777
- * @example tx.verify(new WhatsOnChain(), new SatoshisPerKilobyte(1))
777
+ * @example tx.verify(new WhatsOnChain(), LivePolicy.getInstance())
778
778
  */
779
779
  async verify (
780
780
  chainTracker: ChainTracker | 'scripts only' = defaultChainTracker(),
@@ -0,0 +1,97 @@
1
+ import SatoshisPerKilobyte from './SatoshisPerKilobyte.js'
2
+ import Transaction from '../Transaction.js'
3
+
4
+ /**
5
+ * Represents a live fee policy that fetches current rates from ARC GorillaPool.
6
+ * Extends SatoshisPerKilobyte to reuse transaction size calculation logic.
7
+ */
8
+ export default class LivePolicy extends SatoshisPerKilobyte {
9
+ private static readonly ARC_POLICY_URL = 'https://arc.gorillapool.io/v1/policy'
10
+ private static instance: LivePolicy | null = null
11
+ private cachedRate: number | null = null
12
+ private cacheTimestamp: number = 0
13
+ private readonly cacheValidityMs: number
14
+
15
+ /**
16
+ * Constructs an instance of the live policy fee model.
17
+ *
18
+ * @param {number} cacheValidityMs - How long to cache the fee rate in milliseconds (default: 5 minutes)
19
+ */
20
+ constructor(cacheValidityMs: number = 5 * 60 * 1000) {
21
+ super(100) // Initialize with dummy value, will be overridden by fetchFeeRate
22
+ this.cacheValidityMs = cacheValidityMs
23
+ }
24
+
25
+ /**
26
+ * Gets the singleton instance of LivePolicy to ensure cache sharing across the application.
27
+ *
28
+ * @param {number} cacheValidityMs - How long to cache the fee rate in milliseconds (default: 5 minutes)
29
+ * @returns The singleton LivePolicy instance
30
+ */
31
+ static getInstance(cacheValidityMs: number = 5 * 60 * 1000): LivePolicy {
32
+ if (!LivePolicy.instance) {
33
+ LivePolicy.instance = new LivePolicy(cacheValidityMs)
34
+ }
35
+ return LivePolicy.instance
36
+ }
37
+
38
+ /**
39
+ * Fetches the current fee rate from ARC GorillaPool API.
40
+ *
41
+ * @returns The current satoshis per kilobyte rate
42
+ */
43
+ private async fetchFeeRate(): Promise<number> {
44
+ const now = Date.now()
45
+
46
+ // Return cached rate if still valid
47
+ if (this.cachedRate !== null && (now - this.cacheTimestamp) < this.cacheValidityMs) {
48
+ return this.cachedRate
49
+ }
50
+
51
+ try {
52
+ const response = await fetch(LivePolicy.ARC_POLICY_URL)
53
+ if (!response.ok) {
54
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
55
+ }
56
+
57
+ const response_data = await response.json()
58
+
59
+ if (!response_data.policy?.miningFee || typeof response_data.policy.miningFee.satoshis !== 'number' || typeof response_data.policy.miningFee.bytes !== 'number') {
60
+ throw new Error('Invalid policy response format')
61
+ }
62
+
63
+ // Convert to satoshis per kilobyte
64
+ const rate = (response_data.policy.miningFee.satoshis / response_data.policy.miningFee.bytes) * 1000
65
+
66
+ // Cache the result
67
+ this.cachedRate = rate
68
+ this.cacheTimestamp = now
69
+
70
+ return rate
71
+ } catch (error) {
72
+ // If we have a cached rate, use it as fallback
73
+ if (this.cachedRate !== null) {
74
+ console.warn('Failed to fetch live fee rate, using cached value:', error)
75
+ return this.cachedRate
76
+ }
77
+
78
+ // Otherwise, use a reasonable default (100 sat/kb)
79
+ console.warn('Failed to fetch live fee rate, using default 100 sat/kb:', error)
80
+ return 100
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Computes the fee for a given transaction using the current live rate.
86
+ * Overrides the parent method to use dynamic rate fetching.
87
+ *
88
+ * @param tx The transaction for which a fee is to be computed.
89
+ * @returns The fee in satoshis for the transaction.
90
+ */
91
+ async computeFee(tx: Transaction): Promise<number> {
92
+ const rate = await this.fetchFeeRate()
93
+ // Update the value property so parent's computeFee uses the live rate
94
+ this.value = rate
95
+ return super.computeFee(tx)
96
+ }
97
+ }
@@ -0,0 +1,148 @@
1
+ import LivePolicy from '../LivePolicy.js'
2
+
3
+ describe('LivePolicy', () => {
4
+ let consoleSpy: jest.SpyInstance
5
+
6
+ const createMockTransaction = () => ({
7
+ inputs: [],
8
+ outputs: []
9
+ } as any)
10
+
11
+ const createSuccessfulFetchMock = (satoshis: number, bytes: number = 1000) =>
12
+ jest.fn().mockResolvedValue({
13
+ ok: true,
14
+ json: async () => ({
15
+ policy: {
16
+ miningFee: { satoshis, bytes }
17
+ }
18
+ })
19
+ })
20
+
21
+ const createErrorFetchMock = (status = 500, statusText = 'Internal Server Error') =>
22
+ jest.fn().mockResolvedValue({
23
+ ok: false,
24
+ status,
25
+ statusText
26
+ })
27
+
28
+ const createInvalidResponseMock = () =>
29
+ jest.fn().mockResolvedValue({
30
+ ok: true,
31
+ json: async () => ({ invalid: 'response' })
32
+ })
33
+
34
+ const createNetworkErrorMock = () =>
35
+ jest.fn().mockRejectedValue(new Error('Network error'))
36
+
37
+ const expectDefaultFallback = (consoleSpy: jest.SpyInstance) => {
38
+ expect(consoleSpy).toHaveBeenCalledWith(
39
+ 'Failed to fetch live fee rate, using default 100 sat/kb:',
40
+ expect.any(Error)
41
+ )
42
+ }
43
+
44
+ const expectCachedFallback = (consoleSpy: jest.SpyInstance) => {
45
+ expect(consoleSpy).toHaveBeenCalledWith(
46
+ 'Failed to fetch live fee rate, using cached value:',
47
+ expect.any(Error)
48
+ )
49
+ }
50
+
51
+ beforeEach(() => {
52
+ ;(LivePolicy as any).instance = null
53
+ jest.clearAllMocks()
54
+ consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
55
+ })
56
+
57
+ afterEach(() => {
58
+ consoleSpy.mockRestore()
59
+ })
60
+
61
+ it('should return the same instance when getInstance is called multiple times', () => {
62
+ const instance1 = LivePolicy.getInstance()
63
+ const instance2 = LivePolicy.getInstance()
64
+
65
+ expect(instance1).toBe(instance2)
66
+ expect(instance1).toBeInstanceOf(LivePolicy)
67
+ })
68
+
69
+ it('should share cache between singleton instances', async () => {
70
+ const instance1 = LivePolicy.getInstance()
71
+ const instance2 = LivePolicy.getInstance()
72
+
73
+ global.fetch = createSuccessfulFetchMock(5)
74
+ const mockTx = createMockTransaction()
75
+
76
+ const fee1 = await instance1.computeFee(mockTx)
77
+ const fee2 = await instance2.computeFee(mockTx)
78
+
79
+ expect(fee1).toBe(fee2)
80
+ expect(fee1).toBe(1) // 5 sat/kb rate, minimum tx size gets 1 sat
81
+ expect(global.fetch).toHaveBeenCalledTimes(1)
82
+ })
83
+
84
+ it('should allow different cache validity when creating singleton', () => {
85
+ const instance1 = LivePolicy.getInstance(10000)
86
+ const instance2 = LivePolicy.getInstance(20000)
87
+
88
+ expect(instance1).toBe(instance2)
89
+ expect((instance1 as any).cacheValidityMs).toBe(10000)
90
+ })
91
+
92
+ it('should create instance with custom cache validity', () => {
93
+ const instance = new LivePolicy(30000)
94
+ expect((instance as any).cacheValidityMs).toBe(30000)
95
+ })
96
+
97
+ it('should handle HTTP error responses', async () => {
98
+ const instance = LivePolicy.getInstance()
99
+ global.fetch = createErrorFetchMock()
100
+ const mockTx = createMockTransaction()
101
+
102
+ const fee = await instance.computeFee(mockTx)
103
+
104
+ expect(fee).toBe(1)
105
+ expectDefaultFallback(consoleSpy)
106
+ })
107
+
108
+ it('should handle invalid API response format', async () => {
109
+ const instance = LivePolicy.getInstance()
110
+ global.fetch = createInvalidResponseMock()
111
+ const mockTx = createMockTransaction()
112
+
113
+ const fee = await instance.computeFee(mockTx)
114
+
115
+ expect(fee).toBe(1)
116
+ expectDefaultFallback(consoleSpy)
117
+ })
118
+
119
+ it('should use cached value when API fails after successful fetch', async () => {
120
+ const instance = LivePolicy.getInstance()
121
+ const mockTx = createMockTransaction()
122
+
123
+ // First call - successful fetch
124
+ global.fetch = createSuccessfulFetchMock(10)
125
+ const fee1 = await instance.computeFee(mockTx)
126
+ expect(fee1).toBe(1)
127
+
128
+ // Expire cache and simulate API failure
129
+ ;(instance as any).cacheTimestamp = Date.now() - (6 * 60 * 1000)
130
+ global.fetch = createNetworkErrorMock()
131
+
132
+ const fee2 = await instance.computeFee(mockTx)
133
+
134
+ expect(fee2).toBe(1)
135
+ expectCachedFallback(consoleSpy)
136
+ })
137
+
138
+ it('should handle network errors with no cached value', async () => {
139
+ const instance = LivePolicy.getInstance()
140
+ global.fetch = createNetworkErrorMock()
141
+ const mockTx = createMockTransaction()
142
+
143
+ const fee = await instance.computeFee(mockTx)
144
+
145
+ expect(fee).toBe(1)
146
+ expectDefaultFallback(consoleSpy)
147
+ })
148
+ })
@@ -1 +1,2 @@
1
1
  export { default as SatoshisPerKilobyte } from './SatoshisPerKilobyte.js'
2
+ export { default as LivePolicy } from './LivePolicy.js'
@@ -58,6 +58,7 @@ export default class WalletClient implements WalletInterface {
58
58
  | 'window.CWI'
59
59
  | 'json-api'
60
60
  | 'react-native'
61
+ | 'secure-json-api'
61
62
  | WalletInterface = 'auto',
62
63
  originator?: OriginatorDomainNameStringUnder250Bytes
63
64
  ) {
@@ -68,6 +69,7 @@ export default class WalletClient implements WalletInterface {
68
69
  if (substrate === 'XDM') substrate = new XDMSubstrate()
69
70
  if (substrate === 'json-api') substrate = new HTTPWalletJSON(originator)
70
71
  if (substrate === 'react-native') substrate = new ReactNativeWebView(originator)
72
+ if (substrate === 'secure-json-api') substrate = new HTTPWalletJSON(originator, 'https://localhost:2121')
71
73
  this.substrate = substrate
72
74
  this.originator = originator
73
75
  }
@@ -76,61 +78,58 @@ export default class WalletClient implements WalletInterface {
76
78
  if (typeof this.substrate === 'object') {
77
79
  return // substrate is already connected
78
80
  }
79
- let sub: WalletInterface
80
- const checkSub = async (timeout?: number): Promise<void> => {
81
- let result
82
- if (typeof timeout === 'number') {
83
- result = await Promise.race([
84
- sub.getVersion({}),
85
- new Promise<never>((_resolve, reject) =>
86
- setTimeout(() => reject(new Error('Timed out.')), timeout)
87
- )
88
- ])
89
- } else {
90
- result = await sub.getVersion({})
91
- }
92
- if (typeof result !== 'object' || typeof result.version !== 'string') {
93
- throw new Error('Failed to use substrate.')
94
- }
95
- }
96
- try {
97
- sub = new WindowCWISubstrate()
98
- await checkSub()
99
- this.substrate = sub
100
- } catch (e) {
101
- // XDM failed, try the next one...
81
+
82
+ const attemptSubstrate = async (factory: () => WalletInterface, timeout?: number): Promise<{ success: boolean, sub?: WalletInterface }> => {
102
83
  try {
103
- sub = new XDMSubstrate()
104
- await checkSub(MAX_XDM_RESPONSE_WAIT)
105
- this.substrate = sub
106
- } catch (e) {
107
- // HTTP wire failed, move on...
108
- try {
109
- sub = new WalletWireTransceiver(new HTTPWalletWire(this.originator))
110
- await checkSub()
111
- this.substrate = sub
112
- } catch (e) {
113
- // HTTP Wire failed, attempt the next...
114
- try {
115
- sub = new HTTPWalletJSON(this.originator)
116
- await checkSub()
117
- this.substrate = sub
118
- } catch (e) {
119
- // HTTP JSON failed, attempt the next...
120
- try {
121
- sub = new ReactNativeWebView(this.originator)
122
- await checkSub()
123
- this.substrate = sub
124
- } catch (e) {
125
- // No comms. Tell the user to install a BSV wallet.
126
- throw new Error(
127
- 'No wallet available over any communication substrate. Install a BSV wallet today!'
128
- )
129
- }
130
- }
84
+ const sub = factory()
85
+ let result
86
+ if (typeof timeout === 'number') {
87
+ result = await Promise.race([
88
+ sub.getVersion({}),
89
+ new Promise<never>((_resolve, reject) =>
90
+ setTimeout(() => reject(new Error('Timed out.')), timeout)
91
+ )
92
+ ])
93
+ } else {
94
+ result = await sub.getVersion({})
95
+ }
96
+ if (typeof result !== 'object' || typeof result.version !== 'string') {
97
+ return { success: false }
131
98
  }
99
+ return { success: true, sub }
100
+ } catch {
101
+ return { success: false }
132
102
  }
133
103
  }
104
+
105
+ // Try fast substrates first
106
+ const fastAttempts = [
107
+ attemptSubstrate(() => new WindowCWISubstrate()),
108
+ attemptSubstrate(() => new HTTPWalletJSON(this.originator, 'https://localhost:2121')),
109
+ attemptSubstrate(() => new HTTPWalletJSON(this.originator)),
110
+ attemptSubstrate(() => new ReactNativeWebView(this.originator)),
111
+ attemptSubstrate(() => new WalletWireTransceiver(new HTTPWalletWire(this.originator)))
112
+ ]
113
+
114
+ const fastResults = await Promise.allSettled(fastAttempts)
115
+ const fastSuccessful = fastResults
116
+ .filter((r): r is PromiseFulfilledResult<{ success: boolean, sub?: WalletInterface }> => r.status === 'fulfilled' && r.value.success && r.value.sub !== undefined)
117
+ .map(r => r.value.sub as WalletInterface)
118
+
119
+ if (fastSuccessful.length > 0) {
120
+ this.substrate = fastSuccessful[0]
121
+ return
122
+ }
123
+
124
+ // Fall back to slower XDM substrate
125
+ const xdmResult = await attemptSubstrate(() => new XDMSubstrate(), MAX_XDM_RESPONSE_WAIT)
126
+ if (xdmResult.success && xdmResult.sub !== undefined) {
127
+ this.substrate = xdmResult.sub
128
+ } else {
129
+ throw new Error(
130
+ 'No wallet available over any communication substrate. Install a BSV wallet today!'
131
+ )
132
+ }
134
133
  }
135
134
 
136
135
  async createAction (args: CreateActionArgs): Promise<CreateActionResult> {
@@ -1782,6 +1782,27 @@ export default class WalletWireProcessor implements WalletWire {
1782
1782
  // Write certificate binary length and data
1783
1783
  resultWriter.writeVarIntNum(certBin.length)
1784
1784
  resultWriter.write(certBin)
1785
+
1786
+ if (cert.keyring && Object.keys(cert.keyring).length > 0) {
1787
+ resultWriter.writeInt8(1) // Flag indicating keyring is present
1788
+ const keyringEntries = Object.entries(cert.keyring)
1789
+ resultWriter.writeVarIntNum(keyringEntries.length)
1790
+ for (const [fieldName, fieldValue] of keyringEntries) {
1791
+ const fieldNameBytes = Utils.toArray(fieldName, 'utf8')
1792
+ resultWriter.writeVarIntNum(fieldNameBytes.length)
1793
+ resultWriter.write(fieldNameBytes)
1794
+
1795
+ const fieldValueBytes = Utils.toArray(fieldValue, 'base64')
1796
+ resultWriter.writeVarIntNum(fieldValueBytes.length)
1797
+ resultWriter.write(fieldValueBytes)
1798
+ }
1799
+ } else {
1800
+ resultWriter.writeInt8(0) // Flag indicating no keyring
1801
+ }
1802
+
1803
+ const verifierBytes = Utils.toArray(cert.verifier, 'hex')
1804
+ resultWriter.writeVarIntNum(verifierBytes.length)
1805
+ resultWriter.write(verifierBytes)
1785
1806
  }
1786
1807
 
1787
1808
  // Return the response
@@ -11,6 +11,7 @@ import {
11
11
  BooleanDefaultTrue,
12
12
  Byte,
13
13
  CertificateFieldNameUnder50Bytes,
14
+ CertificateResult,
14
15
  CreateActionArgs,
15
16
  CreateActionResult,
16
17
  DescriptionString5to50Bytes,
@@ -1672,22 +1673,33 @@ export default class WalletWireTransceiver implements WalletInterface {
1672
1673
  )
1673
1674
  const resultReader = new Utils.Reader(result)
1674
1675
  const totalCertificates = resultReader.readVarIntNum()
1675
- const certificates: Array<{
1676
- type: Base64String
1677
- subject: PubKeyHex
1678
- serialNumber: Base64String
1679
- certifier: PubKeyHex
1680
- revocationOutpoint: OutpointString
1681
- signature: HexString
1682
- fields: Record<CertificateFieldNameUnder50Bytes, Base64String>
1683
- }> = []
1676
+ const certificates: Array<CertificateResult> = []
1684
1677
  for (let i = 0; i < totalCertificates; i++) {
1685
1678
  const certificateLength = resultReader.readVarIntNum()
1686
1679
  const certificateBin = resultReader.read(certificateLength)
1687
1680
  const cert = Certificate.fromBinary(certificateBin)
1681
+ const keyringForVerifier: Record<string, string> = {}
1682
+ if (resultReader.readInt8() === 1) {
1683
+ const numFields = resultReader.readVarIntNum()
1684
+ for (let i = 0; i < numFields; i++) {
1685
+ const fieldKeyLength = resultReader.readVarIntNum()
1686
+ const fieldKey = Utils.toUTF8(resultReader.read(fieldKeyLength))
1687
+ const fieldValueLength = resultReader.readVarIntNum()
1688
+ keyringForVerifier[fieldKey] = Utils.toBase64(
1689
+ resultReader.read(fieldValueLength)
1690
+ )
1691
+ }
1692
+ }
1693
+ const verifierLength = resultReader.readVarIntNum()
1694
+ let verifier: string | undefined = undefined
1695
+ if (verifierLength > 0) {
1696
+ verifier = Utils.toUTF8(resultReader.read(verifierLength))
1697
+ }
1688
1698
  certificates.push({
1689
1699
  ...cert,
1690
- signature: cert.signature as string
1700
+ signature: cert.signature as string,
1701
+ keyring: keyringForVerifier,
1702
+ verifier,
1691
1703
  })
1692
1704
  }
1693
1705
  return {