@drift-labs/sdk 2.0.14 → 2.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/dlob/DLOB.ts CHANGED
@@ -59,8 +59,6 @@ export type NodeToTrigger = {
59
59
  node: TriggerOrderNode;
60
60
  };
61
61
 
62
- type Side = 'ask' | 'bid' | 'both' | 'nocross';
63
-
64
62
  export class DLOB {
65
63
  openOrders = new Map<MarketTypeStr, Set<string>>();
66
64
  orderLists = new Map<MarketTypeStr, Map<number, MarketNodeLists>>();
@@ -348,53 +346,19 @@ export class DLOB {
348
346
  ): NodeToFill[] {
349
347
  const nodesToFill = new Array<NodeToFill>();
350
348
 
351
- let askGenerator = this.getLimitAsks(
352
- marketIndex,
353
- slot,
354
- marketType,
355
- oraclePriceData
356
- );
357
-
358
- let bidGenerator = this.getLimitBids(
349
+ const crossingNodes = this.findCrossingLimitOrders(
359
350
  marketIndex,
360
351
  slot,
361
352
  marketType,
362
353
  oraclePriceData
363
354
  );
364
355
 
365
- let nextAsk = askGenerator.next();
366
- let nextBid = bidGenerator.next();
367
-
368
- // First try to find orders that cross
369
- while (!nextAsk.done && !nextBid.done) {
370
- const { crossingNodes, exhaustedSide } = this.findCrossingLimitOrders(
371
- nextAsk.value,
372
- nextBid.value,
373
- oraclePriceData,
374
- slot
375
- );
376
-
377
- if (exhaustedSide === 'bid') {
378
- nextBid = bidGenerator.next();
379
- } else if (exhaustedSide === 'ask') {
380
- nextAsk = askGenerator.next();
381
- } else if (exhaustedSide === 'both') {
382
- nextBid = bidGenerator.next();
383
- nextAsk = askGenerator.next();
384
- } else if (exhaustedSide === 'nocross') {
385
- break;
386
- } else {
387
- console.error(`invalid exhaustedSide: ${exhaustedSide}`);
388
- break;
389
- }
390
-
391
- for (const crossingNode of crossingNodes) {
392
- nodesToFill.push(crossingNode);
393
- }
356
+ for (const crossingNode of crossingNodes) {
357
+ nodesToFill.push(crossingNode);
394
358
  }
395
359
 
396
360
  if (fallbackBid && !isAmmPaused) {
397
- askGenerator = this.getLimitAsks(
361
+ const askGenerator = this.getLimitAsks(
398
362
  marketIndex,
399
363
  slot,
400
364
  marketType,
@@ -417,7 +381,7 @@ export class DLOB {
417
381
  }
418
382
 
419
383
  if (fallbackAsk && !isAmmPaused) {
420
- bidGenerator = this.getLimitBids(
384
+ const bidGenerator = this.getLimitBids(
421
385
  marketIndex,
422
386
  slot,
423
387
  marketType,
@@ -454,18 +418,14 @@ export class DLOB {
454
418
  const nodesToFill = new Array<NodeToFill>();
455
419
 
456
420
  let marketOrderGenerator = this.getMarketAsks(marketIndex, marketType);
457
- let limitOrderGenerator = this.getLimitBids(
458
- marketIndex,
459
- slot,
460
- marketType,
461
- oraclePriceData
462
- );
463
421
 
464
422
  const marketAsksCrossingBids = this.findMarketNodesCrossingLimitNodes(
423
+ marketIndex,
465
424
  slot,
425
+ marketType,
466
426
  oraclePriceData,
467
427
  marketOrderGenerator,
468
- limitOrderGenerator,
428
+ this.getLimitBids.bind(this),
469
429
  (takerPrice, makerPrice) => {
470
430
  return takerPrice === undefined || takerPrice.lte(makerPrice);
471
431
  }
@@ -494,18 +454,14 @@ export class DLOB {
494
454
  }
495
455
 
496
456
  marketOrderGenerator = this.getMarketBids(marketIndex, marketType);
497
- limitOrderGenerator = this.getLimitAsks(
498
- marketIndex,
499
- slot,
500
- marketType,
501
- oraclePriceData
502
- );
503
457
 
504
458
  const marketBidsToFill = this.findMarketNodesCrossingLimitNodes(
459
+ marketIndex,
505
460
  slot,
461
+ marketType,
506
462
  oraclePriceData,
507
463
  marketOrderGenerator,
508
- limitOrderGenerator,
464
+ this.getLimitAsks.bind(this),
509
465
  (takerPrice, fallbackPrice) => {
510
466
  return takerPrice === undefined || takerPrice.gte(fallbackPrice);
511
467
  }
@@ -537,88 +493,92 @@ export class DLOB {
537
493
  }
538
494
 
539
495
  public findMarketNodesCrossingLimitNodes(
496
+ marketIndex: number,
540
497
  slot: number,
498
+ marketType: MarketType,
541
499
  oraclePriceData: OraclePriceData,
542
500
  takerNodeGenerator: Generator<DLOBNode>,
543
- makerNodeGenerator: Generator<DLOBNode>,
501
+ makerNodeGeneratorFn: (
502
+ marketIndex: number,
503
+ slot: number,
504
+ marketType: MarketType,
505
+ oraclePriceData: OraclePriceData
506
+ ) => Generator<DLOBNode>,
544
507
  doesCross: (takerPrice: BN | undefined, makerPrice: BN) => boolean
545
508
  ): NodeToFill[] {
546
509
  const nodesToFill = new Array<NodeToFill>();
547
510
 
548
- let nextTakerNode = takerNodeGenerator.next();
549
- let nextMakerNode = makerNodeGenerator.next();
550
-
551
- while (!nextTakerNode.done && !nextMakerNode.done) {
552
- const takerNode: DLOBNode = nextTakerNode.value;
553
- const makerNode: DLOBNode = nextMakerNode.value;
554
-
555
- const bidUserAuthority = this.userMap.getUserAuthority(
556
- makerNode.userAccount.toString()
557
- );
558
- const askUserAuthority = this.userMap.getUserAuthority(
559
- takerNode.userAccount.toString()
511
+ for (const takerNode of takerNodeGenerator) {
512
+ const makerNodeGenerator = makerNodeGeneratorFn(
513
+ marketIndex,
514
+ slot,
515
+ marketType,
516
+ oraclePriceData
560
517
  );
561
518
 
562
- // Can't match orders from the same authority
563
- const sameAuthority = bidUserAuthority.equals(askUserAuthority);
519
+ for (const makerNode of makerNodeGenerator) {
520
+ const bidUserAuthority = this.userMap.getUserAuthority(
521
+ makerNode.userAccount.toString()
522
+ );
523
+ const askUserAuthority = this.userMap.getUserAuthority(
524
+ takerNode.userAccount.toString()
525
+ );
526
+
527
+ // Can't match orders from the same authority
528
+ const sameAuthority = bidUserAuthority.equals(askUserAuthority);
564
529
 
565
- if (sameAuthority) {
566
- if (Math.random() < 0.5) {
567
- nextTakerNode = takerNodeGenerator.next();
568
- } else {
569
- nextMakerNode = makerNodeGenerator.next();
530
+ if (sameAuthority) {
531
+ continue;
570
532
  }
571
- continue;
572
- }
573
533
 
574
- const makerPrice = makerNode.getPrice(oraclePriceData, slot);
575
- const takerPrice = takerNode.getPrice(oraclePriceData, slot);
534
+ const makerPrice = makerNode.getPrice(oraclePriceData, slot);
535
+ const takerPrice = takerNode.getPrice(oraclePriceData, slot);
576
536
 
577
- const ordersCross = doesCross(takerPrice, makerPrice);
578
- if (!ordersCross) {
579
- // market orders aren't sorted by price, they are sorted by time, so we need to traverse
580
- // through all of em
581
- nextTakerNode = takerNodeGenerator.next();
582
- continue;
583
- }
537
+ const ordersCross = doesCross(takerPrice, makerPrice);
538
+ if (!ordersCross) {
539
+ // market orders aren't sorted by price, they are sorted by time, so we need to traverse
540
+ // through all of em
541
+ break;
542
+ }
584
543
 
585
- nodesToFill.push({
586
- node: takerNode,
587
- makerNode: makerNode,
588
- });
544
+ nodesToFill.push({
545
+ node: takerNode,
546
+ makerNode: makerNode,
547
+ });
589
548
 
590
- const makerOrder = makerNode.order;
591
- const takerOrder = takerNode.order;
549
+ const makerOrder = makerNode.order;
550
+ const takerOrder = takerNode.order;
592
551
 
593
- const makerBaseRemaining = makerOrder.baseAssetAmount.sub(
594
- makerOrder.baseAssetAmountFilled
595
- );
596
- const takerBaseRemaining = takerOrder.baseAssetAmount.sub(
597
- takerOrder.baseAssetAmountFilled
598
- );
552
+ const makerBaseRemaining = makerOrder.baseAssetAmount.sub(
553
+ makerOrder.baseAssetAmountFilled
554
+ );
555
+ const takerBaseRemaining = takerOrder.baseAssetAmount.sub(
556
+ takerOrder.baseAssetAmountFilled
557
+ );
599
558
 
600
- const baseFilled = BN.min(makerBaseRemaining, takerBaseRemaining);
559
+ const baseFilled = BN.min(makerBaseRemaining, takerBaseRemaining);
601
560
 
602
- const newMakerOrder = { ...makerOrder };
603
- newMakerOrder.baseAssetAmountFilled =
604
- makerOrder.baseAssetAmountFilled.add(baseFilled);
605
- this.getListForOrder(newMakerOrder).update(
606
- newMakerOrder,
607
- makerNode.userAccount
608
- );
609
- if (newMakerOrder.baseAssetAmountFilled.eq(makerOrder.baseAssetAmount)) {
610
- nextMakerNode = makerNodeGenerator.next();
611
- }
561
+ const newMakerOrder = { ...makerOrder };
562
+ newMakerOrder.baseAssetAmountFilled =
563
+ makerOrder.baseAssetAmountFilled.add(baseFilled);
564
+ this.getListForOrder(newMakerOrder).update(
565
+ newMakerOrder,
566
+ makerNode.userAccount
567
+ );
612
568
 
613
- const newTakerOrder = { ...takerOrder };
614
- newTakerOrder.baseAssetAmountFilled =
615
- takerOrder.baseAssetAmountFilled.add(baseFilled);
616
- this.getListForOrder(newTakerOrder).update(
617
- newTakerOrder,
618
- takerNode.userAccount
619
- );
620
- if (newTakerOrder.baseAssetAmountFilled.eq(takerOrder.baseAssetAmount)) {
621
- nextTakerNode = takerNodeGenerator.next();
569
+ const newTakerOrder = { ...takerOrder };
570
+ newTakerOrder.baseAssetAmountFilled =
571
+ takerOrder.baseAssetAmountFilled.add(baseFilled);
572
+ this.getListForOrder(newTakerOrder).update(
573
+ newTakerOrder,
574
+ takerNode.userAccount
575
+ );
576
+
577
+ if (
578
+ newTakerOrder.baseAssetAmountFilled.eq(takerOrder.baseAssetAmount)
579
+ ) {
580
+ break;
581
+ }
622
582
  }
623
583
  }
624
584
 
@@ -959,162 +919,117 @@ export class DLOB {
959
919
  }
960
920
 
961
921
  findCrossingLimitOrders(
962
- askNode: DLOBNode,
963
- bidNode: DLOBNode,
964
- oraclePriceData: OraclePriceData,
965
- slot: number
966
- ): {
967
- crossingNodes: NodeToFill[];
968
- exhaustedSide: Side;
969
- } {
970
- const bidPrice = bidNode.getPrice(oraclePriceData, slot);
971
- const askPrice = askNode.getPrice(oraclePriceData, slot);
972
-
973
- // orders don't cross - we're done walkin gup the book
974
- if (bidPrice.lt(askPrice)) {
975
- return {
976
- crossingNodes: [],
977
- exhaustedSide: 'nocross',
978
- };
979
- }
922
+ marketIndex: number,
923
+ slot: number,
924
+ marketType: MarketType,
925
+ oraclePriceData: OraclePriceData
926
+ ): NodeToFill[] {
927
+ const nodesToFill = new Array<NodeToFill>();
980
928
 
981
- const bidOrder = bidNode.order;
982
- const askOrder = askNode.order;
929
+ for (const askNode of this.getLimitAsks(
930
+ marketIndex,
931
+ slot,
932
+ marketType,
933
+ oraclePriceData
934
+ )) {
935
+ for (const bidNode of this.getLimitBids(
936
+ marketIndex,
937
+ slot,
938
+ marketType,
939
+ oraclePriceData
940
+ )) {
941
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
942
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
983
943
 
984
- const bidUserAuthority = this.userMap.getUserAuthority(
985
- bidNode.userAccount.toString()
986
- );
987
- const askUserAuthority = this.userMap.getUserAuthority(
988
- askNode.userAccount.toString()
989
- );
944
+ // orders don't cross - we're done walking the book
945
+ if (bidPrice.lt(askPrice)) {
946
+ return nodesToFill;
947
+ }
990
948
 
991
- // Can't match orders from the same authority
992
- const sameAuthority = bidUserAuthority.equals(askUserAuthority);
993
- if (sameAuthority || (bidOrder.postOnly && askOrder.postOnly)) {
994
- // don't have a principle way to pick which one to exhaust,
995
- // exhaust each one 50% of the time so we can try each one against other orders
996
- const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
997
- return {
998
- crossingNodes: [],
999
- exhaustedSide,
1000
- };
1001
- }
949
+ const bidOrder = bidNode.order;
950
+ const askOrder = askNode.order;
1002
951
 
1003
- const { takerNode, makerNode } = this.determineMakerAndTaker(
1004
- askNode,
1005
- bidNode
1006
- );
952
+ const bidUserAuthority = this.userMap.getUserAuthority(
953
+ bidNode.userAccount.toString()
954
+ );
955
+ const askUserAuthority = this.userMap.getUserAuthority(
956
+ askNode.userAccount.toString()
957
+ );
1007
958
 
1008
- const bidBaseRemaining = bidOrder.baseAssetAmount.sub(
1009
- bidOrder.baseAssetAmountFilled
1010
- );
1011
- const askBaseRemaining = askOrder.baseAssetAmount.sub(
1012
- askOrder.baseAssetAmountFilled
1013
- );
959
+ // Can't match orders from the same authority
960
+ const sameAuthority = bidUserAuthority.equals(askUserAuthority);
961
+ if (sameAuthority || (bidOrder.postOnly && askOrder.postOnly)) {
962
+ continue;
963
+ }
1014
964
 
1015
- let exhaustedSide: Side;
1016
- if (bidBaseRemaining.eq(askBaseRemaining)) {
1017
- exhaustedSide = 'both';
1018
- } else if (bidBaseRemaining.gt(askBaseRemaining)) {
1019
- exhaustedSide = 'ask';
1020
- } else {
1021
- exhaustedSide = 'bid';
1022
- }
965
+ const { takerNode, makerNode } = this.determineMakerAndTaker(
966
+ askNode,
967
+ bidNode
968
+ );
1023
969
 
1024
- // update the orders on DLOB as if they were fill - so we don't try to match them on the next iteration
1025
- // NOTE: if something goes wrong during the actual fill (transaction fails, i.e. due to orders already being filled)
1026
- // then we risk having a mismatch between this local DLOB and the actual DLOB state on the blockchain. This isn't
1027
- // a problem in the current implementation because we construct a new DLOB from the blockchain state every time, rather
1028
- // than updating the existing DLOB based on events.
1029
- if (exhaustedSide === 'ask') {
1030
- // bid partially filled
1031
- const newBidOrder = { ...bidOrder };
1032
- newBidOrder.baseAssetAmountFilled =
1033
- bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
1034
- this.getListForOrder(newBidOrder).update(
1035
- newBidOrder,
1036
- bidNode.userAccount
1037
- );
970
+ const bidBaseRemaining = bidOrder.baseAssetAmount.sub(
971
+ bidOrder.baseAssetAmountFilled
972
+ );
973
+ const askBaseRemaining = askOrder.baseAssetAmount.sub(
974
+ askOrder.baseAssetAmountFilled
975
+ );
1038
976
 
1039
- // ask completely filled
1040
- const newAskOrder = { ...askOrder };
1041
- newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
1042
- this.getListForOrder(newAskOrder).update(
1043
- newAskOrder,
1044
- askNode.userAccount
1045
- );
1046
- } else if (exhaustedSide === 'bid') {
1047
- // ask partially filled
1048
- const newAskOrder = { ...askOrder };
1049
- newAskOrder.baseAssetAmountFilled =
1050
- askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
1051
- this.getListForOrder(newAskOrder).update(
1052
- newAskOrder,
1053
- askNode.userAccount
1054
- );
977
+ const baseFilled = BN.min(bidBaseRemaining, askBaseRemaining);
1055
978
 
1056
- // bid completely filled
1057
- const newBidOrder = { ...bidOrder };
1058
- newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
1059
- this.getListForOrder(newBidOrder).update(
1060
- newBidOrder,
1061
- bidNode.userAccount
1062
- );
1063
- } else {
1064
- // both completely filled
1065
- const newBidOrder = { ...bidOrder };
1066
- newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
1067
- this.getListForOrder(newBidOrder).update(
1068
- newBidOrder,
1069
- bidNode.userAccount
1070
- );
979
+ const newBidOrder = { ...bidOrder };
980
+ newBidOrder.baseAssetAmountFilled =
981
+ bidOrder.baseAssetAmountFilled.add(baseFilled);
982
+ this.getListForOrder(newBidOrder).update(
983
+ newBidOrder,
984
+ bidNode.userAccount
985
+ );
1071
986
 
1072
- const newAskOrder = { ...askOrder };
1073
- newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
1074
- this.getListForOrder(newAskOrder).update(
1075
- newAskOrder,
1076
- askNode.userAccount
1077
- );
1078
- }
987
+ // ask completely filled
988
+ const newAskOrder = { ...askOrder };
989
+ newAskOrder.baseAssetAmountFilled =
990
+ askOrder.baseAssetAmountFilled.add(baseFilled);
991
+ this.getListForOrder(newAskOrder).update(
992
+ newAskOrder,
993
+ askNode.userAccount
994
+ );
1079
995
 
1080
- return {
1081
- crossingNodes: [
1082
- {
996
+ nodesToFill.push({
1083
997
  node: takerNode,
1084
998
  makerNode: makerNode,
1085
- },
1086
- ],
1087
- exhaustedSide,
1088
- };
999
+ });
1000
+
1001
+ if (newAskOrder.baseAssetAmount.eq(newAskOrder.baseAssetAmountFilled)) {
1002
+ break;
1003
+ }
1004
+ }
1005
+ }
1006
+
1007
+ return nodesToFill;
1089
1008
  }
1090
1009
 
1091
1010
  determineMakerAndTaker(
1092
1011
  askNode: DLOBNode,
1093
1012
  bidNode: DLOBNode
1094
- ): { takerNode: DLOBNode; makerNode: DLOBNode; makerSide: Side } {
1013
+ ): { takerNode: DLOBNode; makerNode: DLOBNode } {
1095
1014
  if (bidNode.order.postOnly) {
1096
1015
  return {
1097
1016
  takerNode: askNode,
1098
1017
  makerNode: bidNode,
1099
- makerSide: 'bid',
1100
1018
  };
1101
1019
  } else if (askNode.order.postOnly) {
1102
1020
  return {
1103
1021
  takerNode: bidNode,
1104
1022
  makerNode: askNode,
1105
- makerSide: 'ask',
1106
1023
  };
1107
1024
  } else if (askNode.order.slot.lt(bidNode.order.slot)) {
1108
1025
  return {
1109
1026
  takerNode: bidNode,
1110
1027
  makerNode: askNode,
1111
- makerSide: 'ask',
1112
1028
  };
1113
1029
  } else {
1114
1030
  return {
1115
1031
  takerNode: askNode,
1116
1032
  makerNode: bidNode,
1117
- makerSide: 'bid',
1118
1033
  };
1119
1034
  }
1120
1035
  }
@@ -1432,7 +1432,7 @@
1432
1432
  },
1433
1433
  {
1434
1434
  "name": "spotMarket",
1435
- "isMut": false,
1435
+ "isMut": true,
1436
1436
  "isSigner": false
1437
1437
  },
1438
1438
  {
@@ -3233,6 +3233,48 @@
3233
3233
  }
3234
3234
  ]
3235
3235
  },
3236
+ {
3237
+ "name": "updateInitialPctToLiquidate",
3238
+ "accounts": [
3239
+ {
3240
+ "name": "admin",
3241
+ "isMut": false,
3242
+ "isSigner": true
3243
+ },
3244
+ {
3245
+ "name": "state",
3246
+ "isMut": true,
3247
+ "isSigner": false
3248
+ }
3249
+ ],
3250
+ "args": [
3251
+ {
3252
+ "name": "initialPctToLiquidate",
3253
+ "type": "u16"
3254
+ }
3255
+ ]
3256
+ },
3257
+ {
3258
+ "name": "updateLiquidationDuration",
3259
+ "accounts": [
3260
+ {
3261
+ "name": "admin",
3262
+ "isMut": false,
3263
+ "isSigner": true
3264
+ },
3265
+ {
3266
+ "name": "state",
3267
+ "isMut": true,
3268
+ "isSigner": false
3269
+ }
3270
+ ],
3271
+ "args": [
3272
+ {
3273
+ "name": "liquidationDuration",
3274
+ "type": "u8"
3275
+ }
3276
+ ]
3277
+ },
3236
3278
  {
3237
3279
  "name": "updateOracleGuardRails",
3238
3280
  "accounts": [
@@ -4320,12 +4362,20 @@
4320
4362
  "defined": "ExchangeStatus"
4321
4363
  }
4322
4364
  },
4365
+ {
4366
+ "name": "liquidationDuration",
4367
+ "type": "u8"
4368
+ },
4369
+ {
4370
+ "name": "initialPctToLiquidate",
4371
+ "type": "u16"
4372
+ },
4323
4373
  {
4324
4374
  "name": "padding",
4325
4375
  "type": {
4326
4376
  "array": [
4327
4377
  "u8",
4328
- 17
4378
+ 14
4329
4379
  ]
4330
4380
  }
4331
4381
  }
@@ -4420,8 +4470,8 @@
4420
4470
  "type": "u64"
4421
4471
  },
4422
4472
  {
4423
- "name": "liquidationStartTs",
4424
- "type": "i64"
4473
+ "name": "liquidationStartSlot",
4474
+ "type": "u64"
4425
4475
  },
4426
4476
  {
4427
4477
  "name": "nextOrderId",
@@ -5596,11 +5646,11 @@
5596
5646
  "type": "u64"
5597
5647
  },
5598
5648
  {
5599
- "name": "lastNetBaseAssetAmountPerLp",
5649
+ "name": "lastBaseAssetAmountPerLp",
5600
5650
  "type": "i64"
5601
5651
  },
5602
5652
  {
5603
- "name": "lastNetQuoteAssetAmountPerLp",
5653
+ "name": "lastQuoteAssetAmountPerLp",
5604
5654
  "type": "i64"
5605
5655
  },
5606
5656
  {
@@ -6016,6 +6066,9 @@
6016
6066
  },
6017
6067
  {
6018
6068
  "name": "OrderFillWithSerum"
6069
+ },
6070
+ {
6071
+ "name": "NoBorrowLiquidity"
6019
6072
  }
6020
6073
  ]
6021
6074
  }
@@ -8454,11 +8507,16 @@
8454
8507
  },
8455
8508
  {
8456
8509
  "code": 6219,
8510
+ "name": "UnableToGetLimitPrice",
8511
+ "msg": "Unable To Get Limit Price"
8512
+ },
8513
+ {
8514
+ "code": 6220,
8457
8515
  "name": "InvalidLiquidation",
8458
8516
  "msg": "Invalid Liquidation"
8459
8517
  },
8460
8518
  {
8461
- "code": 6220,
8519
+ "code": 6221,
8462
8520
  "name": "SpotFulfillmentConfigDisabled",
8463
8521
  "msg": "Spot Fulfullment Config Disabled"
8464
8522
  }