@defisaver/automation-sdk 3.3.18 → 3.3.19-sub-fetching.1-dev

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 (55) hide show
  1. package/cjs/automation/private/StrategiesAutomation.d.ts +5 -1
  2. package/cjs/automation/private/StrategiesAutomation.js +29 -2
  3. package/cjs/automation/private/StrategiesAutomation.test.js +66 -0
  4. package/cjs/constants/index.js +35 -0
  5. package/cjs/services/apiSubscriptionsService.d.ts +7 -0
  6. package/cjs/services/apiSubscriptionsService.js +33 -0
  7. package/cjs/services/apiSubscriptionsService.test.d.ts +1 -0
  8. package/cjs/services/apiSubscriptionsService.test.js +69 -0
  9. package/cjs/services/ethereumService.test.js +4 -7
  10. package/cjs/services/strategiesService.js +40 -9
  11. package/cjs/services/strategiesService.test.js +0 -1
  12. package/cjs/services/strategySubService.d.ts +4 -0
  13. package/cjs/services/strategySubService.js +24 -0
  14. package/cjs/services/strategySubService.test.js +295 -0
  15. package/cjs/services/subDataService.d.ts +38 -0
  16. package/cjs/services/subDataService.js +76 -2
  17. package/cjs/services/subDataService.test.js +494 -4
  18. package/cjs/types/enums.d.ts +15 -2
  19. package/cjs/types/enums.js +15 -1
  20. package/cjs/types/index.d.ts +32 -3
  21. package/esm/automation/private/StrategiesAutomation.d.ts +5 -1
  22. package/esm/automation/private/StrategiesAutomation.js +29 -2
  23. package/esm/automation/private/StrategiesAutomation.test.js +67 -1
  24. package/esm/constants/index.js +35 -0
  25. package/esm/services/apiSubscriptionsService.d.ts +7 -0
  26. package/esm/services/apiSubscriptionsService.js +29 -0
  27. package/esm/services/apiSubscriptionsService.test.d.ts +1 -0
  28. package/esm/services/apiSubscriptionsService.test.js +67 -0
  29. package/esm/services/ethereumService.test.js +4 -7
  30. package/esm/services/strategiesService.js +40 -9
  31. package/esm/services/strategiesService.test.js +0 -1
  32. package/esm/services/strategySubService.d.ts +4 -0
  33. package/esm/services/strategySubService.js +24 -0
  34. package/esm/services/strategySubService.test.js +295 -0
  35. package/esm/services/subDataService.d.ts +38 -0
  36. package/esm/services/subDataService.js +74 -0
  37. package/esm/services/subDataService.test.js +494 -4
  38. package/esm/types/enums.d.ts +15 -2
  39. package/esm/types/enums.js +14 -0
  40. package/esm/types/index.d.ts +32 -3
  41. package/package.json +1 -1
  42. package/src/automation/private/StrategiesAutomation.test.ts +84 -1
  43. package/src/automation/private/StrategiesAutomation.ts +33 -0
  44. package/src/constants/index.ts +35 -0
  45. package/src/services/apiSubscriptionsService.test.ts +75 -0
  46. package/src/services/apiSubscriptionsService.ts +33 -0
  47. package/src/services/ethereumService.test.ts +5 -8
  48. package/src/services/strategiesService.test.ts +0 -1
  49. package/src/services/strategiesService.ts +43 -10
  50. package/src/services/strategySubService.test.ts +382 -0
  51. package/src/services/strategySubService.ts +87 -0
  52. package/src/services/subDataService.test.ts +608 -4
  53. package/src/services/subDataService.ts +107 -0
  54. package/src/types/enums.ts +14 -0
  55. package/src/types/index.ts +34 -2
@@ -1,8 +1,8 @@
1
1
  import type Web3 from 'web3';
2
2
  import type { AbiItem } from 'web3-utils';
3
3
  import type { BaseContract, BlockType } from './contracts/generated/types';
4
- import type { Subscribe, StrategyModel } from './contracts/generated/SubStorage';
5
- import type { ChainId, Strategies, Bundles, ProtocolIdentifiers, RatioState, CloseToAssetType } from './enums';
4
+ import type { StrategyModel } from './contracts/generated/SubStorage';
5
+ import type { ChainId, Strategies, Bundles, ProtocolIdentifiers, RatioState, CloseToAssetType, SubscriptionStatus } from './enums';
6
6
  export type PlaceholderType = any;
7
7
  export type EthereumAddress = string;
8
8
  export type BlockNumber = BlockType;
@@ -53,6 +53,23 @@ interface _SubscriptionOptions {
53
53
  unexpiredOnly: boolean;
54
54
  }
55
55
  export type SubscriptionOptions = Partial<_SubscriptionOptions>;
56
+ /** Subscription record as returned by the automation API (GET /v1/subscriptions) */
57
+ export interface ApiSubscriptionRecord {
58
+ id: number;
59
+ wallet: EthereumAddress;
60
+ wallet_type: string;
61
+ is_enabled: boolean;
62
+ invalid: boolean;
63
+ status: SubscriptionStatus;
64
+ is_bundle: boolean;
65
+ strategy_or_bundle_id: number;
66
+ strategy_ids: number[];
67
+ sub_data_hash: string;
68
+ trigger_data: string[];
69
+ sub_data: string[];
70
+ block_number: number;
71
+ additional_triggers: string[] | null;
72
+ }
56
73
  export declare namespace Interfaces {
57
74
  interface ProtocolBase {
58
75
  id: ProtocolIdentifiers.StrategiesAutomation | ProtocolIdentifiers.LegacyAutomation;
@@ -105,6 +122,9 @@ export declare namespace Position {
105
122
  triggerBoostRatio?: number;
106
123
  targetBoostRatio?: number;
107
124
  boostEnabled?: boolean;
125
+ /** Set only for subscriptions parsed from the automation API: the backend marked that half invalid */
126
+ repayInvalid?: boolean;
127
+ boostInvalid?: boolean;
108
128
  }
109
129
  interface CloseOnPrice extends Base {
110
130
  price: string;
@@ -238,6 +258,8 @@ export declare namespace Position {
238
258
  subId: number;
239
259
  subIds?: number[];
240
260
  isEnabled?: boolean;
261
+ /** Set only for subscriptions parsed from the automation API: the backend will not execute this subscription */
262
+ invalid?: boolean;
241
263
  subHash: string;
242
264
  blockNumber: BlockNumber;
243
265
  protocol: Interfaces.Protocol;
@@ -291,10 +313,17 @@ export interface BundlesInfo {
291
313
  [ChainId.Base]: BaseBundleInfo;
292
314
  }
293
315
  export type StrategyOrBundleIds = typeof Strategies.MainnetIds[keyof typeof Strategies.MainnetIds] | typeof Strategies.OptimismIds[keyof typeof Strategies.OptimismIds] | typeof Strategies.ArbitrumIds[keyof typeof Strategies.ArbitrumIds] | typeof Strategies.BaseIds[keyof typeof Strategies.BaseIds] | typeof Bundles.MainnetIds[keyof typeof Bundles.MainnetIds] | typeof Bundles.OptimismIds[keyof typeof Bundles.OptimismIds] | typeof Bundles.ArbitrumIds[keyof typeof Bundles.ArbitrumIds] | typeof Bundles.BaseIds[keyof typeof Bundles.BaseIds];
316
+ /** Fields of the Subscribe event that parsing relies on */
317
+ export interface SubscriptionEventData {
318
+ subId: string;
319
+ proxy: EthereumAddress;
320
+ subHash: string;
321
+ subStruct: StrategyModel.StrategySubStructOutputStruct;
322
+ }
294
323
  export interface ParseData {
295
324
  chainId: ChainId;
296
325
  blockNumber: BlockNumber;
297
- subscriptionEventData: Subscribe['returnValues'];
326
+ subscriptionEventData: SubscriptionEventData;
298
327
  strategiesSubsData: StrategyModel.StoredSubDataStructOutputStruct;
299
328
  }
300
329
  type MapToProtocolVersion<T> = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defisaver/automation-sdk",
3
- "version": "3.3.18",
3
+ "version": "3.3.19-sub-fetching.1-dev",
4
4
  "description": "",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./esm/index.js",
@@ -1,7 +1,8 @@
1
1
  import Web3 from 'web3';
2
2
  import { expect } from 'chai';
3
3
 
4
- import {ChainId} from '../../types/enums';
4
+ import { ChainId, SubscriptionStatus } from '../../types/enums';
5
+ import type { ApiSubscriptionRecord } from '../../types';
5
6
 
6
7
  import '../../configuration';
7
8
  import StrategiesAutomation from './StrategiesAutomation';
@@ -700,4 +701,86 @@ describe('Feature: StrategiesAutomation.ts', () => {
700
701
  expect(loneBoost?.subId).to.equal(1);
701
702
  });
702
703
  });
704
+
705
+ describe('When testing StrategiesAutomation.parseSubscriptionsFromApi', () => {
706
+ const strategiesAutomation = new StrategiesAutomation({
707
+ chainId: ChainId.Ethereum,
708
+ provider: Web3_1,
709
+ providerFork: null!,
710
+ });
711
+
712
+ const owner = '0x9cB7E19861665366011899d74E75d4F2A419aEeD';
713
+ const subData = [
714
+ '0x0000000000000000000000000000000000000000000000001bc16d674ec80000',
715
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
716
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
717
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
718
+ ];
719
+ // Aave V3 leverage management: repay (bundle 8, ratio under) and boost (bundle 9, ratio over) for the same owner and market
720
+ const repay: ApiSubscriptionRecord = {
721
+ id: 379,
722
+ wallet: owner,
723
+ wallet_type: 'safe',
724
+ is_enabled: true,
725
+ invalid: false,
726
+ status: SubscriptionStatus.Active,
727
+ is_bundle: true,
728
+ strategy_or_bundle_id: 8,
729
+ strategy_ids: [34, 35],
730
+ sub_data_hash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
731
+ trigger_data: ['0000000000000000000000009cb7e19861665366011899d74e75d4f2a419aeed0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e00000000000000000000000000000000000000000000000019ac8532c27900000000000000000000000000000000000000000000000000000000000000000001'],
732
+ sub_data: subData,
733
+ block_number: 18015756,
734
+ additional_triggers: null,
735
+ };
736
+ const boost: ApiSubscriptionRecord = {
737
+ ...repay,
738
+ id: 380,
739
+ is_enabled: false,
740
+ status: SubscriptionStatus.Disabled,
741
+ strategy_or_bundle_id: 9,
742
+ sub_data_hash: '0x1111111111111111111111111111111111111111111111111111111111111111',
743
+ trigger_data: ['0000000000000000000000009cb7e19861665366011899d74e75d4f2a419aeed0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e0000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000000000'],
744
+ };
745
+
746
+ it('Given API records should return parsed positions carrying the invalid flags', () => {
747
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([repay, { ...boost, invalid: true }]);
748
+ expect(positions).to.have.length(2);
749
+ expect(positions.map((p) => p?.subId)).to.eql([379, 380]);
750
+ expect(positions.map((p) => p?.isEnabled)).to.eql([true, false]);
751
+ expect(positions.map((p) => p?.invalid)).to.eql([false, true]);
752
+ expect((positions[0]?.specific as { repayInvalid?: boolean }).repayInvalid).to.equal(false);
753
+ expect((positions[1]?.specific as { boostInvalid?: boolean }).boostInvalid).to.equal(true);
754
+ });
755
+
756
+ it('Given a merged pair should keep each half\'s invalid flag and mark the pair invalid only when both are', () => {
757
+ const oneInvalid = strategiesAutomation.parseSubscriptionsFromApi([repay, { ...boost, invalid: true }], { mergeSubs: true });
758
+ expect(oneInvalid).to.have.length(1);
759
+ expect(oneInvalid[0]?.invalid).to.equal(false);
760
+ expect(oneInvalid[0]?.specific).to.include({ repayInvalid: false, boostInvalid: true });
761
+
762
+ const bothInvalid = strategiesAutomation.parseSubscriptionsFromApi(
763
+ [{ ...repay, invalid: true }, { ...boost, invalid: true }],
764
+ { mergeSubs: true },
765
+ );
766
+ expect(bothInvalid[0]?.invalid).to.equal(true);
767
+ });
768
+
769
+ it('Given mergeSubs option should merge the repay and boost pair', () => {
770
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([repay, boost], { mergeSubs: true });
771
+ expect(positions).to.have.length(1);
772
+ expect(positions[0]?.subIds).to.eql([379, 380]);
773
+ expect(positions[0]?.isEnabled).to.equal(true);
774
+ });
775
+
776
+ it('Given enabledOnly option should drop disabled records before parsing', () => {
777
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([repay, boost], { enabledOnly: true });
778
+ expect(positions.map((p) => p?.subId)).to.eql([379]);
779
+ });
780
+
781
+ it('Given a record of an unknown strategy should return null for it', () => {
782
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([{ ...repay, strategy_or_bundle_id: 9999 }]);
783
+ expect(positions).to.eql([null]);
784
+ });
785
+ });
703
786
  });
@@ -4,6 +4,7 @@ import type { PastEventOptions } from 'web3-eth-contract';
4
4
  import PromisePool from 'es6-promise-pool';
5
5
  import type {
6
6
  Position, Interfaces, EthereumAddress, SubscriptionOptions, Contract, ParseData, PlaceholderType, BlockNumber,
7
+ ApiSubscriptionRecord,
7
8
  } from '../../types';
8
9
  import type {
9
10
  StrategyModel, Subscribe, SubStorage, UpdateData,
@@ -15,6 +16,7 @@ import { addToObjectIf, isDefined, isUndefined } from '../../services/utils';
15
16
  import { getAbiItem, makeSubStorageContract } from '../../services/contractService';
16
17
  import { getEventsFromContract, multicall } from '../../services/ethereumService';
17
18
  import { parseStrategiesAutomatedPosition } from '../../services/strategiesService';
19
+ import { parseDataFromApiSubscription } from '../../services/apiSubscriptionsService';
18
20
 
19
21
  import Automation from './Automation';
20
22
 
@@ -23,6 +25,17 @@ interface IStrategiesAutomation extends Interfaces.Automation {
23
25
  providerFork?: Web3,
24
26
  }
25
27
 
28
+ /**
29
+ * Adds the backend flags to a parsed position. A repay or boost half of a leverage management pair also gets its
30
+ * own invalid flag in specific, next to repayEnabled and boostEnabled, so it survives merging.
31
+ */
32
+ const withApiFlags = (position: Position.Automated, record: ApiSubscriptionRecord): Position.Automated => {
33
+ const specific = { ...position.specific } as Position.Specific.RatioProtection;
34
+ if (isDefined(specific.mergeWithId)) specific.repayInvalid = record.invalid;
35
+ if (isDefined(specific.mergeId)) specific.boostInvalid = record.invalid;
36
+ return { ...position, invalid: record.invalid, specific: specific as Position.SpecificAny };
37
+ };
38
+
26
39
  export default class StrategiesAutomation extends Automation {
27
40
  protected chainId: ChainId;
28
41
 
@@ -167,6 +180,8 @@ export default class StrategiesAutomation extends Automation {
167
180
  blockNumber: Dec.max(mergePair.blockNumber, current.blockNumber).toNumber(),
168
181
  subIds: [current.subId, mergePair.subId],
169
182
  isEnabled: mergePair.isEnabled || current.isEnabled,
183
+ // set only for subscriptions parsed from the automation API, per half flags live in specific
184
+ ...addToObjectIf(isDefined(current.invalid), { invalid: !!mergePair.invalid && !!current.invalid }),
170
185
  specific: {
171
186
  ...mergePair.specific,
172
187
  ...current.specific,
@@ -259,6 +274,24 @@ export default class StrategiesAutomation extends Automation {
259
274
  return this._getSubscriptions(undefined, options);
260
275
  }
261
276
 
277
+ /**
278
+ * @description Parses subscriptions returned by the automation API (GET /v1/subscriptions)
279
+ */
280
+ public parseSubscriptionsFromApi(records: ApiSubscriptionRecord[], options?: SubscriptionOptions): (Position.Automated | null)[] {
281
+ const filtered = options?.enabledOnly ? records.filter((record) => record.is_enabled) : records;
282
+
283
+ let subscriptions: (Position.Automated | null)[] = filtered.map((record) => {
284
+ const position = this.getParsedSubscriptions(parseDataFromApiSubscription(record, this.chainId));
285
+ return position && withApiFlags(position, record);
286
+ });
287
+
288
+ if (options?.mergeSubs) {
289
+ subscriptions = this.mergeSubs(subscriptions);
290
+ }
291
+
292
+ return options?.unexpiredOnly ? this.removeExpiredSubscriptions(subscriptions) : subscriptions;
293
+ }
294
+
262
295
  public async getSubscriptionsFor(addresses: EthereumAddress[], options?: SubscriptionOptions): Promise<(Position.Automated | null)[]> {
263
296
  return this._getSubscriptions(addresses, options);
264
297
  }
@@ -140,6 +140,11 @@ export const MAINNET_STRATEGIES_INFO: MainnetStrategiesInfo = {
140
140
  strategyId: Strategies.Identifiers.CollateralSwitch,
141
141
  protocol: PROTOCOLS.Spark,
142
142
  },
143
+ [Strategies.MainnetIds.SPARK_GENERIC_FL_COLLATERAL_SWITCH]: {
144
+ strategyOrBundleId: Strategies.MainnetIds.SPARK_GENERIC_FL_COLLATERAL_SWITCH,
145
+ strategyId: Strategies.Identifiers.EoaCollateralSwitch,
146
+ protocol: PROTOCOLS.Spark,
147
+ },
143
148
  };
144
149
 
145
150
  export const OPTIMISM_STRATEGIES_INFO: OptimismStrategiesInfo = {
@@ -573,6 +578,31 @@ export const MAINNET_BUNDLES_INFO: MainnetBundleInfo = {
573
578
  strategyId: Strategies.Identifiers.EoaCloseOnPrice,
574
579
  protocol: PROTOCOLS.AaveV4,
575
580
  },
581
+ [Bundles.MainnetIds.SPARK_EOA_REPAY]: {
582
+ strategyOrBundleId: Bundles.MainnetIds.SPARK_EOA_REPAY,
583
+ strategyId: Strategies.Identifiers.EoaRepay,
584
+ protocol: PROTOCOLS.Spark,
585
+ },
586
+ [Bundles.MainnetIds.SPARK_EOA_BOOST]: {
587
+ strategyOrBundleId: Bundles.MainnetIds.SPARK_EOA_BOOST,
588
+ strategyId: Strategies.Identifiers.EoaBoost,
589
+ protocol: PROTOCOLS.Spark,
590
+ },
591
+ [Bundles.MainnetIds.SPARK_EOA_CLOSE]: {
592
+ strategyOrBundleId: Bundles.MainnetIds.SPARK_EOA_CLOSE,
593
+ strategyId: Strategies.Identifiers.EoaCloseOnPrice,
594
+ protocol: PROTOCOLS.Spark,
595
+ },
596
+ [Bundles.MainnetIds.SPARK_EOA_REPAY_ON_PRICE]: {
597
+ strategyOrBundleId: Bundles.MainnetIds.SPARK_EOA_REPAY_ON_PRICE,
598
+ strategyId: Strategies.Identifiers.EoaRepayOnPrice,
599
+ protocol: PROTOCOLS.Spark,
600
+ },
601
+ [Bundles.MainnetIds.SPARK_EOA_BOOST_ON_PRICE]: {
602
+ strategyOrBundleId: Bundles.MainnetIds.SPARK_EOA_BOOST_ON_PRICE,
603
+ strategyId: Strategies.Identifiers.EoaBoostOnPrice,
604
+ protocol: PROTOCOLS.Spark,
605
+ },
576
606
  [Bundles.MainnetIds.MAKER_SW_LIQUIDATION_PROTECTION]: {
577
607
  strategyOrBundleId: Bundles.MainnetIds.MAKER_SW_LIQUIDATION_PROTECTION,
578
608
  strategyId: Strategies.Identifiers.LiquidationProtection,
@@ -649,6 +679,11 @@ export const MAINNET_BUNDLES_INFO: MainnetBundleInfo = {
649
679
  strategyId: Strategies.Identifiers.EoaCloseOnPrice,
650
680
  protocol: PROTOCOLS.MorphoBlue,
651
681
  },
682
+ [Bundles.MainnetIds.SPARK_EOA_LIQUIDATION_PROTECTION]: {
683
+ strategyOrBundleId: Bundles.MainnetIds.SPARK_EOA_LIQUIDATION_PROTECTION,
684
+ strategyId: Strategies.Identifiers.EoaLiquidationProtection,
685
+ protocol: PROTOCOLS.Spark,
686
+ },
652
687
  };
653
688
 
654
689
  export const OPTIMISM_BUNDLES_INFO: OptimismBundleInfo = {
@@ -0,0 +1,75 @@
1
+ import { expect } from 'chai';
2
+
3
+ import type { ApiSubscriptionRecord, ParseData } from '../types';
4
+ import { ChainId, SubscriptionStatus } from '../types/enums';
5
+
6
+ import '../configuration';
7
+ import { parseDataFromApiSubscription } from './apiSubscriptionsService';
8
+ import { parseStrategiesAutomatedPosition } from './strategiesService';
9
+
10
+ describe('Feature: apiSubscriptionsService.ts', () => {
11
+ describe('When testing apiSubscriptionsService.parseDataFromApiSubscription', () => {
12
+ // same subscription as in strategiesService.test.ts, in the shape the automation API returns it
13
+ const record: ApiSubscriptionRecord = {
14
+ id: 379,
15
+ wallet: '0x9cB7E19861665366011899d74E75d4F2A419aEeD',
16
+ wallet_type: 'safe',
17
+ is_enabled: true,
18
+ invalid: false,
19
+ status: SubscriptionStatus.Active,
20
+ is_bundle: true,
21
+ strategy_or_bundle_id: 8,
22
+ strategy_ids: [34, 35],
23
+ sub_data_hash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
24
+ trigger_data: [
25
+ '0000000000000000000000009cb7e19861665366011899d74e75d4f2a419aeed0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e00000000000000000000000000000000000000000000000019ac8532c27900000000000000000000000000000000000000000000000000000000000000000001',
26
+ ],
27
+ sub_data: [
28
+ '0x0000000000000000000000000000000000000000000000001bc16d674ec80000',
29
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
30
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
31
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
32
+ ],
33
+ block_number: 18015756,
34
+ additional_triggers: null,
35
+ };
36
+
37
+ const fromEvents: ParseData = {
38
+ chainId: ChainId.Ethereum,
39
+ blockNumber: 18015756,
40
+ subscriptionEventData: {
41
+ subId: '379',
42
+ proxy: '0x9cB7E19861665366011899d74E75d4F2A419aEeD',
43
+ subHash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
44
+ subStruct: {
45
+ strategyOrBundleId: '8',
46
+ isBundle: true,
47
+ triggerData: [
48
+ '0x0000000000000000000000009cb7e19861665366011899d74e75d4f2a419aeed0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e00000000000000000000000000000000000000000000000019ac8532c27900000000000000000000000000000000000000000000000000000000000000000001',
49
+ ],
50
+ subData: record.sub_data,
51
+ },
52
+ },
53
+ strategiesSubsData: {
54
+ userProxy: '0x9cB7E19861665366011899d74E75d4F2A419aEeD',
55
+ isEnabled: true,
56
+ strategySubHash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
57
+ },
58
+ };
59
+
60
+ it('Given an API record should return the same parse data as Subscribe event and SubStorage would', () => {
61
+ expect(parseDataFromApiSubscription(record, ChainId.Ethereum)).to.eql(fromEvents);
62
+ });
63
+
64
+ it('Given an API record the parsed position should match the one parsed from events', () => {
65
+ const fromApi = parseStrategiesAutomatedPosition(parseDataFromApiSubscription(record, ChainId.Ethereum));
66
+ expect(fromApi).to.not.equal(null);
67
+ expect(fromApi).to.eql(parseStrategiesAutomatedPosition(fromEvents));
68
+ });
69
+
70
+ it('Given a disabled record should keep it disabled', () => {
71
+ const parseData = parseDataFromApiSubscription({ ...record, is_enabled: false }, ChainId.Ethereum);
72
+ expect(parseData.strategiesSubsData.isEnabled).to.equal(false);
73
+ });
74
+ });
75
+ });
@@ -0,0 +1,33 @@
1
+ import type { ApiSubscriptionRecord, ParseData } from '../types';
2
+ import type { ChainId } from '../types/enums';
3
+
4
+ function addHexPrefix(value: string): string {
5
+ return value.startsWith('0x') ? value : `0x${value}`;
6
+ }
7
+
8
+ /**
9
+ * @description Maps a subscription record returned by the automation API to the data
10
+ * the parser expects from Subscribe events and SubStorage (trigger data is stored without 0x prefix)
11
+ */
12
+ export function parseDataFromApiSubscription(record: ApiSubscriptionRecord, chainId: ChainId): ParseData {
13
+ return {
14
+ chainId,
15
+ blockNumber: record.block_number,
16
+ subscriptionEventData: {
17
+ subId: record.id.toString(),
18
+ proxy: record.wallet,
19
+ subHash: record.sub_data_hash,
20
+ subStruct: {
21
+ strategyOrBundleId: record.strategy_or_bundle_id.toString(),
22
+ isBundle: record.is_bundle,
23
+ triggerData: record.trigger_data.map(addHexPrefix),
24
+ subData: record.sub_data.map(addHexPrefix),
25
+ },
26
+ },
27
+ strategiesSubsData: {
28
+ userProxy: record.wallet,
29
+ isEnabled: record.is_enabled,
30
+ strategySubHash: record.sub_data_hash,
31
+ },
32
+ };
33
+ }
@@ -1,10 +1,11 @@
1
1
  import Web3 from 'web3';
2
2
  import { expect } from 'chai';
3
+ import { omit } from 'lodash';
3
4
  import { getAssetInfo } from '@defisaver/tokens';
4
5
  import { PastEventOptions } from 'web3-eth-contract';
5
6
 
6
7
  import { ChainId } from '../types/enums';
7
- import type { BlockNumber, Multicall } from '../types';
8
+ import type { BlockNumber, Multicall, PlaceholderType } from '../types';
8
9
  import { Contract } from '../types';
9
10
  import type { Erc20 } from '../types/contracts/generated';
10
11
 
@@ -115,7 +116,6 @@ describe('Feature: ethereumService.ts', () => {
115
116
  'address': '0x5f98805A4E8be255a32880FDeC7F6728C6568bA0',
116
117
  'blockHash': '0xb92cab2569456dbfbdb853d2c67d72c9a7580543dbcb55d483a77322b40755a4',
117
118
  'blockNumber': 15166163,
118
- 'blockTimestamp': '0x62d53ad8',
119
119
  'event': 'Transfer',
120
120
  'id': 'log_e2258e3a',
121
121
  'logIndex': 385,
@@ -142,7 +142,7 @@ describe('Feature: ethereumService.ts', () => {
142
142
  },
143
143
  ],
144
144
  [
145
- makeErc20Contract(Web3_1, getAssetInfo('LUSD').address, ChainId.Ethereum),
145
+ { ...makeErc20Contract(Web3_1, getAssetInfo('LUSD').address, ChainId.Ethereum), createdBlock: 15166163 },
146
146
  null,
147
147
  'Transfer',
148
148
  {
@@ -161,7 +161,6 @@ describe('Feature: ethereumService.ts', () => {
161
161
  'address': '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
162
162
  'blockHash': '0xacb0213af63b4c17c436f084a96d1ac385641a59a9a4cf014ae3337cbe545aa7',
163
163
  'blockNumber': 5353002,
164
- 'blockTimestamp': '0x624c1a5b',
165
164
  'event': 'Transfer',
166
165
  'id': 'log_f49645b8',
167
166
  'logIndex': 1,
@@ -207,7 +206,6 @@ describe('Feature: ethereumService.ts', () => {
207
206
  'address': '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
208
207
  'blockHash': '0xacb0213af63b4c17c436f084a96d1ac385641a59a9a4cf014ae3337cbe545aa7',
209
208
  'blockNumber': 5353002,
210
- 'blockTimestamp': '0x624c1a5b',
211
209
  'event': 'Transfer',
212
210
  'id': 'log_f49645b8',
213
211
  'logIndex': 1,
@@ -251,9 +249,8 @@ describe('Feature: ethereumService.ts', () => {
251
249
 
252
250
  examples.forEach(([expected, actual]) => {
253
251
  it(`Given ${actual} should return expected value: ${JSON.stringify(expected)}`, async () => {
254
- const data = await getEventsFromContract(...actual);
255
- console.log(data);
256
- expect(await getEventsFromContract(...actual)).to.eql(expected);
252
+ const events = await getEventsFromContract(...actual);
253
+ expect(events.map((event: PlaceholderType) => omit(event, 'blockTimestamp'))).to.eql(expected);
257
254
  });
258
255
  });
259
256
  });
@@ -76,7 +76,6 @@ describe('Feature: strategiesService.ts', () => {
76
76
  subId: '379',
77
77
  proxy: '0x9cb7e19861665366011899d74e75d4f2a419aeed',
78
78
  subHash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
79
- // @ts-ignore
80
79
  subStruct:
81
80
  {
82
81
  strategyOrBundleId: '8',
@@ -8,7 +8,7 @@ import type {
8
8
  BundleInfoUnion, StrategyInfoUnion,
9
9
  } from '../types';
10
10
  import {
11
- ChainId, ProtocolIdentifiers, RatioState, Strategies,
11
+ ChainId, ProtocolIdentifiers, Strategies,
12
12
  } from '../types/enums';
13
13
 
14
14
  import {
@@ -448,11 +448,17 @@ function parseAaveV3CollateralSwitch(position: Position.Automated, parseData: Pa
448
448
  function parseSparkCollateralSwitch(position: Position.Automated, parseData: ParseData): Position.Automated {
449
449
  const _position = cloneDeep(position);
450
450
  const { subStruct } = parseData.subscriptionEventData;
451
+ const isEOA = _position.strategy.strategyId.includes('eoa');
451
452
  const triggerData = triggerService.sparkQuotePriceTrigger.decode(subStruct.triggerData);
452
- const subData = subDataService.sparkCollateralSwitchSubData.decode(subStruct.subData);
453
+ const subData = isEOA
454
+ ? subDataService.sparkGenericFLCollateralSwitchSubData.decode(subStruct.subData)
455
+ : subDataService.sparkCollateralSwitchSubData.decode(subStruct.subData);
453
456
  _position.strategyData.decoded.triggerData = triggerData;
454
457
  _position.strategyData.decoded.subData = subData;
455
458
  _position.positionId = getPositionId(_position.chainId, _position.protocol.id, _position.owner, subData.marketAddr);
459
+ _position.strategy.strategyId = isEOA
460
+ ? Strategies.Identifiers.EoaCollateralSwitch
461
+ : Strategies.Identifiers.CollateralSwitch;
456
462
  return _position;
457
463
  }
458
464
 
@@ -987,16 +993,19 @@ function parseSparkLeverageManagement(position: Position.Automated, parseData: P
987
993
 
988
994
  const { subStruct, subId, subHash } = parseData.subscriptionEventData;
989
995
  const { isEnabled } = parseData.strategiesSubsData;
996
+ const isEOA = _position.strategy.strategyId.includes('eoa');
990
997
 
991
998
  const triggerData = triggerService.sparkRatioTrigger.decode(subStruct.triggerData);
992
- const subData = subDataService.legacySparkLeverageManagementSubData.decode(subStruct.subData);
999
+ const subData = isEOA
1000
+ ? subDataService.sparkGenericLeverageManagementSubData.decode(subStruct.subData)
1001
+ : subDataService.legacySparkLeverageManagementSubData.decode(subStruct.subData);
993
1002
 
994
1003
  _position.strategyData.decoded.triggerData = triggerData;
995
1004
  _position.strategyData.decoded.subData = subData;
996
1005
 
997
1006
  _position.positionId = getPositionId(_position.chainId, _position.protocol.id, _position.owner, triggerData.market);
998
1007
 
999
- const isRepay = _position.strategy.strategyId === Strategies.Identifiers.Repay;
1008
+ const isRepay = [Strategies.Identifiers.Repay, Strategies.Identifiers.EoaRepay].includes(_position.strategy.strategyId as Strategies.Identifiers);
1000
1009
 
1001
1010
  if (isRepay) {
1002
1011
  _position.specific = {
@@ -1004,7 +1013,7 @@ function parseSparkLeverageManagement(position: Position.Automated, parseData: P
1004
1013
  targetRepayRatio: subData.targetRatio,
1005
1014
  repayEnabled: isEnabled,
1006
1015
  subId1: Number(subId),
1007
- mergeWithId: Strategies.Identifiers.Boost,
1016
+ mergeWithId: isEOA ? Strategies.Identifiers.EoaBoost : Strategies.Identifiers.Boost,
1008
1017
  subHashRepay: subHash,
1009
1018
  };
1010
1019
  } else {
@@ -1013,12 +1022,14 @@ function parseSparkLeverageManagement(position: Position.Automated, parseData: P
1013
1022
  targetBoostRatio: subData.targetRatio,
1014
1023
  boostEnabled: isEnabled,
1015
1024
  subId2: Number(subId),
1016
- mergeId: Strategies.Identifiers.Boost,
1025
+ mergeId: isEOA ? Strategies.Identifiers.EoaBoost : Strategies.Identifiers.Boost,
1017
1026
  subHashBoost: subHash,
1018
1027
  };
1019
1028
  }
1020
1029
 
1021
- _position.strategy.strategyId = Strategies.IdOverrides.LeverageManagement;
1030
+ _position.strategy.strategyId = isEOA
1031
+ ? Strategies.IdOverrides.EoaLeverageManagement
1032
+ : Strategies.IdOverrides.LeverageManagement;
1022
1033
 
1023
1034
  return _position;
1024
1035
  }
@@ -1028,9 +1039,12 @@ function parseSparkLiquidationProtection(position: Position.Automated, parseData
1028
1039
 
1029
1040
  const { subStruct, subId, subHash } = parseData.subscriptionEventData;
1030
1041
  const { isEnabled } = parseData.strategiesSubsData;
1042
+ const isEOA = _position.strategy.strategyId.includes('eoa');
1031
1043
 
1032
1044
  const triggerData = triggerService.sparkRatioTrigger.decode(subStruct.triggerData);
1033
- const subData = subDataService.sparkLiquidationProtectionSubData.decode(subStruct.subData);
1045
+ const subData = isEOA
1046
+ ? subDataService.sparkGenericLiquidationProtectionSubData.decode(subStruct.subData)
1047
+ : subDataService.sparkLiquidationProtectionSubData.decode(subStruct.subData);
1034
1048
 
1035
1049
  _position.strategyData.decoded.triggerData = triggerData;
1036
1050
  _position.strategyData.decoded.subData = subData;
@@ -1045,7 +1059,9 @@ function parseSparkLiquidationProtection(position: Position.Automated, parseData
1045
1059
  subHashRepay: subHash,
1046
1060
  };
1047
1061
 
1048
- _position.strategy.strategyId = Strategies.IdOverrides.LiquidationProtection;
1062
+ _position.strategy.strategyId = isEOA
1063
+ ? Strategies.IdOverrides.EoaLiquidationProtection
1064
+ : Strategies.IdOverrides.LiquidationProtection;
1049
1065
 
1050
1066
  return _position;
1051
1067
  }
@@ -1054,9 +1070,12 @@ function parseSparkLiquidationProtection(position: Position.Automated, parseData
1054
1070
  function parseSparkLeverageManagementOnPrice(position: Position.Automated, parseData: ParseData): Position.Automated {
1055
1071
  const _position = cloneDeep(position);
1056
1072
  const { subStruct } = parseData.subscriptionEventData;
1073
+ const isEOA = _position.strategy.strategyId.includes('eoa');
1057
1074
 
1058
1075
  const triggerData = triggerService.sparkQuotePriceTrigger.decode(subStruct.triggerData);
1059
- const subData = subDataService.sparkLeverageManagementOnPriceSubData.decode(subStruct.subData);
1076
+ const subData = isEOA
1077
+ ? subDataService.sparkLeverageManagementOnPriceGenericSubData.decode(subStruct.subData)
1078
+ : subDataService.sparkLeverageManagementOnPriceSubData.decode(subStruct.subData);
1060
1079
 
1061
1080
  _position.strategyData.decoded.triggerData = triggerData;
1062
1081
  _position.strategyData.decoded.subData = subData;
@@ -1077,6 +1096,10 @@ function parseSparkLeverageManagementOnPrice(position: Position.Automated, parse
1077
1096
  ratioState: triggerData.ratioState,
1078
1097
  };
1079
1098
 
1099
+ _position.strategy.strategyId = isEOA
1100
+ ? Strategies.IdOverrides.EoaLeverageManagementOnPrice
1101
+ : Strategies.IdOverrides.LeverageManagementOnPrice;
1102
+
1080
1103
  return _position;
1081
1104
  }
1082
1105
 
@@ -1087,10 +1110,13 @@ function parseSparkCloseOnPrice(position: Position.Automated, parseData: ParseDa
1087
1110
  const triggerData = triggerService.sparkQuotePriceRangeTrigger.decode(subStruct.triggerData);
1088
1111
  const subData = subDataService.sparkCloseGenericSubData.decode(subStruct.subData);
1089
1112
 
1113
+ const isEOA = _position.strategy.strategyId.includes('eoa');
1114
+
1090
1115
  _position.strategyData.decoded.triggerData = triggerData;
1091
1116
  _position.strategyData.decoded.subData = subData;
1092
1117
 
1093
1118
  _position.positionId = getPositionId(_position.chainId, _position.protocol.id, _position.owner, subData.marketAddr);
1119
+ _position.strategy.strategyId = isEOA ? Strategies.Identifiers.EoaCloseOnPrice : Strategies.Identifiers.CloseOnPrice;
1094
1120
 
1095
1121
  const { takeProfitType, stopLossType } = getStopLossAndTakeProfitTypeByCloseStrategyType(+subData.closeType);
1096
1122
 
@@ -1625,8 +1651,15 @@ const parsingMethodsMapping: StrategiesToProtocolVersionMapping = {
1625
1651
  [Strategies.Identifiers.RepayOnPrice]: parseSparkLeverageManagementOnPrice,
1626
1652
  [Strategies.Identifiers.BoostOnPrice]: parseSparkLeverageManagementOnPrice,
1627
1653
  [Strategies.Identifiers.CloseOnPrice]: parseSparkCloseOnPrice,
1654
+ [Strategies.Identifiers.EoaRepay]: parseSparkLeverageManagement,
1655
+ [Strategies.Identifiers.EoaBoost]: parseSparkLeverageManagement,
1656
+ [Strategies.Identifiers.EoaRepayOnPrice]: parseSparkLeverageManagementOnPrice,
1657
+ [Strategies.Identifiers.EoaBoostOnPrice]: parseSparkLeverageManagementOnPrice,
1658
+ [Strategies.Identifiers.EoaCloseOnPrice]: parseSparkCloseOnPrice,
1628
1659
  [Strategies.Identifiers.CollateralSwitch]: parseSparkCollateralSwitch,
1660
+ [Strategies.Identifiers.EoaCollateralSwitch]: parseSparkCollateralSwitch,
1629
1661
  [Strategies.Identifiers.LiquidationProtection]: parseSparkLiquidationProtection,
1662
+ [Strategies.Identifiers.EoaLiquidationProtection]: parseSparkLiquidationProtection,
1630
1663
  },
1631
1664
  [ProtocolIdentifiers.StrategiesAutomation.CrvUSD]: {
1632
1665
  [Strategies.Identifiers.Repay]: parseCrvUSDLeverageManagement,