@bsv/wallet-toolbox 1.3.24 → 1.3.25

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 (112) hide show
  1. package/mobile/out/src/monitor/Monitor.d.ts.map +1 -1
  2. package/mobile/out/src/monitor/Monitor.js +4 -0
  3. package/mobile/out/src/monitor/Monitor.js.map +1 -1
  4. package/mobile/out/src/monitor/tasks/TaskServiceCallHistory.d.ts +12 -0
  5. package/mobile/out/src/monitor/tasks/TaskServiceCallHistory.d.ts.map +1 -0
  6. package/mobile/out/src/monitor/tasks/TaskServiceCallHistory.js +23 -0
  7. package/mobile/out/src/monitor/tasks/TaskServiceCallHistory.js.map +1 -0
  8. package/mobile/out/src/sdk/WalletServices.interfaces.d.ts +2 -0
  9. package/mobile/out/src/sdk/WalletServices.interfaces.d.ts.map +1 -1
  10. package/mobile/out/src/sdk/WalletStorage.interfaces.d.ts +5 -0
  11. package/mobile/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  12. package/mobile/out/src/services/ServiceCollection.d.ts +38 -0
  13. package/mobile/out/src/services/ServiceCollection.d.ts.map +1 -1
  14. package/mobile/out/src/services/ServiceCollection.js +85 -0
  15. package/mobile/out/src/services/ServiceCollection.js.map +1 -1
  16. package/mobile/out/src/services/Services.d.ts +11 -2
  17. package/mobile/out/src/services/Services.d.ts.map +1 -1
  18. package/mobile/out/src/services/Services.js +159 -68
  19. package/mobile/out/src/services/Services.js.map +1 -1
  20. package/mobile/out/src/services/createDefaultWalletServicesOptions.d.ts +1 -0
  21. package/mobile/out/src/services/createDefaultWalletServicesOptions.d.ts.map +1 -1
  22. package/mobile/out/src/services/createDefaultWalletServicesOptions.js +15 -1
  23. package/mobile/out/src/services/createDefaultWalletServicesOptions.js.map +1 -1
  24. package/mobile/out/src/services/providers/ARC.d.ts +3 -2
  25. package/mobile/out/src/services/providers/ARC.d.ts.map +1 -1
  26. package/mobile/out/src/services/providers/ARC.js +5 -4
  27. package/mobile/out/src/services/providers/ARC.js.map +1 -1
  28. package/mobile/out/src/signer/methods/internalizeAction.d.ts.map +1 -1
  29. package/mobile/out/src/signer/methods/internalizeAction.js +3 -13
  30. package/mobile/out/src/signer/methods/internalizeAction.js.map +1 -1
  31. package/mobile/out/src/storage/StorageProvider.js +1 -1
  32. package/mobile/out/src/storage/StorageProvider.js.map +1 -1
  33. package/mobile/out/src/storage/methods/createAction.d.ts.map +1 -1
  34. package/mobile/out/src/storage/methods/createAction.js +5 -1
  35. package/mobile/out/src/storage/methods/createAction.js.map +1 -1
  36. package/mobile/out/src/storage/methods/internalizeAction.js +2 -1
  37. package/mobile/out/src/storage/methods/internalizeAction.js.map +1 -1
  38. package/mobile/package-lock.json +7 -6
  39. package/mobile/package.json +2 -2
  40. package/out/src/monitor/Monitor.d.ts.map +1 -1
  41. package/out/src/monitor/Monitor.js +4 -0
  42. package/out/src/monitor/Monitor.js.map +1 -1
  43. package/out/src/monitor/tasks/TaskServiceCallHistory.d.ts +12 -0
  44. package/out/src/monitor/tasks/TaskServiceCallHistory.d.ts.map +1 -0
  45. package/out/src/monitor/tasks/TaskServiceCallHistory.js +23 -0
  46. package/out/src/monitor/tasks/TaskServiceCallHistory.js.map +1 -0
  47. package/out/src/sdk/WalletServices.interfaces.d.ts +2 -0
  48. package/out/src/sdk/WalletServices.interfaces.d.ts.map +1 -1
  49. package/out/src/sdk/WalletStorage.interfaces.d.ts +5 -0
  50. package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  51. package/out/src/services/ServiceCollection.d.ts +38 -0
  52. package/out/src/services/ServiceCollection.d.ts.map +1 -1
  53. package/out/src/services/ServiceCollection.js +85 -0
  54. package/out/src/services/ServiceCollection.js.map +1 -1
  55. package/out/src/services/Services.d.ts +11 -2
  56. package/out/src/services/Services.d.ts.map +1 -1
  57. package/out/src/services/Services.js +159 -68
  58. package/out/src/services/Services.js.map +1 -1
  59. package/out/src/services/__tests/ArcGorillaPool.man.test.d.ts +2 -0
  60. package/out/src/services/__tests/ArcGorillaPool.man.test.d.ts.map +1 -0
  61. package/out/src/services/__tests/ArcGorillaPool.man.test.js +93 -0
  62. package/out/src/services/__tests/ArcGorillaPool.man.test.js.map +1 -0
  63. package/out/src/services/createDefaultWalletServicesOptions.d.ts +1 -0
  64. package/out/src/services/createDefaultWalletServicesOptions.d.ts.map +1 -1
  65. package/out/src/services/createDefaultWalletServicesOptions.js +15 -1
  66. package/out/src/services/createDefaultWalletServicesOptions.js.map +1 -1
  67. package/out/src/services/providers/ARC.d.ts +3 -2
  68. package/out/src/services/providers/ARC.d.ts.map +1 -1
  69. package/out/src/services/providers/ARC.js +5 -4
  70. package/out/src/services/providers/ARC.js.map +1 -1
  71. package/out/src/signer/methods/internalizeAction.d.ts.map +1 -1
  72. package/out/src/signer/methods/internalizeAction.js +3 -13
  73. package/out/src/signer/methods/internalizeAction.js.map +1 -1
  74. package/out/src/storage/StorageKnex.d.ts.map +1 -1
  75. package/out/src/storage/StorageKnex.js +50 -2
  76. package/out/src/storage/StorageKnex.js.map +1 -1
  77. package/out/src/storage/StorageProvider.js +1 -1
  78. package/out/src/storage/StorageProvider.js.map +1 -1
  79. package/out/src/storage/methods/createAction.d.ts.map +1 -1
  80. package/out/src/storage/methods/createAction.js +5 -1
  81. package/out/src/storage/methods/createAction.js.map +1 -1
  82. package/out/src/storage/methods/internalizeAction.js +2 -1
  83. package/out/src/storage/methods/internalizeAction.js.map +1 -1
  84. package/out/src/storage/schema/KnexMigrations.d.ts.map +1 -1
  85. package/out/src/storage/schema/KnexMigrations.js +12 -0
  86. package/out/src/storage/schema/KnexMigrations.js.map +1 -1
  87. package/out/test/Wallet/local/localWallet.man.test.js +12 -16
  88. package/out/test/Wallet/local/localWallet.man.test.js.map +1 -1
  89. package/out/test/Wallet/support/operations.man.test.js +96 -6
  90. package/out/test/Wallet/support/operations.man.test.js.map +1 -1
  91. package/out/test/storage/KnexMigrations.test.js +1 -1
  92. package/out/test/storage/KnexMigrations.test.js.map +1 -1
  93. package/out/tsconfig.all.tsbuildinfo +1 -1
  94. package/package.json +2 -2
  95. package/src/monitor/Monitor.ts +4 -0
  96. package/src/monitor/tasks/TaskServiceCallHistory.ts +26 -0
  97. package/src/sdk/WalletServices.interfaces.ts +2 -0
  98. package/src/sdk/WalletStorage.interfaces.ts +5 -0
  99. package/src/services/ServiceCollection.ts +121 -0
  100. package/src/services/Services.ts +157 -71
  101. package/src/services/__tests/ArcGorillaPool.man.test.ts +108 -0
  102. package/src/services/createDefaultWalletServicesOptions.ts +16 -1
  103. package/src/services/providers/ARC.ts +8 -6
  104. package/src/signer/methods/internalizeAction.ts +4 -14
  105. package/src/storage/StorageKnex.ts +25 -1
  106. package/src/storage/StorageProvider.ts +1 -1
  107. package/src/storage/methods/createAction.ts +5 -3
  108. package/src/storage/methods/internalizeAction.ts +2 -1
  109. package/src/storage/schema/KnexMigrations.ts +13 -0
  110. package/test/Wallet/local/localWallet.man.test.ts +13 -16
  111. package/test/Wallet/support/operations.man.test.ts +107 -7
  112. package/test/storage/KnexMigrations.test.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/wallet-toolbox",
3
- "version": "1.3.24",
3
+ "version": "1.3.25",
4
4
  "description": "BRC100 conforming wallet, wallet storage and wallet signer components",
5
5
  "main": "./out/src/index.js",
6
6
  "types": "./out/src/index.d.ts",
@@ -32,7 +32,7 @@
32
32
  "dependencies": {
33
33
  "@bsv/auth-express-middleware": "^1.1.2",
34
34
  "@bsv/payment-express-middleware": "^1.0.6",
35
- "@bsv/sdk": "^1.4.24",
35
+ "@bsv/sdk": "^1.5.1",
36
36
  "express": "^4.21.2",
37
37
  "idb": "^8.0.2",
38
38
  "knex": "^3.1.0",
@@ -13,6 +13,7 @@ import { TaskFailAbandoned } from './tasks/TaskFailAbandoned'
13
13
  import { TaskCheckForProofs } from './tasks/TaskCheckForProofs'
14
14
  import { TaskClock } from './tasks/TaskClock'
15
15
  import { TaskNewHeader } from './tasks/TaskNewHeader'
16
+ import { TaskServiceCallHistory } from './tasks/TaskServiceCallHistory'
16
17
 
17
18
  import { TaskSendWaiting } from './tasks/TaskSendWaiting'
18
19
  import { TaskCheckNoSends } from './tasks/TaskCheckNoSends'
@@ -110,6 +111,7 @@ export class Monitor {
110
111
  addAllTasksToOther(): void {
111
112
  this._otherTasks.push(new TaskClock(this))
112
113
  this._otherTasks.push(new TaskNewHeader(this))
114
+ this._otherTasks.push(new TaskServiceCallHistory(this))
113
115
  this._otherTasks.push(new TaskPurge(this, this.defaultPurgeParams))
114
116
  this._otherTasks.push(new TaskReviewStatus(this))
115
117
  this._otherTasks.push(new TaskSendWaiting(this))
@@ -128,6 +130,7 @@ export class Monitor {
128
130
  addDefaultTasks(): void {
129
131
  this._tasks.push(new TaskClock(this))
130
132
  this._tasks.push(new TaskNewHeader(this))
133
+ this._tasks.push(new TaskServiceCallHistory(this))
131
134
  this._tasks.push(new TaskSendWaiting(this, 8 * this.oneSecond, 7 * this.oneSecond)) // Check every 8 seconds but must be 7 seconds old
132
135
  this._tasks.push(new TaskCheckForProofs(this, 2 * this.oneHour)) // Every two hours if no block found
133
136
  this._tasks.push(new TaskCheckNoSends(this))
@@ -144,6 +147,7 @@ export class Monitor {
144
147
  addMultiUserTasks(): void {
145
148
  this._tasks.push(new TaskClock(this))
146
149
  this._tasks.push(new TaskNewHeader(this))
150
+ this._tasks.push(new TaskServiceCallHistory(this))
147
151
  this._tasks.push(new TaskSendWaiting(this, 8 * this.oneSecond, 7 * this.oneSecond)) // Check every 8 seconds but must be 7 seconds old
148
152
  this._tasks.push(new TaskCheckForProofs(this, 2 * this.oneHour)) // Every two hours if no block found
149
153
  this._tasks.push(new TaskCheckNoSends(this))
@@ -0,0 +1,26 @@
1
+ import { TableMonitorEvent } from '../../storage/index.client'
2
+ import { Monitor } from '../Monitor'
3
+ import { WalletMonitorTask } from './WalletMonitorTask'
4
+
5
+ export class TaskServiceCallHistory extends WalletMonitorTask {
6
+ static taskName = 'ServiceCallHistory'
7
+
8
+ constructor(
9
+ monitor: Monitor,
10
+ public triggerMsecs = monitor.oneMinute * 12
11
+ ) {
12
+ super(monitor, TaskServiceCallHistory.taskName)
13
+ }
14
+
15
+ trigger(nowMsecsSinceEpoch: number): { run: boolean } {
16
+ return {
17
+ run: nowMsecsSinceEpoch > this.lastRunMsecsSinceEpoch + this.triggerMsecs
18
+ }
19
+ }
20
+
21
+ async runTask(): Promise<string> {
22
+ const r = await this.monitor.services.getServicesCallHistory(true)
23
+ const log = JSON.stringify(r)
24
+ return log
25
+ }
26
+ }
@@ -200,6 +200,8 @@ export interface WalletServicesOptions {
200
200
  chaintracks?: ChaintracksServiceClient
201
201
  arcUrl: string
202
202
  arcConfig: ArcConfig
203
+ arcGorillaPoolUrl?: string
204
+ arcGorillaPoolConfig?: ArcConfig
203
205
  }
204
206
 
205
207
  export interface GetStatusForTxidsResult {
@@ -189,6 +189,11 @@ export interface FindSincePagedArgs {
189
189
  since?: Date
190
190
  paged?: Paged
191
191
  trx?: TrxToken
192
+ /**
193
+ * Support for orderDescending is implemented in StorageKnex for basic table find methods,
194
+ * excluding certificate_fields table, map tables, and settings (singleton row table).
195
+ */
196
+ orderDescending?: boolean
192
197
  }
193
198
 
194
199
  export interface FindForUserSincePagedArgs extends FindSincePagedArgs {
@@ -1,10 +1,16 @@
1
+ import { WalletError } from "../sdk/WalletError";
2
+
1
3
  export class ServiceCollection<T> {
4
+ since: Date
2
5
  services: { name: string; service: T }[]
3
6
  _index: number
4
7
 
8
+ _callHistory: Record<string, ServiceCallHistory> = {}
9
+
5
10
  constructor(services?: { name: string; service: T }[]) {
6
11
  this.services = services || []
7
12
  this._index = 0
13
+ this.since = new Date()
8
14
  }
9
15
 
10
16
  add(s: { name: string; service: T }): ServiceCollection<T> {
@@ -24,6 +30,23 @@ export class ServiceCollection<T> {
24
30
  return this.services[this._index].service
25
31
  }
26
32
 
33
+ get serviceToCall(): ServiceToCall<T> {
34
+ const i = this._index
35
+ const name = this.services[i].name
36
+ const service = this.services[i].service
37
+ const call = { name, when: new Date(), durationMsecs: 0, success: false, result: undefined, error: undefined }
38
+ return { name, service, call }
39
+ }
40
+
41
+ get allServicesToCall(): ServiceToCall<T>[] {
42
+ const all: ServiceToCall<T>[] = []
43
+ for (let i = 0; i < this.services.length; i++) {
44
+ all.push(this.serviceToCall)
45
+ this.next()
46
+ }
47
+ return all
48
+ }
49
+
27
50
  get allServices() {
28
51
  return this.services.map(x => x.service)
29
52
  }
@@ -47,4 +70,102 @@ export class ServiceCollection<T> {
47
70
  clone(): ServiceCollection<T> {
48
71
  return new ServiceCollection([...this.services])
49
72
  }
73
+
74
+ _addServiceCall(name: string, call: ServiceCall): ServiceCallHistory {
75
+ let h = this._callHistory[name]
76
+ if (!h) {
77
+ h = { name, calls: [], count: 0, countError: 0, countFailure: 0, countSuccess: 0, since: this.since }
78
+ this._callHistory[name] = h
79
+ }
80
+ h.calls.push(call)
81
+ h.count++
82
+ h.calls = h.calls.slice(-32)
83
+ return h
84
+ }
85
+
86
+ addServiceCallSuccess(stc: ServiceToCall<T>, result?: string): void {
87
+ const call = stc.call
88
+ call.success = true
89
+ call.result = result
90
+ call.error = undefined
91
+ call.durationMsecs = new Date().getTime() - call.when.getTime()
92
+ this._addServiceCall(this.name, call).countSuccess++
93
+ }
94
+
95
+ addServiceCallFailure(stc: ServiceToCall<T>, result?: string): void {
96
+ const call = stc.call
97
+ call.success = false
98
+ call.result = result
99
+ call.error = undefined
100
+ call.durationMsecs = new Date().getTime() - call.when.getTime()
101
+ this._addServiceCall(this.name, call).countFailure++
102
+ }
103
+
104
+ addServiceCallError(stc: ServiceToCall<T>, error: WalletError): void {
105
+ const call = stc.call
106
+ call.success = false
107
+ call.result = undefined
108
+ call.error = error
109
+ call.durationMsecs = new Date().getTime() - call.when.getTime()
110
+ this._addServiceCall(this.name, call).countError++
111
+ }
112
+
113
+ /**
114
+ * @returns A copy of current service call history
115
+ */
116
+ getServiceCallHistory(reset?: boolean): Record<string, ServiceCallHistory> {
117
+ const histories: Record<string, ServiceCallHistory> = {}
118
+ for (const name of Object.keys(this._callHistory)) {
119
+ const h = this._callHistory[name]
120
+ histories[name] = {
121
+ name: h.name,
122
+ count: h.count,
123
+ countError: h.countError,
124
+ countFailure: h.countFailure,
125
+ countSuccess: h.countSuccess,
126
+ since: h.since,
127
+ calls: h.calls.map(c => ({
128
+ name: c.name,
129
+ when: c.when,
130
+ durationMsecs: c.durationMsecs,
131
+ success: c.success,
132
+ result: c.result,
133
+ error: c.error ? { message: c.error.message, code: c.error.code } : undefined
134
+ }))
135
+ }
136
+ if (reset) {
137
+ h.count = 0
138
+ h.countError = 0
139
+ h.countFailure = 0
140
+ h.countSuccess = 0
141
+ h.since = new Date()
142
+ }
143
+ }
144
+ return histories
145
+ }
50
146
  }
147
+
148
+ export interface ServiceCall {
149
+ name: string
150
+ when: Date
151
+ durationMsecs: number
152
+ success: boolean
153
+ result?: string
154
+ error?: { message: string, code: string }
155
+ }
156
+
157
+ export interface ServiceCallHistory {
158
+ name: string
159
+ calls: ServiceCall[]
160
+ count: number
161
+ countSuccess: number
162
+ countFailure: number
163
+ countError: number
164
+ since: Date
165
+ }
166
+
167
+ export interface ServiceToCall<T> {
168
+ name: string
169
+ service: T
170
+ call: ServiceCall
171
+ }
@@ -1,6 +1,6 @@
1
1
  import { Transaction as BsvTransaction, Beef, ChainTracker, Utils } from '@bsv/sdk'
2
2
  import { asArray, asString, doubleSha256BE, sdk, sha256Hash, TableOutput, wait } from '../index.client'
3
- import { ServiceCollection } from './ServiceCollection'
3
+ import { ServiceCall, ServiceCollection } from './ServiceCollection'
4
4
  import { createDefaultWalletServicesOptions } from './createDefaultWalletServicesOptions'
5
5
  import { ChaintracksChainTracker } from './chaintracker'
6
6
  import { WhatsOnChain } from './providers/WhatsOnChain'
@@ -16,7 +16,8 @@ export class Services implements sdk.WalletServices {
16
16
 
17
17
  options: sdk.WalletServicesOptions
18
18
  whatsonchain: WhatsOnChain
19
- arc: ARC
19
+ arcTaal: ARC
20
+ arcGorillaPool?: ARC
20
21
  bitails: Bitails
21
22
 
22
23
  getMerklePathServices: ServiceCollection<sdk.GetMerklePathService>
@@ -36,7 +37,10 @@ export class Services implements sdk.WalletServices {
36
37
 
37
38
  this.whatsonchain = new WhatsOnChain(this.chain, { apiKey: this.options.whatsOnChainApiKey }, this)
38
39
 
39
- this.arc = new ARC(this.options.arcUrl, this.options.arcConfig)
40
+ this.arcTaal = new ARC(this.options.arcUrl, this.options.arcConfig, 'arcTaal')
41
+ if (this.options.arcGorillaPoolUrl) {
42
+ //this.arcGorillaPool = new ARC(this.options.arcGorillaPoolUrl, this.options.arcGorillaPoolConfig, 'arcGorillaPool')
43
+ }
40
44
 
41
45
  this.bitails = new Bitails(this.chain)
42
46
 
@@ -49,11 +53,17 @@ export class Services implements sdk.WalletServices {
49
53
  this.getRawTxServices = new ServiceCollection<sdk.GetRawTxService>()
50
54
  .add({ name: 'WhatsOnChain', service: this.whatsonchain.getRawTxResult.bind(this.whatsonchain) })
51
55
 
52
- //prettier-ignore
53
56
  this.postBeefServices = new ServiceCollection<sdk.PostBeefService>()
54
- .add({ name: 'TaalArcBeef', service: this.arc.postBeef.bind(this.arc) })
57
+ if (this.arcGorillaPool) {
58
+ //prettier-ignore
59
+ this.postBeefServices.add({ name: 'GorillaPool', service: this.arcGorillaPool.postBeef.bind(this.arcGorillaPool) })
60
+ }
61
+ //prettier-ignore
62
+ this.postBeefServices
63
+ .add({ name: 'TaalArcBeef', service: this.arcTaal.postBeef.bind(this.arcTaal) })
55
64
  .add({ name: 'WhatsOnChain', service: this.whatsonchain.postBeef.bind(this.whatsonchain) })
56
65
  .add({ name: 'Bitails', service: this.bitails.postBeef.bind(this.bitails) })
66
+ ;
57
67
 
58
68
  //prettier-ignore
59
69
  this.getUtxoStatusServices = new ServiceCollection<sdk.GetUtxoStatusService>()
@@ -73,6 +83,18 @@ export class Services implements sdk.WalletServices {
73
83
  .add({ name: 'exchangeratesapi', service: updateExchangeratesapi })
74
84
  }
75
85
 
86
+ getServicesCallHistory(reset?: boolean) {
87
+ return {
88
+ version: 1,
89
+ getMerklePath: this.getMerklePathServices.getServiceCallHistory(reset),
90
+ getRawTx: this.getRawTxServices.getServiceCallHistory(reset),
91
+ postBeef: this.postBeefServices.getServiceCallHistory(reset),
92
+ getUtxoStatus: this.getUtxoStatusServices.getServiceCallHistory(reset),
93
+ getStatusForTxids: this.getStatusForTxidsServices.getServiceCallHistory(reset),
94
+ getScriptHashHistory: this.getScriptHashHistoryServices.getServiceCallHistory(reset)
95
+ }
96
+ }
97
+
76
98
  async getChainTracker(): Promise<ChainTracker> {
77
99
  if (!this.options.chaintracks)
78
100
  throw new sdk.WERR_INVALID_PARAMETER('options.chaintracks', `valid to enable 'getChainTracker' service.`)
@@ -123,11 +145,22 @@ export class Services implements sdk.WalletServices {
123
145
  }
124
146
 
125
147
  for (let tries = 0; tries < services.count; tries++) {
126
- const service = services.service
127
- const r = await service(txids)
128
- if (r.status === 'success') {
129
- r0 = r
130
- break
148
+ const stc = services.serviceToCall
149
+ try {
150
+ const r = await stc.service(txids)
151
+ if (r.status === 'success') {
152
+ services.addServiceCallSuccess(stc)
153
+ r0 = r
154
+ break
155
+ } else {
156
+ if (r.error)
157
+ services.addServiceCallError(stc, r.error)
158
+ else
159
+ services.addServiceCallFailure(stc)
160
+ }
161
+ } catch (eu: unknown) {
162
+ const e = sdk.WalletError.fromUnknown(eu)
163
+ services.addServiceCallError(stc, e)
131
164
  }
132
165
  services.next()
133
166
  }
@@ -174,11 +207,22 @@ export class Services implements sdk.WalletServices {
174
207
 
175
208
  for (let retry = 0; retry < 2; retry++) {
176
209
  for (let tries = 0; tries < services.count; tries++) {
177
- const service = services.service
178
- const r = await service(output, outputFormat, outpoint)
179
- if (r.status === 'success') {
180
- r0 = r
181
- break
210
+ const stc = services.serviceToCall
211
+ try {
212
+ const r = await stc.service(output, outputFormat, outpoint)
213
+ if (r.status === 'success') {
214
+ services.addServiceCallSuccess(stc)
215
+ r0 = r
216
+ break
217
+ } else {
218
+ if (r.error)
219
+ services.addServiceCallError(stc, r.error)
220
+ else
221
+ services.addServiceCallFailure(stc)
222
+ }
223
+ } catch (eu: unknown) {
224
+ const e = sdk.WalletError.fromUnknown(eu)
225
+ services.addServiceCallError(stc, e)
182
226
  }
183
227
  services.next()
184
228
  }
@@ -200,19 +244,27 @@ export class Services implements sdk.WalletServices {
200
244
  }
201
245
 
202
246
  for (let tries = 0; tries < services.count; tries++) {
203
- const service = services.service
204
- const r = await service(hash)
205
- if (r.status === 'success') {
206
- r0 = r
207
- break
247
+ const stc = services.serviceToCall
248
+ try {
249
+ const r = await stc.service(hash)
250
+ if (r.status === 'success') {
251
+ r0 = r
252
+ break
253
+ } else {
254
+ if (r.error)
255
+ services.addServiceCallError(stc, r.error)
256
+ else
257
+ services.addServiceCallFailure(stc)
258
+ }
259
+ } catch (eu: unknown) {
260
+ const e = sdk.WalletError.fromUnknown(eu)
261
+ services.addServiceCallError(stc, e)
208
262
  }
209
263
  services.next()
210
264
  }
211
265
  return r0
212
266
  }
213
267
 
214
- postBeefCount = 0
215
-
216
268
  /**
217
269
  *
218
270
  * @param beef
@@ -220,15 +272,20 @@ export class Services implements sdk.WalletServices {
220
272
  * @returns
221
273
  */
222
274
  async postBeef(beef: Beef, txids: string[]): Promise<sdk.PostBeefResult[]> {
223
- this.postBeefCount++
224
- const services = [...this.postBeefServices.allServices]
225
- for (let i = this.postBeefCount % services.length; i > 0; i--) {
226
- // roll the array of services so the providers aren't always called in the same order.
227
- services.unshift(services.pop()!)
228
- }
275
+ const services = this.postBeefServices
276
+ const stcs = services.allServicesToCall
229
277
  let rs = await Promise.all(
230
- services.map(async service => {
231
- const r = await service(beef, txids)
278
+ stcs.map(async stc => {
279
+ const r = await stc.service(beef, txids)
280
+ if (r.status === 'success') {
281
+ services.addServiceCallSuccess(stc)
282
+ } else {
283
+ if (r.error) {
284
+ services.addServiceCallError(stc, r.error)
285
+ } else {
286
+ services.addServiceCallFailure(stc)
287
+ }
288
+ }
232
289
  return r
233
290
  })
234
291
  )
@@ -236,31 +293,44 @@ export class Services implements sdk.WalletServices {
236
293
  }
237
294
 
238
295
  async getRawTx(txid: string, useNext?: boolean): Promise<sdk.GetRawTxResult> {
239
- if (useNext) this.getRawTxServices.next()
296
+ const services = this.getRawTxServices
297
+ if (useNext) services.next()
240
298
 
241
299
  const r0: sdk.GetRawTxResult = { txid }
242
300
 
243
- for (let tries = 0; tries < this.getRawTxServices.count; tries++) {
244
- const service = this.getRawTxServices.service
245
- const r = await service(txid, this.chain)
246
- if (r.rawTx) {
247
- const hash = asString(doubleSha256BE(r.rawTx!))
248
- // Confirm transaction hash matches txid
249
- if (hash === asString(txid)) {
250
- // If we have a match, call it done.
251
- r0.rawTx = r.rawTx
252
- r0.name = r.name
253
- r0.error = undefined
254
- break
301
+ for (let tries = 0; tries < services.count; tries++) {
302
+ const stc = services.serviceToCall
303
+ try {
304
+ const r = await stc.service(txid, this.chain)
305
+ if (r.rawTx) {
306
+ const hash = asString(doubleSha256BE(r.rawTx!))
307
+ // Confirm transaction hash matches txid
308
+ if (hash === asString(txid)) {
309
+ // If we have a match, call it done.
310
+ r0.rawTx = r.rawTx
311
+ r0.name = r.name
312
+ r0.error = undefined
313
+ services.addServiceCallSuccess(stc)
314
+ break
315
+ }
316
+ r.error = new sdk.WERR_INTERNAL(`computed txid ${hash} doesn't match requested value ${txid}`)
317
+ r.rawTx = undefined
255
318
  }
256
- r.error = new sdk.WERR_INTERNAL(`computed txid ${hash} doesn't match requested value ${txid}`)
257
- r.rawTx = undefined
258
- }
259
- if (r.error && !r0.error && !r0.rawTx)
260
- // If we have an error and didn't before...
261
- r0.error = r.error
262
319
 
263
- this.getRawTxServices.next()
320
+ if (r.error)
321
+ services.addServiceCallError(stc, r.error)
322
+ else
323
+ services.addServiceCallFailure(stc)
324
+
325
+ if (r.error && !r0.error && !r0.rawTx)
326
+ // If we have an error and didn't before...
327
+ r0.error = r.error
328
+
329
+ } catch (eu: unknown) {
330
+ const e = sdk.WalletError.fromUnknown(eu)
331
+ services.addServiceCallError(stc, e)
332
+ }
333
+ services.next()
264
334
  }
265
335
  return r0
266
336
  }
@@ -307,28 +377,41 @@ export class Services implements sdk.WalletServices {
307
377
  }
308
378
 
309
379
  async getMerklePath(txid: string, useNext?: boolean): Promise<sdk.GetMerklePathResult> {
310
- if (useNext) this.getMerklePathServices.next()
380
+ const services = this.getMerklePathServices
381
+ if (useNext) services.next()
311
382
 
312
383
  const r0: sdk.GetMerklePathResult = { notes: [] }
313
384
 
314
- for (let tries = 0; tries < this.getMerklePathServices.count; tries++) {
315
- const service = this.getMerklePathServices.service
316
- const r = await service(txid, this)
317
- if (r.notes) r0.notes!.push(...r.notes)
318
- if (!r0.name) r0.name = r.name
319
- if (r.merklePath) {
320
- // If we have a proof, call it done.
321
- r0.merklePath = r.merklePath
322
- r0.header = r.header
323
- r0.name = r.name
324
- r0.error = undefined
325
- break
326
- } else if (r.error && !r0.error) {
327
- // If we have an error and didn't before...
328
- r0.error = r.error
385
+ for (let tries = 0; tries < services.count; tries++) {
386
+ const stc = services.serviceToCall
387
+ try {
388
+ const r = await stc.service(txid, this)
389
+ if (r.notes) r0.notes!.push(...r.notes)
390
+ if (!r0.name) r0.name = r.name
391
+ if (r.merklePath) {
392
+ // If we have a proof, call it done.
393
+ r0.merklePath = r.merklePath
394
+ r0.header = r.header
395
+ r0.name = r.name
396
+ r0.error = undefined
397
+ services.addServiceCallSuccess(stc)
398
+ break
399
+ }
400
+
401
+ if (r.error)
402
+ services.addServiceCallError(stc, r.error)
403
+ else
404
+ services.addServiceCallFailure(stc)
405
+
406
+ if (r.error && !r0.error) {
407
+ // If we have an error and didn't before...
408
+ r0.error = r.error
409
+ }
410
+ } catch (eu: unknown) {
411
+ const e = sdk.WalletError.fromUnknown(eu)
412
+ services.addServiceCallError(stc, e)
329
413
  }
330
-
331
- this.getMerklePathServices.next()
414
+ services.next()
332
415
  }
333
416
  return r0
334
417
  }
@@ -350,16 +433,19 @@ export class Services implements sdk.WalletServices {
350
433
  let r0: sdk.FiatExchangeRates | undefined
351
434
 
352
435
  for (let tries = 0; tries < services.count; tries++) {
353
- const service = services.service
436
+ const stc = services.serviceToCall
354
437
  try {
355
- const r = await service(this.targetCurrencies, this.options)
438
+ const r = await stc.service(this.targetCurrencies, this.options)
356
439
  if (this.targetCurrencies.every(c => typeof r.rates[c] === 'number')) {
440
+ services.addServiceCallSuccess(stc)
357
441
  r0 = r
358
442
  break
443
+ } else {
444
+ services.addServiceCallFailure(stc)
359
445
  }
360
446
  } catch (eu: unknown) {
361
447
  const e = sdk.WalletError.fromUnknown(eu)
362
- console.error(`updateFiatExchangeRates servcice name ${service.name} error ${e.message}`)
448
+ services.addServiceCallError(stc, e)
363
449
  }
364
450
  services.next()
365
451
  }
@@ -0,0 +1,108 @@
1
+ import { _tu, logger } from '../../../test/utils/TestUtilsWalletStorage'
2
+ import { sdk, wait } from '../../index.client'
3
+ import { ARC } from '../providers/ARC'
4
+ import { Beef, BeefTx } from '@bsv/sdk'
5
+ import { arcDefaultUrl, arcGorillaPoolUrl } from '../createDefaultWalletServicesOptions'
6
+ import { Setup } from '../../index.all'
7
+
8
+ describe('ArcGorillaPool tests', () => {
9
+ jest.setTimeout(99999999)
10
+
11
+ const env = _tu.getEnv('main')
12
+ const arc = new ARC(arcGorillaPoolUrl(env.chain)!, {
13
+ apiKey: ''
14
+ })
15
+
16
+ test.skip('0 double spend', async () => {
17
+ const beef = Beef.fromString(testnetDoubleSpendBeef)
18
+ const txids = [beef.txs.slice(-1)[0].txid]
19
+ const r = await arc.postBeef(beef, txids)
20
+ expect(r.status === 'error').toBe(true)
21
+ expect(r.txidResults[0].doubleSpend).toBe(true)
22
+ })
23
+
24
+ test.skip('1 postRawTx', async () => {
25
+ const r = await postRawTxTest('main', arc)
26
+ logger(`2 postBeef mainnet done ${r}`)
27
+ })
28
+
29
+ test('2 postBeef', async () => {
30
+ const r = await postBeefTest('main', arc)
31
+ logger(`2 postBeef mainnet done ${r}`)
32
+ })
33
+ })
34
+
35
+ async function postBeefTest(chain: sdk.Chain, arc: ARC): Promise<string> {
36
+ if (Setup.noEnv(chain)) return 'skipped'
37
+ const c = await _tu.createNoSendTxPair(chain)
38
+
39
+ const txids = [c.txidDo, c.txidUndo]
40
+
41
+ const r = await arc.postBeef(c.beef, txids)
42
+ expect(r.status).toBe('success')
43
+ for (const txid of txids) {
44
+ const tr = r.txidResults.find(tx => tx.txid === txid)
45
+ expect(tr).not.toBeUndefined()
46
+ expect(tr!.status).toBe('success')
47
+ }
48
+
49
+ // replace Undo transaction with double spend transaction and send again.
50
+ const beef2 = c.beef.clone()
51
+ beef2.txs[beef2.txs.length - 1] = BeefTx.fromTx(c.doubleSpendTx)
52
+ const txids2 = [c.txidDo, c.doubleSpendTx.id('hex')]
53
+
54
+ const r2 = await arc.postBeef(beef2, txids2)
55
+ expect(r2.status).toBe('error')
56
+ for (const txid of txids2) {
57
+ const tr = r2.txidResults.find(tx => tx.txid === txid)
58
+ expect(tr).not.toBeUndefined()
59
+ if (txid === c.txidDo) {
60
+ expect(tr!.status).toBe('success')
61
+ } else {
62
+ expect(tr!.status).toBe('error')
63
+ expect(tr!.doubleSpend).toBe(true)
64
+ expect(tr!.competingTxs).toEqual([c.txidUndo])
65
+ }
66
+ }
67
+ return 'passed'
68
+ }
69
+
70
+ async function postRawTxTest(chain: sdk.Chain, arc: ARC): Promise<void> {
71
+ if (Setup.noEnv(chain)) return
72
+ const c = await _tu.createNoSendTxPair(chain)
73
+
74
+ const rawTxDo = c.beef.findTxid(c.txidDo)!.tx!.toHex()
75
+ const rawTxUndo = c.beef.findTxid(c.txidUndo)!.tx!.toHex()
76
+
77
+ const rDo = await arc.postRawTx(rawTxDo)
78
+ expect(rDo.status).toBe('success')
79
+ expect(rDo.txid).toBe(c.txidDo)
80
+
81
+ await wait(1000)
82
+
83
+ const rUndo = await arc.postRawTx(rawTxUndo)
84
+ expect(rUndo.status).toBe('success')
85
+ expect(rUndo.txid).toBe(c.txidUndo)
86
+ expect(rUndo.doubleSpend).not.toBe(true)
87
+
88
+ await wait(1000)
89
+
90
+ {
91
+ // Send same transaction again...
92
+ const rUndo = await arc.postRawTx(rawTxUndo)
93
+ expect(rUndo.status).toBe('success')
94
+ expect(rUndo.txid).toBe(c.txidUndo)
95
+ expect(rUndo.doubleSpend).not.toBe(true)
96
+ }
97
+
98
+ await wait(1000)
99
+
100
+ // Confirm double spend detection.
101
+ const rDouble = await arc.postRawTx(c.doubleSpendTx.toHex())
102
+ expect(rDouble.status).toBe('error')
103
+ expect(rDouble.doubleSpend).toBe(true)
104
+ expect(rDouble.competingTxs![0]).toBe(c.txidUndo)
105
+ }
106
+
107
+ const testnetDoubleSpendBeef =
108
+ '0100beef01fe65631900020200009df812619ae232d2363d91516ab3e811211192933526bbc2aee71b54ccb236d10102462876eec65d9aa26d957421c5cc8dd9119b61177242b9dd814fb190fd0a361801010076a3297928f6841bcb656e91225540e87c65f67d8ec12bc768d7656eb7561b3d02010000000159617a9d17562f7c9765e5dfa6a9a393aa2809ca6166a3d7a31c09efcc5070141f0000006a47304402200a528145a67ba1879b88a093cb711f79f04413a81d5678f314302e36a7f59e43022010bc4bb3c2574052c50bbdc8a05c31fb39e69280656b34f5dc22e2ceadc3bb4a412102fd4200bf389d16479b3d06f97fee0752f2c3b9dc29fb3ddce2b327d851b8902bffffffff0204000000000000001976a9140df1a69c834bb7d9bb5b2b7d6a34e5a401db3e1688ac01000000000000001976a91423f2562a8092ed24eddc77c74387b44c561692a188ac0000000001000100000001462876eec65d9aa26d957421c5cc8dd9119b61177242b9dd814fb190fd0a3618000000006a47304402204183bbfdcf11d50907b91f5e70ea8f81228501ce84e24af75c8d984682d094dc022029caa8f7e5acb4990bbeafee523a3c4a99b78e98b9e5c41349147b099679d4ae412103b76389eea6494c2c30443cba9d59b9dba05fb04e467bc94272629615b87a429fffffffff0202000000000000001976a91476d851e59fcb4ee0ebe6947496db3a393b08e49c88ac01000000000000001976a91423f2562a8092ed24eddc77c74387b44c561692a188ac0000000000'