@mojaloop/central-ledger 17.7.8 → 17.8.0-orb.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 (147) hide show
  1. package/.ncurc.yaml +3 -1
  2. package/.nvmrc +1 -1
  3. package/.nycrc.yml +16 -1
  4. package/Dockerfile +9 -10
  5. package/README.md +39 -9
  6. package/audit-ci.jsonc +15 -2
  7. package/config/default.json +17 -1
  8. package/docker/central-ledger/default.json +9 -0
  9. package/docker/config-modifier/configs/central-ledger.js +25 -0
  10. package/docker/env.sh +15 -0
  11. package/docker/kafka/scripts/provision.sh +4 -1
  12. package/docker/ml-api-adapter/default.json +4 -1
  13. package/docker-compose.yml +90 -2
  14. package/documentation/db/erd-transfer-timeout.png +0 -0
  15. package/documentation/db/erd-transfer-timeout.txt +81 -0
  16. package/documentation/fx-implementation/README.md +48 -0
  17. package/documentation/fx-implementation/assets/fx-position-movements.drawio.svg +4 -0
  18. package/documentation/fx-implementation/assets/test-scenario.drawio.svg +4 -0
  19. package/documentation/sequence-diagrams/Handler - FX timeout.plantuml +123 -0
  20. package/documentation/sequence-diagrams/Handler - FX timeout.png +0 -0
  21. package/documentation/sequence-diagrams/Handler - timeout.plantuml +81 -0
  22. package/documentation/sequence-diagrams/Handler - timeout.png +0 -0
  23. package/documentation/state-diagrams/transfer-ML-spec-states-diagram.png +0 -0
  24. package/documentation/state-diagrams/transfer-internal-states-diagram.png +0 -0
  25. package/documentation/state-diagrams/transfer-internal-states.plantuml +75 -0
  26. package/documentation/state-diagrams/transfer-states.plantuml +13 -0
  27. package/migrations/310204_transferParticipant-participantId.js +52 -0
  28. package/migrations/310403_participantPositionChange-participantCurrencyId.js +47 -0
  29. package/migrations/310404_participantPositionChange-change.js +46 -0
  30. package/migrations/600010_fxTransferType.js +43 -0
  31. package/migrations/600011_fxTransferType-indexes.js +38 -0
  32. package/migrations/600012_fxParticipantCurrencyType.js +43 -0
  33. package/migrations/600013_fxParticipantCurrencyType-indexes.js +38 -0
  34. package/migrations/600100_fxTransferDuplicateCheck.js +42 -0
  35. package/migrations/600110_fxTransferErrorDuplicateCheck.js.js +17 -0
  36. package/migrations/600200_fxTransfer.js +51 -0
  37. package/migrations/600201_fxTransfer-indexes.js +40 -0
  38. package/migrations/600400_fxTransferStateChange.js +46 -0
  39. package/migrations/600401_fxTransferStateChange-indexes.js +40 -0
  40. package/migrations/600501_fxWatchList.js +46 -0
  41. package/migrations/600502_fxWatchList-indexes.js +40 -0
  42. package/migrations/600600_fxTransferFulfilmentDuplicateCheck.js +43 -0
  43. package/migrations/600601_fxTransferFulfilmentDuplicateCheck-indexes.js +38 -0
  44. package/migrations/600700_fxTransferFulfilment.js +47 -0
  45. package/migrations/600701_fxTransferFulfilment-indexes.js +43 -0
  46. package/migrations/600800_fxTransferExtension.js +47 -0
  47. package/migrations/601400_fxTransferTimeout.js +43 -0
  48. package/migrations/601401_fxTransferTimeout-indexes.js +37 -0
  49. package/migrations/601500_fxTransferError.js +44 -0
  50. package/migrations/601501_fxTransferError-indexes.js +37 -0
  51. package/migrations/610200_fxTransferParticipant.js +52 -0
  52. package/migrations/610201_fxTransferParticipant-indexes.js +44 -0
  53. package/migrations/610202_fxTransferParticipant-participantId.js +52 -0
  54. package/migrations/610403_participantPositionChange-fxTransfer.js +46 -0
  55. package/migrations/910101_feature904DataMigration.js +46 -52
  56. package/migrations/910102_feature949DataMigration.js +219 -225
  57. package/migrations/950104_settlementModel-settlementAccountTypeId.js +15 -20
  58. package/migrations/950108_participantProxy.js +18 -0
  59. package/migrations/950109_fxQuote.js +19 -0
  60. package/migrations/950110_fxQuoteResponse.js +25 -0
  61. package/migrations/950111_fxQuoteError.js +23 -0
  62. package/migrations/950113_fxQuoteDuplicateCheck.js +18 -0
  63. package/migrations/950114_fxQuoteResponseDuplicateCheck.js +21 -0
  64. package/migrations/950115_fxQuoteConversionTerms.js +36 -0
  65. package/migrations/950116_fxQuoteConversionTermsExtension.js +21 -0
  66. package/migrations/950117_fxQuoteResponseConversionTerms.js +39 -0
  67. package/migrations/950118_fxQuoteResponseConversionTermsExtension.js +21 -0
  68. package/migrations/950119_fxCharge.js +27 -0
  69. package/migrations/960100_create_externalParticipant.js +47 -0
  70. package/migrations/960110_alter_transferParticipant__addFiled_externalParticipantId.js +50 -0
  71. package/migrations/960111_alter_fxTransferParticipant__addFiled_externalParticipantId.js +50 -0
  72. package/package.json +27 -20
  73. package/seeds/endpointType.js +18 -0
  74. package/seeds/fxParticipantCurrencyType.js +45 -0
  75. package/seeds/fxTransferType.js +45 -0
  76. package/seeds/participant.js +2 -1
  77. package/seeds/transferParticipantRoleType.js +9 -0
  78. package/seeds/transferState.js +10 -0
  79. package/src/api/interface/swagger.json +23 -30
  80. package/src/api/participants/handler.js +6 -2
  81. package/src/api/participants/routes.js +8 -7
  82. package/src/api/root/handler.js +15 -5
  83. package/src/domain/fx/cyril.js +466 -0
  84. package/src/domain/fx/index.js +107 -0
  85. package/src/domain/participant/index.js +108 -1
  86. package/src/domain/position/abort.js +215 -0
  87. package/src/domain/position/binProcessor.js +361 -99
  88. package/src/domain/position/fulfil.js +252 -111
  89. package/src/domain/position/fx-fulfil.js +138 -0
  90. package/src/domain/position/fx-prepare.js +280 -0
  91. package/src/domain/position/fx-timeout-reserved.js +159 -0
  92. package/src/domain/position/index.js +1 -0
  93. package/src/domain/position/prepare.js +69 -49
  94. package/src/domain/position/timeout-reserved.js +162 -0
  95. package/src/domain/timeout/index.js +26 -2
  96. package/src/domain/transfer/index.js +22 -5
  97. package/src/domain/transfer/transform.js +19 -6
  98. package/src/handlers/admin/handler.js +0 -2
  99. package/src/handlers/bulk/fulfil/handler.js +5 -5
  100. package/src/handlers/bulk/get/handler.js +5 -5
  101. package/src/handlers/bulk/prepare/handler.js +9 -9
  102. package/src/handlers/bulk/processing/handler.js +8 -7
  103. package/src/handlers/bulk/shared/validator.js +1 -1
  104. package/src/handlers/positions/handler.js +23 -10
  105. package/src/handlers/positions/handlerBatch.js +54 -26
  106. package/src/handlers/register.js +2 -1
  107. package/src/handlers/timeouts/handler.js +212 -68
  108. package/src/handlers/transfers/FxFulfilService.js +387 -0
  109. package/src/handlers/transfers/createRemittanceEntity.js +106 -0
  110. package/src/handlers/transfers/dto.js +53 -0
  111. package/src/handlers/transfers/handler.js +609 -571
  112. package/src/handlers/transfers/prepare.js +572 -0
  113. package/src/handlers/transfers/validator.js +75 -14
  114. package/src/lib/cache.js +1 -1
  115. package/src/lib/config.js +6 -3
  116. package/src/lib/healthCheck/subServiceHealth.js +12 -2
  117. package/src/lib/proxyCache.js +131 -0
  118. package/src/models/bulkTransfer/facade.js +58 -82
  119. package/src/models/fxTransfer/duplicateCheck.js +153 -0
  120. package/src/models/fxTransfer/fxTransfer.js +578 -0
  121. package/src/models/fxTransfer/fxTransferError.js +53 -0
  122. package/src/models/fxTransfer/fxTransferExtension.js +41 -0
  123. package/src/models/fxTransfer/fxTransferTimeout.js +68 -0
  124. package/src/models/fxTransfer/index.js +15 -0
  125. package/src/models/fxTransfer/stateChange.js +47 -0
  126. package/src/models/fxTransfer/watchList.js +49 -0
  127. package/src/models/ledgerAccountType/ledgerAccountType.js +8 -32
  128. package/src/models/misc/segment.js +0 -1
  129. package/src/models/participant/externalParticipant.js +96 -0
  130. package/src/models/participant/externalParticipantCached.js +148 -0
  131. package/src/models/participant/facade.js +192 -123
  132. package/src/models/participant/participant.js +2 -1
  133. package/src/models/participant/participantCurrency.js +1 -1
  134. package/src/models/participant/participantPosition.js +2 -8
  135. package/src/models/position/batch.js +90 -1
  136. package/src/models/position/facade.js +5 -5
  137. package/src/models/position/participantPositionChanges.js +68 -0
  138. package/src/models/settlement/settlementModel.js +5 -23
  139. package/src/models/transfer/facade.js +806 -447
  140. package/src/shared/constants.js +52 -0
  141. package/src/shared/fspiopErrorFactory.js +131 -0
  142. package/src/shared/logger/index.js +8 -0
  143. package/src/shared/loggingPlugin.js +43 -0
  144. package/src/shared/plugins.js +6 -0
  145. package/src/shared/setup.js +10 -0
  146. package/test-integration.Dockerfile +1 -1
  147. package/test.Dockerfile +1 -1
@@ -28,17 +28,20 @@
28
28
  * @module src/models/participant/facade/
29
29
  */
30
30
 
31
- const Db = require('../../lib/db')
32
31
  const Time = require('@mojaloop/central-services-shared').Util.Time
32
+ const { Enum } = require('@mojaloop/central-services-shared')
33
33
  const ErrorHandler = require('@mojaloop/central-services-error-handling')
34
34
  const Metrics = require('@mojaloop/central-services-metrics')
35
+
36
+ const Db = require('../../lib/db')
35
37
  const Cache = require('../../lib/cache')
36
38
  const ParticipantModelCached = require('../../models/participant/participantCached')
37
39
  const ParticipantCurrencyModelCached = require('../../models/participant/participantCurrencyCached')
38
40
  const ParticipantLimitCached = require('../../models/participant/participantLimitCached')
41
+ const externalParticipantModelCached = require('../../models/participant/externalParticipantCached')
39
42
  const Config = require('../../lib/config')
40
43
  const SettlementModelModel = require('../settlement/settlementModel')
41
- const { Enum } = require('@mojaloop/central-services-shared')
44
+ const { logger } = require('../../shared/logger')
42
45
 
43
46
  const getByNameAndCurrency = async (name, currencyId, ledgerAccountTypeId, isCurrencyActive) => {
44
47
  const histTimerParticipantGetByNameAndCurrencyEnd = Metrics.getHistogram(
@@ -106,6 +109,72 @@ const getByNameAndCurrency = async (name, currencyId, ledgerAccountTypeId, isCur
106
109
  }
107
110
  }
108
111
 
112
+ const getByIDAndCurrency = async (participantId, currencyId, ledgerAccountTypeId, isCurrencyActive) => {
113
+ const histTimerParticipantGetByIDAndCurrencyEnd = Metrics.getHistogram(
114
+ 'model_participant',
115
+ 'facade_getByIDAndCurrency - Metrics for participant model',
116
+ ['success', 'queryName']
117
+ ).startTimer()
118
+
119
+ try {
120
+ let participant
121
+ if (Cache.isCacheEnabled()) {
122
+ /* Cached version - fetch data from Models (which we trust are cached) */
123
+ /* find paricipant by ID */
124
+ participant = await ParticipantModelCached.getById(participantId)
125
+ if (participant) {
126
+ /* use the paricipant id and incoming params to prepare the filter */
127
+ const searchFilter = {
128
+ participantId,
129
+ currencyId,
130
+ ledgerAccountTypeId
131
+ }
132
+ if (isCurrencyActive !== undefined) {
133
+ searchFilter.isActive = isCurrencyActive
134
+ }
135
+
136
+ /* find the participantCurrency by prepared filter */
137
+ const participantCurrency = await ParticipantCurrencyModelCached.findOneByParams(searchFilter)
138
+
139
+ if (participantCurrency) {
140
+ /* mix requested data from participantCurrency */
141
+ participant.participantCurrencyId = participantCurrency.participantCurrencyId
142
+ participant.currencyId = participantCurrency.currencyId
143
+ participant.currencyIsActive = participantCurrency.isActive
144
+ }
145
+ }
146
+ } else {
147
+ /* Non-cached version - direct call to DB */
148
+ participant = await Db.from('participant').query(async (builder) => {
149
+ let b = builder
150
+ .where({ 'participant.participantId': participantId })
151
+ .andWhere({ 'pc.currencyId': currencyId })
152
+ .andWhere({ 'pc.ledgerAccountTypeId': ledgerAccountTypeId })
153
+ .innerJoin('participantCurrency AS pc', 'pc.participantId', 'participant.participantId')
154
+ .select(
155
+ 'participant.*',
156
+ 'pc.participantCurrencyId',
157
+ 'pc.currencyId',
158
+ 'pc.isActive AS currencyIsActive'
159
+ )
160
+ .first()
161
+
162
+ if (isCurrencyActive !== undefined) {
163
+ b = b.andWhere({ 'pc.isActive': isCurrencyActive })
164
+ }
165
+ return b
166
+ })
167
+ }
168
+
169
+ histTimerParticipantGetByIDAndCurrencyEnd({ success: true, queryName: 'facade_getByIDAndCurrency' })
170
+
171
+ return participant
172
+ } catch (err) {
173
+ histTimerParticipantGetByIDAndCurrencyEnd({ success: false, queryName: 'facade_getByIDAndCurrency' })
174
+ throw ErrorHandler.Factory.reformatFSPIOPError(err)
175
+ }
176
+ }
177
+
109
178
  const getParticipantLimitByParticipantIdAndCurrencyId = async (participantId, currencyId, ledgerAccountTypeId) => {
110
179
  try {
111
180
  return await Db.from('participant').query(async (builder) => {
@@ -259,34 +328,30 @@ const addEndpoint = async (participantId, endpoint) => {
259
328
  try {
260
329
  const knex = Db.getKnex()
261
330
  return knex.transaction(async trx => {
262
- try {
263
- const endpointType = await knex('endpointType').where({ name: endpoint.type, isActive: 1 }).select('endpointTypeId').first()
264
- // let endpointType = await trx.first('endpointTypeId').from('endpointType').where({ 'name': endpoint.type, 'isActive': 1 })
331
+ const endpointType = await knex('endpointType').where({
332
+ name: endpoint.type,
333
+ isActive: 1
334
+ }).select('endpointTypeId').first()
265
335
 
266
- const existingEndpoint = await knex('participantEndpoint').transacting(trx).forUpdate().select('*')
267
- .where({
268
- participantId,
269
- endpointTypeId: endpointType.endpointTypeId,
270
- isActive: 1
271
- })
272
- if (Array.isArray(existingEndpoint) && existingEndpoint.length > 0) {
273
- await knex('participantEndpoint').transacting(trx).update({ isActive: 0 }).where('participantEndpointId', existingEndpoint[0].participantEndpointId)
274
- }
275
- const newEndpoint = {
336
+ const existingEndpoint = await knex('participantEndpoint').transacting(trx).forUpdate().select('*')
337
+ .where({
276
338
  participantId,
277
339
  endpointTypeId: endpointType.endpointTypeId,
278
- value: endpoint.value,
279
- isActive: 1,
280
- createdBy: 'unknown'
281
- }
282
- const result = await knex('participantEndpoint').transacting(trx).insert(newEndpoint)
283
- newEndpoint.participantEndpointId = result[0]
284
- await trx.commit
285
- return newEndpoint
286
- } catch (err) {
287
- await trx.rollback
288
- throw err
340
+ isActive: 1
341
+ })
342
+ if (Array.isArray(existingEndpoint) && existingEndpoint.length > 0) {
343
+ await knex('participantEndpoint').transacting(trx).update({ isActive: 0 }).where('participantEndpointId', existingEndpoint[0].participantEndpointId)
289
344
  }
345
+ const newEndpoint = {
346
+ participantId,
347
+ endpointTypeId: endpointType.endpointTypeId,
348
+ value: endpoint.value,
349
+ isActive: 1,
350
+ createdBy: 'unknown'
351
+ }
352
+ const result = await knex('participantEndpoint').transacting(trx).insert(newEndpoint)
353
+ newEndpoint.participantEndpointId = result[0]
354
+ return newEndpoint
290
355
  })
291
356
  } catch (err) {
292
357
  throw ErrorHandler.Factory.reformatFSPIOPError(err)
@@ -413,73 +478,67 @@ const addLimitAndInitialPosition = async (participantCurrencyId, settlementAccou
413
478
  try {
414
479
  const knex = Db.getKnex()
415
480
  return knex.transaction(async trx => {
416
- try {
417
- const limitType = await knex('participantLimitType').where({ name: limitPositionObj.limit.type, isActive: 1 }).select('participantLimitTypeId').first()
418
- const participantLimit = {
419
- participantCurrencyId,
420
- participantLimitTypeId: limitType.participantLimitTypeId,
421
- value: limitPositionObj.limit.value,
422
- isActive: 1,
423
- createdBy: 'unknown'
424
- }
425
- const result = await knex('participantLimit').transacting(trx).insert(participantLimit)
426
- participantLimit.participantLimitId = result[0]
427
-
428
- const allSettlementModels = await SettlementModelModel.getAll()
429
- const settlementModels = allSettlementModels.filter(model => model.currencyId === limitPositionObj.currency)
430
- if (Array.isArray(settlementModels) && settlementModels.length > 0) {
431
- for (const settlementModel of settlementModels) {
432
- const positionAccount = await getByNameAndCurrency(limitPositionObj.name, limitPositionObj.currency, settlementModel.ledgerAccountTypeId)
433
- const settlementAccount = await getByNameAndCurrency(limitPositionObj.name, limitPositionObj.currency, settlementModel.settlementAccountTypeId)
434
-
435
- const participantPosition = {
436
- participantCurrencyId: positionAccount.participantCurrencyId,
437
- value: (settlementModel.ledgerAccountTypeId === Enum.Accounts.LedgerAccountType.POSITION ? limitPositionObj.initialPosition : 0),
438
- reservedValue: 0
439
- }
440
- await knex('participantPosition').transacting(trx).insert(participantPosition)
481
+ const limitType = await knex('participantLimitType').where({ name: limitPositionObj.limit.type, isActive: 1 }).select('participantLimitTypeId').first()
482
+ const participantLimit = {
483
+ participantCurrencyId,
484
+ participantLimitTypeId: limitType.participantLimitTypeId,
485
+ value: limitPositionObj.limit.value,
486
+ isActive: 1,
487
+ createdBy: 'unknown'
488
+ }
489
+ const result = await knex('participantLimit').transacting(trx).insert(participantLimit)
490
+ participantLimit.participantLimitId = result[0]
491
+
492
+ const allSettlementModels = await SettlementModelModel.getAll()
493
+ const settlementModels = allSettlementModels.filter(model => model.currencyId === limitPositionObj.currency)
494
+ if (Array.isArray(settlementModels) && settlementModels.length > 0) {
495
+ for (const settlementModel of settlementModels) {
496
+ const positionAccount = await getByNameAndCurrency(limitPositionObj.name, limitPositionObj.currency, settlementModel.ledgerAccountTypeId)
497
+ const settlementAccount = await getByNameAndCurrency(limitPositionObj.name, limitPositionObj.currency, settlementModel.settlementAccountTypeId)
441
498
 
442
- const settlementPosition = {
443
- participantCurrencyId: settlementAccount.participantCurrencyId,
444
- value: 0,
445
- reservedValue: 0
446
- }
447
- await knex('participantPosition').transacting(trx).insert(settlementPosition)
448
- if (setCurrencyActive) { // if the flag is true then set the isActive flag for corresponding participantCurrency record to true
449
- await knex('participantCurrency').transacting(trx).update({ isActive: 1 }).where('participantCurrencyId', positionAccount.participantCurrencyId)
450
- await knex('participantCurrency').transacting(trx).update({ isActive: 1 }).where('participantCurrencyId', settlementAccount.participantCurrencyId)
451
- await ParticipantCurrencyModelCached.invalidateParticipantCurrencyCache()
452
- await ParticipantLimitCached.invalidateParticipantLimitCache()
453
- }
454
- }
455
- } else {
456
499
  const participantPosition = {
457
- participantCurrencyId,
458
- value: limitPositionObj.initialPosition,
500
+ participantCurrencyId: positionAccount.participantCurrencyId,
501
+ value: (settlementModel.ledgerAccountTypeId === Enum.Accounts.LedgerAccountType.POSITION ? limitPositionObj.initialPosition : 0),
459
502
  reservedValue: 0
460
503
  }
461
- const participantPositionResult = await knex('participantPosition').transacting(trx).insert(participantPosition)
462
- participantPosition.participantPositionId = participantPositionResult[0]
504
+ await knex('participantPosition').transacting(trx).insert(participantPosition)
505
+
463
506
  const settlementPosition = {
464
- participantCurrencyId: settlementAccountId,
507
+ participantCurrencyId: settlementAccount.participantCurrencyId,
465
508
  value: 0,
466
509
  reservedValue: 0
467
510
  }
468
511
  await knex('participantPosition').transacting(trx).insert(settlementPosition)
469
512
  if (setCurrencyActive) { // if the flag is true then set the isActive flag for corresponding participantCurrency record to true
470
- await knex('participantCurrency').transacting(trx).update({ isActive: 1 }).where('participantCurrencyId', participantCurrencyId)
471
- await knex('participantCurrency').transacting(trx).update({ isActive: 1 }).where('participantCurrencyId', settlementAccountId)
513
+ await knex('participantCurrency').transacting(trx).update({ isActive: 1 }).where('participantCurrencyId', positionAccount.participantCurrencyId)
514
+ await knex('participantCurrency').transacting(trx).update({ isActive: 1 }).where('participantCurrencyId', settlementAccount.participantCurrencyId)
472
515
  await ParticipantCurrencyModelCached.invalidateParticipantCurrencyCache()
473
516
  await ParticipantLimitCached.invalidateParticipantLimitCache()
474
517
  }
475
518
  }
476
-
477
- await trx.commit
478
- return true
479
- } catch (err) {
480
- await trx.rollback
481
- throw err
519
+ } else {
520
+ const participantPosition = {
521
+ participantCurrencyId,
522
+ value: limitPositionObj.initialPosition,
523
+ reservedValue: 0
524
+ }
525
+ const participantPositionResult = await knex('participantPosition').transacting(trx).insert(participantPosition)
526
+ participantPosition.participantPositionId = participantPositionResult[0]
527
+ const settlementPosition = {
528
+ participantCurrencyId: settlementAccountId,
529
+ value: 0,
530
+ reservedValue: 0
531
+ }
532
+ await knex('participantPosition').transacting(trx).insert(settlementPosition)
533
+ if (setCurrencyActive) { // if the flag is true then set the isActive flag for corresponding participantCurrency record to true
534
+ await knex('participantCurrency').transacting(trx).update({ isActive: 1 }).where('participantCurrencyId', participantCurrencyId)
535
+ await knex('participantCurrency').transacting(trx).update({ isActive: 1 }).where('participantCurrencyId', settlementAccountId)
536
+ await ParticipantCurrencyModelCached.invalidateParticipantCurrencyCache()
537
+ await ParticipantLimitCached.invalidateParticipantLimitCache()
538
+ }
482
539
  }
540
+
541
+ return true
483
542
  })
484
543
  } catch (err) {
485
544
  throw ErrorHandler.Factory.reformatFSPIOPError(err)
@@ -510,7 +569,7 @@ const addLimitAndInitialPosition = async (participantCurrencyId, settlementAccou
510
569
 
511
570
  const adjustLimits = async (participantCurrencyId, limit, trx) => {
512
571
  try {
513
- const trxFunction = async (trx, doCommit = true) => {
572
+ const trxFunction = async (trx) => {
514
573
  try {
515
574
  const limitType = await knex('participantLimitType').where({ name: limit.type, isActive: 1 }).select('participantLimitTypeId').first()
516
575
  // const limitType = await trx.first('participantLimitTypeId').from('participantLimitType').where({ 'name': limit.type, 'isActive': 1 })
@@ -535,23 +594,17 @@ const adjustLimits = async (participantCurrencyId, limit, trx) => {
535
594
  }
536
595
  const result = await knex('participantLimit').transacting(trx).insert(newLimit)
537
596
  newLimit.participantLimitId = result[0]
538
- if (doCommit) {
539
- await trx.commit
540
- }
541
597
  return {
542
598
  participantLimit: newLimit
543
599
  }
544
600
  } catch (err) {
545
- if (doCommit) {
546
- await trx.rollback
547
- }
548
601
  throw ErrorHandler.Factory.reformatFSPIOPError(err)
549
602
  }
550
603
  }
551
604
 
552
605
  const knex = Db.getKnex()
553
606
  if (trx) {
554
- return trxFunction(trx, false)
607
+ return trxFunction(trx)
555
608
  } else {
556
609
  return knex.transaction(trxFunction)
557
610
  }
@@ -640,34 +693,28 @@ const addHubAccountAndInitPosition = async (participantId, currencyId, ledgerAcc
640
693
  try {
641
694
  const knex = Db.getKnex()
642
695
  return knex.transaction(async trx => {
643
- try {
644
- let result
645
- const participantCurrency = {
646
- participantId,
647
- currencyId,
648
- ledgerAccountTypeId,
649
- createdBy: 'unknown',
650
- isActive: 1,
651
- createdDate: Time.getUTCString(new Date())
652
- }
653
- result = await knex('participantCurrency').transacting(trx).insert(participantCurrency)
654
- await ParticipantCurrencyModelCached.invalidateParticipantCurrencyCache()
655
- participantCurrency.participantCurrencyId = result[0]
656
- const participantPosition = {
657
- participantCurrencyId: participantCurrency.participantCurrencyId,
658
- value: 0,
659
- reservedValue: 0
660
- }
661
- result = await knex('participantPosition').transacting(trx).insert(participantPosition)
662
- participantPosition.participantPositionId = result[0]
663
- await trx.commit
664
- return {
665
- participantCurrency,
666
- participantPosition
667
- }
668
- } catch (err) {
669
- await trx.rollback
670
- throw err
696
+ let result
697
+ const participantCurrency = {
698
+ participantId,
699
+ currencyId,
700
+ ledgerAccountTypeId,
701
+ createdBy: 'unknown',
702
+ isActive: 1,
703
+ createdDate: Time.getUTCString(new Date())
704
+ }
705
+ result = await knex('participantCurrency').transacting(trx).insert(participantCurrency)
706
+ await ParticipantCurrencyModelCached.invalidateParticipantCurrencyCache()
707
+ participantCurrency.participantCurrencyId = result[0]
708
+ const participantPosition = {
709
+ participantCurrencyId: participantCurrency.participantCurrencyId,
710
+ value: 0,
711
+ reservedValue: 0
712
+ }
713
+ result = await knex('participantPosition').transacting(trx).insert(participantPosition)
714
+ participantPosition.participantPositionId = result[0]
715
+ return {
716
+ participantCurrency,
717
+ participantPosition
671
718
  }
672
719
  })
673
720
  } catch (err) {
@@ -706,7 +753,7 @@ const getAllNonHubParticipantsWithCurrencies = async (trx) => {
706
753
  try {
707
754
  const HUB_ACCOUNT_NAME = Config.HUB_NAME
708
755
  const knex = Db.getKnex()
709
- const trxFunction = async (trx, doCommit = true) => {
756
+ const trxFunction = async (trx) => {
710
757
  try {
711
758
  const res = await knex.distinct('participant.participantId', 'pc.participantId', 'pc.currencyId')
712
759
  .from('participant')
@@ -714,19 +761,13 @@ const getAllNonHubParticipantsWithCurrencies = async (trx) => {
714
761
  .whereNot('participant.name', HUB_ACCOUNT_NAME)
715
762
  .transacting(trx)
716
763
 
717
- if (doCommit) {
718
- await trx.commit
719
- }
720
764
  return res
721
765
  } catch (err) {
722
- if (doCommit) {
723
- await trx.rollback
724
- }
725
766
  throw ErrorHandler.Factory.reformatFSPIOPError(err)
726
767
  }
727
768
  }
728
769
  if (trx) {
729
- return trxFunction(trx, false)
770
+ return trxFunction(trx)
730
771
  } else {
731
772
  return knex.transaction(trxFunction)
732
773
  }
@@ -735,9 +776,36 @@ const getAllNonHubParticipantsWithCurrencies = async (trx) => {
735
776
  }
736
777
  }
737
778
 
779
+ const getExternalParticipantIdByNameOrCreate = async ({ name, proxyId }) => {
780
+ try {
781
+ let externalFsp = await externalParticipantModelCached.getByName(name)
782
+ if (!externalFsp) {
783
+ const proxy = await ParticipantModelCached.getByName(proxyId)
784
+ if (!proxy) {
785
+ throw new Error(`Proxy participant not found: ${proxyId}`)
786
+ }
787
+ const externalParticipantId = await externalParticipantModelCached.create({
788
+ name,
789
+ proxyId: proxy.participantId
790
+ })
791
+ externalFsp = externalParticipantId
792
+ ? { externalParticipantId }
793
+ : await externalParticipantModelCached.getByName(name)
794
+ }
795
+ const id = externalFsp?.externalParticipantId
796
+ logger.verbose('getExternalParticipantIdByNameOrCreate result:', { id, name })
797
+ return id
798
+ } catch (err) {
799
+ logger.child({ name, proxyId }).warn('error in getExternalParticipantIdByNameOrCreate:', err)
800
+ return null
801
+ // todo: think, if we need to rethrow an error here?
802
+ }
803
+ }
804
+
738
805
  module.exports = {
739
806
  addHubAccountAndInitPosition,
740
807
  getByNameAndCurrency,
808
+ getByIDAndCurrency,
741
809
  getParticipantLimitByParticipantIdAndCurrencyId,
742
810
  getEndpoint,
743
811
  getAllEndpoints,
@@ -750,5 +818,6 @@ module.exports = {
750
818
  getParticipantLimitsByParticipantId,
751
819
  getAllAccountsByNameAndCurrency,
752
820
  getLimitsForAllParticipants,
753
- getAllNonHubParticipantsWithCurrencies
821
+ getAllNonHubParticipantsWithCurrencies,
822
+ getExternalParticipantIdByNameOrCreate
754
823
  }
@@ -43,7 +43,8 @@ exports.create = async (participant) => {
43
43
  try {
44
44
  const result = await Db.from('participant').insert({
45
45
  name: participant.name,
46
- createdBy: 'unknown'
46
+ createdBy: 'unknown',
47
+ isProxy: !!participant.isProxy
47
48
  })
48
49
  return result
49
50
  } catch (err) {
@@ -43,7 +43,7 @@ exports.create = async (participantId, currencyId, ledgerAccountTypeId, isActive
43
43
 
44
44
  exports.getAll = async () => {
45
45
  try {
46
- return Db.from('participantCurrency').find({}, { order: 'participantCurrencyId asc' })
46
+ return await Db.from('participantCurrency').find({}, { order: 'participantCurrencyId asc' })
47
47
  } catch (err) {
48
48
  throw ErrorHandler.Factory.reformatFSPIOPError(err)
49
49
  }
@@ -107,23 +107,17 @@ const destroyByParticipantId = async (participantId) => {
107
107
  const createParticipantPositionRecords = async (participantPositions, trx) => {
108
108
  try {
109
109
  const knex = Db.getKnex()
110
- const trxFunction = async (trx, doCommit = true) => {
110
+ const trxFunction = async (trx) => {
111
111
  try {
112
112
  await knex
113
113
  .batchInsert('participantPosition', participantPositions)
114
114
  .transacting(trx)
115
- if (doCommit) {
116
- await trx.commit
117
- }
118
115
  } catch (err) {
119
- if (doCommit) {
120
- await trx.rollback
121
- }
122
116
  throw ErrorHandler.Factory.reformatFSPIOPError(err)
123
117
  }
124
118
  }
125
119
  if (trx) {
126
- return trxFunction(trx, false)
120
+ return trxFunction(trx)
127
121
  } else {
128
122
  return knex.transaction(trxFunction)
129
123
  }
@@ -63,6 +63,28 @@ const getLatestTransferStateChangesByTransferIdList = async (trx, transfersIdLis
63
63
  }
64
64
  }
65
65
 
66
+ const getLatestFxTransferStateChangesByCommitRequestIdList = async (trx, commitRequestIdList) => {
67
+ const knex = await Db.getKnex()
68
+ try {
69
+ const latestFxTransferStateChanges = {}
70
+ const results = await knex('fxTransferStateChange')
71
+ .transacting(trx)
72
+ .whereIn('fxTransferStateChange.commitRequestId', commitRequestIdList)
73
+ .orderBy('fxTransferStateChangeId', 'desc')
74
+ .select('*')
75
+
76
+ for (const result of results) {
77
+ if (!latestFxTransferStateChanges[result.commitRequestId]) {
78
+ latestFxTransferStateChanges[result.commitRequestId] = result
79
+ }
80
+ }
81
+ return latestFxTransferStateChanges
82
+ } catch (err) {
83
+ Logger.isErrorEnabled && Logger.error(err)
84
+ throw err
85
+ }
86
+ }
87
+
66
88
  const getAllParticipantCurrency = async (trx) => {
67
89
  const knex = await Db.getKnex()
68
90
  if (trx) {
@@ -138,6 +160,11 @@ const bulkInsertTransferStateChanges = async (trx, transferStateChangeList) => {
138
160
  return await knex.batchInsert('transferStateChange', transferStateChangeList).transacting(trx)
139
161
  }
140
162
 
163
+ const bulkInsertFxTransferStateChanges = async (trx, fxTransferStateChangeList) => {
164
+ const knex = await Db.getKnex()
165
+ return await knex.batchInsert('fxTransferStateChange', fxTransferStateChangeList).transacting(trx)
166
+ }
167
+
141
168
  const bulkInsertParticipantPositionChanges = async (trx, participantPositionChangeList) => {
142
169
  const knex = await Db.getKnex()
143
170
  return await knex.batchInsert('participantPositionChange', participantPositionChangeList).transacting(trx)
@@ -184,14 +211,76 @@ const getTransferByIdsForReserve = async (trx, transferIds) => {
184
211
  return {}
185
212
  }
186
213
 
214
+ const getFxTransferInfoList = async (trx, commitRequestId, transferParticipantRoleTypeId, ledgerEntryTypeId) => {
215
+ try {
216
+ const knex = await Db.getKnex()
217
+ const transferInfos = await knex('fxTransferParticipant')
218
+ .transacting(trx)
219
+ .where({
220
+ 'fxTransferParticipant.transferParticipantRoleTypeId': transferParticipantRoleTypeId,
221
+ 'fxTransferParticipant.ledgerEntryTypeId': ledgerEntryTypeId
222
+ })
223
+ .whereIn('fxTransferParticipant.commitRequestId', commitRequestId)
224
+ .select(
225
+ 'fxTransferParticipant.*'
226
+ )
227
+ const info = {}
228
+ // This should key the transfer info with the latest transferStateChangeId
229
+ for (const transferInfo of transferInfos) {
230
+ if (!(transferInfo.commitRequestId in info)) {
231
+ info[transferInfo.commitRequestId] = transferInfo
232
+ }
233
+ }
234
+ return info
235
+ } catch (err) {
236
+ Logger.isErrorEnabled && Logger.error(err)
237
+ throw err
238
+ }
239
+ }
240
+
241
+ // This model assumes that there is only one RESERVED participantPositionChange per commitRequestId and participantPositionId.
242
+ // If an fxTransfer use case changes in the future where more than one reservation happens to a participant's account
243
+ // for the same commitRequestId, this model will need to be updated.
244
+ const getReservedPositionChangesByCommitRequestIds = async (trx, commitRequestIdList) => {
245
+ try {
246
+ const knex = await Db.getKnex()
247
+ const participantPositionChanges = await knex('fxTransferStateChange')
248
+ .transacting(trx)
249
+ .whereIn('fxTransferStateChange.commitRequestId', commitRequestIdList)
250
+ .where('fxTransferStateChange.transferStateId', Enum.Transfers.TransferInternalState.RESERVED)
251
+ .leftJoin('participantPositionChange AS ppc', 'ppc.fxTransferStateChangeId', 'fxTransferStateChange.fxTransferStateChangeId')
252
+ .select(
253
+ 'ppc.*',
254
+ 'fxTransferStateChange.commitRequestId AS commitRequestId'
255
+ )
256
+ const info = {}
257
+ for (const participantPositionChange of participantPositionChanges) {
258
+ if (!(participantPositionChange.commitRequestId in info)) {
259
+ info[participantPositionChange.commitRequestId] = {}
260
+ }
261
+ if (participantPositionChange.participantCurrencyId) {
262
+ info[participantPositionChange.commitRequestId][participantPositionChange.participantCurrencyId] = participantPositionChange
263
+ }
264
+ }
265
+ return info
266
+ } catch (err) {
267
+ Logger.isErrorEnabled && Logger.error(err)
268
+ throw err
269
+ }
270
+ }
271
+
187
272
  module.exports = {
188
273
  startDbTransaction,
189
274
  getLatestTransferStateChangesByTransferIdList,
275
+ getLatestFxTransferStateChangesByCommitRequestIdList,
190
276
  getPositionsByAccountIdsForUpdate,
191
277
  updateParticipantPosition,
192
278
  bulkInsertTransferStateChanges,
279
+ bulkInsertFxTransferStateChanges,
193
280
  bulkInsertParticipantPositionChanges,
194
281
  getAllParticipantCurrency,
195
282
  getTransferInfoList,
196
- getTransferByIdsForReserve
283
+ getTransferByIdsForReserve,
284
+ getFxTransferInfoList,
285
+ getReservedPositionChangesByCommitRequestIds
197
286
  }
@@ -229,11 +229,13 @@ const prepareChangeParticipantPositionTransaction = async (transferList) => {
229
229
  const processedTransfersKeysList = Object.keys(processedTransfers)
230
230
  const batchParticipantPositionChange = []
231
231
  for (const keyIndex in processedTransfersKeysList) {
232
- const { runningPosition, runningReservedValue } = processedTransfers[processedTransfersKeysList[keyIndex]]
232
+ const { transferAmount, runningPosition, runningReservedValue } = processedTransfers[processedTransfersKeysList[keyIndex]]
233
233
  const participantPositionChange = {
234
234
  participantPositionId: initialParticipantPosition.participantPositionId,
235
+ participantCurrencyId: participantCurrency.participantCurrencyId,
235
236
  transferStateChangeId: processedTransferStateChangeIdList[keyIndex],
236
237
  value: runningPosition,
238
+ change: transferAmount.toNumber(),
237
239
  // processBatch: <uuid> - a single value uuid for this entire batch to make sure the set of transfers in this batch can be clearly grouped
238
240
  reservedValue: runningReservedValue
239
241
  }
@@ -241,11 +243,9 @@ const prepareChangeParticipantPositionTransaction = async (transferList) => {
241
243
  }
242
244
  batchParticipantPositionChange.length && await knex.batchInsert('participantPositionChange', batchParticipantPositionChange).transacting(trx)
243
245
  histTimerPersistTransferStateChangeEnd({ success: true, queryName: 'facade_prepareChangeParticipantPositionTransaction_transaction_PersistTransferState' })
244
- await trx.commit()
245
246
  histTimerChangeParticipantPositionTransEnd({ success: true, queryName: 'facade_prepareChangeParticipantPositionTransaction_transaction' })
246
247
  } catch (err) {
247
248
  Logger.isErrorEnabled && Logger.error(err)
248
- await trx.rollback()
249
249
  histTimerChangeParticipantPositionTransEnd({ success: false, queryName: 'facade_prepareChangeParticipantPositionTransaction_transaction' })
250
250
  throw ErrorHandler.Factory.reformatFSPIOPError(err)
251
251
  }
@@ -292,16 +292,16 @@ const changeParticipantPositionTransaction = async (participantCurrencyId, isRev
292
292
  const insertedTransferStateChange = await knex('transferStateChange').transacting(trx).where({ transferId: transferStateChange.transferId }).forUpdate().first().orderBy('transferStateChangeId', 'desc')
293
293
  const participantPositionChange = {
294
294
  participantPositionId: participantPosition.participantPositionId,
295
+ participantCurrencyId,
295
296
  transferStateChangeId: insertedTransferStateChange.transferStateChangeId,
296
297
  value: latestPosition,
298
+ change: isReversal ? -amount : amount,
297
299
  reservedValue: participantPosition.reservedValue,
298
300
  createdDate: transactionTimestamp
299
301
  }
300
302
  await knex('participantPositionChange').transacting(trx).insert(participantPositionChange)
301
- await trx.commit()
302
303
  histTimerChangeParticipantPositionTransactionEnd({ success: true, queryName: 'facade_changeParticipantPositionTransaction' })
303
304
  } catch (err) {
304
- await trx.rollback()
305
305
  throw ErrorHandler.Factory.reformatFSPIOPError(err)
306
306
  }
307
307
  }).catch((err) => {