@defisaver/automation-sdk 3.3.18-spark-eoa-v2-dev → 3.3.19-sub-fetching-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 (30) hide show
  1. package/cjs/automation/private/StrategiesAutomation.d.ts +5 -1
  2. package/cjs/automation/private/StrategiesAutomation.js +19 -2
  3. package/cjs/automation/private/StrategiesAutomation.test.js +55 -0
  4. package/cjs/services/apiSubscriptionsService.d.ts +7 -0
  5. package/cjs/services/apiSubscriptionsService.js +33 -0
  6. package/cjs/services/apiSubscriptionsService.test.d.ts +1 -0
  7. package/cjs/services/apiSubscriptionsService.test.js +69 -0
  8. package/cjs/services/strategiesService.test.js +0 -1
  9. package/cjs/types/enums.d.ts +6 -0
  10. package/cjs/types/enums.js +8 -1
  11. package/cjs/types/index.d.ts +29 -3
  12. package/esm/automation/private/StrategiesAutomation.d.ts +5 -1
  13. package/esm/automation/private/StrategiesAutomation.js +20 -3
  14. package/esm/automation/private/StrategiesAutomation.test.js +56 -1
  15. package/esm/services/apiSubscriptionsService.d.ts +7 -0
  16. package/esm/services/apiSubscriptionsService.js +29 -0
  17. package/esm/services/apiSubscriptionsService.test.d.ts +1 -0
  18. package/esm/services/apiSubscriptionsService.test.js +67 -0
  19. package/esm/services/strategiesService.test.js +0 -1
  20. package/esm/types/enums.d.ts +6 -0
  21. package/esm/types/enums.js +7 -0
  22. package/esm/types/index.d.ts +29 -3
  23. package/package.json +1 -1
  24. package/src/automation/private/StrategiesAutomation.test.ts +70 -1
  25. package/src/automation/private/StrategiesAutomation.ts +25 -1
  26. package/src/services/apiSubscriptionsService.test.ts +75 -0
  27. package/src/services/apiSubscriptionsService.ts +33 -0
  28. package/src/services/strategiesService.test.ts +0 -1
  29. package/src/types/enums.ts +7 -0
  30. package/src/types/index.ts +31 -2
@@ -1,6 +1,6 @@
1
1
  import type Web3 from 'web3';
2
2
  import type { PastEventOptions } from 'web3-eth-contract';
3
- import type { Position, Interfaces, EthereumAddress, SubscriptionOptions, Contract, ParseData, BlockNumber } from '../../types';
3
+ import type { Position, Interfaces, EthereumAddress, SubscriptionOptions, Contract, ParseData, BlockNumber, ApiSubscriptionRecord } from '../../types';
4
4
  import type { StrategyModel, Subscribe, SubStorage, UpdateData } from '../../types/contracts/generated/SubStorage';
5
5
  import type { ChainId } from '../../types/enums';
6
6
  import Automation from './Automation';
@@ -30,6 +30,10 @@ export default class StrategiesAutomation extends Automation {
30
30
  protected mergeSubs(_subscriptions: (Position.Automated | null)[]): Position.Automated[];
31
31
  protected _getSubscriptions(addresses?: EthereumAddress[], options?: SubscriptionOptions): Promise<(Position.Automated | null)[]>;
32
32
  getSubscriptions(options?: SubscriptionOptions): Promise<(Position.Automated | null)[]>;
33
+ /**
34
+ * @description Parses subscriptions returned by the automation API (GET /v1/subscriptions)
35
+ */
36
+ parseSubscriptionsFromApi(records: ApiSubscriptionRecord[], options?: SubscriptionOptions): (Position.Automated | null)[];
33
37
  getSubscriptionsFor(addresses: EthereumAddress[], options?: SubscriptionOptions): Promise<(Position.Automated | null)[]>;
34
38
  }
35
39
  export {};
@@ -19,6 +19,7 @@ const utils_1 = require("../../services/utils");
19
19
  const contractService_1 = require("../../services/contractService");
20
20
  const ethereumService_1 = require("../../services/ethereumService");
21
21
  const strategiesService_1 = require("../../services/strategiesService");
22
+ const apiSubscriptionsService_1 = require("../../services/apiSubscriptionsService");
22
23
  const Automation_1 = __importDefault(require("./Automation"));
23
24
  class StrategiesAutomation extends Automation_1.default {
24
25
  constructor(args) {
@@ -126,9 +127,11 @@ class StrategiesAutomation extends Automation_1.default {
126
127
  if (mergePairIndex !== -1) {
127
128
  const mergePair = mergeExtension[mergePairIndex];
128
129
  mergeExtension.splice(mergePairIndex, 1);
129
- subscriptions.push(Object.assign(Object.assign(Object.assign({}, mergePair), current), {
130
+ subscriptions.push(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, mergePair), current), {
130
131
  // @ts-ignore
131
- blockNumber: decimal_js_1.default.max(mergePair.blockNumber, current.blockNumber).toNumber(), subIds: [current.subId, mergePair.subId], isEnabled: mergePair.isEnabled || current.isEnabled, specific: Object.assign(Object.assign({}, mergePair.specific), current.specific) }));
132
+ blockNumber: decimal_js_1.default.max(mergePair.blockNumber, current.blockNumber).toNumber(), subIds: [current.subId, mergePair.subId], isEnabled: mergePair.isEnabled || current.isEnabled }), (0, utils_1.addToObjectIf)((0, utils_1.isDefined)(current.status), {
133
+ status: mergePair.status === enums_1.SubscriptionStatus.Active ? enums_1.SubscriptionStatus.Active : current.status,
134
+ })), { specific: Object.assign(Object.assign({}, mergePair.specific), current.specific) }));
132
135
  }
133
136
  else {
134
137
  subscriptions.push(Object.assign(Object.assign({}, current), { subIds: [current.subId] }));
@@ -189,6 +192,20 @@ class StrategiesAutomation extends Automation_1.default {
189
192
  return this._getSubscriptions(undefined, options);
190
193
  });
191
194
  }
195
+ /**
196
+ * @description Parses subscriptions returned by the automation API (GET /v1/subscriptions)
197
+ */
198
+ parseSubscriptionsFromApi(records, options) {
199
+ const filtered = (options === null || options === void 0 ? void 0 : options.enabledOnly) ? records.filter((record) => record.is_enabled) : records;
200
+ let subscriptions = filtered.map((record) => {
201
+ const position = this.getParsedSubscriptions((0, apiSubscriptionsService_1.parseDataFromApiSubscription)(record, this.chainId));
202
+ return position && Object.assign(Object.assign({}, position), { status: record.status });
203
+ });
204
+ if (options === null || options === void 0 ? void 0 : options.mergeSubs) {
205
+ subscriptions = this.mergeSubs(subscriptions);
206
+ }
207
+ return (options === null || options === void 0 ? void 0 : options.unexpiredOnly) ? this.removeExpiredSubscriptions(subscriptions) : subscriptions;
208
+ }
192
209
  getSubscriptionsFor(addresses, options) {
193
210
  return __awaiter(this, void 0, void 0, function* () {
194
211
  return this._getSubscriptions(addresses, options);
@@ -693,4 +693,59 @@ describe('Feature: StrategiesAutomation.ts', () => {
693
693
  (0, chai_1.expect)(loneBoost === null || loneBoost === void 0 ? void 0 : loneBoost.subId).to.equal(1);
694
694
  });
695
695
  }));
696
+ describe('When testing StrategiesAutomation.parseSubscriptionsFromApi', () => {
697
+ const strategiesAutomation = new StrategiesAutomation_1.default({
698
+ chainId: enums_1.ChainId.Ethereum,
699
+ provider: Web3_1,
700
+ providerFork: null,
701
+ });
702
+ const owner = '0x9cB7E19861665366011899d74E75d4F2A419aEeD';
703
+ const subData = [
704
+ '0x0000000000000000000000000000000000000000000000001bc16d674ec80000',
705
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
706
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
707
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
708
+ ];
709
+ // Aave V3 leverage management: repay (bundle 8, ratio under) and boost (bundle 9, ratio over) for the same owner and market
710
+ const repay = {
711
+ id: 379,
712
+ wallet: owner,
713
+ wallet_type: 'safe',
714
+ is_enabled: true,
715
+ invalid: false,
716
+ status: enums_1.SubscriptionStatus.Active,
717
+ is_bundle: true,
718
+ strategy_or_bundle_id: 8,
719
+ strategy_ids: [34, 35],
720
+ sub_data_hash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
721
+ trigger_data: ['0000000000000000000000009cb7e19861665366011899d74e75d4f2a419aeed0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e00000000000000000000000000000000000000000000000019ac8532c27900000000000000000000000000000000000000000000000000000000000000000001'],
722
+ sub_data: subData,
723
+ block_number: 18015756,
724
+ additional_triggers: null,
725
+ };
726
+ const boost = Object.assign(Object.assign({}, repay), { id: 380, is_enabled: false, status: enums_1.SubscriptionStatus.Disabled, strategy_or_bundle_id: 9, sub_data_hash: '0x1111111111111111111111111111111111111111111111111111111111111111', trigger_data: ['0000000000000000000000009cb7e19861665366011899d74e75d4f2a419aeed0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e0000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000000000'] });
727
+ it('Given API records should return parsed positions carrying the API status', () => {
728
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([repay, boost]);
729
+ (0, chai_1.expect)(positions).to.have.length(2);
730
+ (0, chai_1.expect)(positions.map((p) => p === null || p === void 0 ? void 0 : p.subId)).to.eql([379, 380]);
731
+ (0, chai_1.expect)(positions.map((p) => p === null || p === void 0 ? void 0 : p.status)).to.eql([enums_1.SubscriptionStatus.Active, enums_1.SubscriptionStatus.Disabled]);
732
+ (0, chai_1.expect)(positions.map((p) => p === null || p === void 0 ? void 0 : p.isEnabled)).to.eql([true, false]);
733
+ });
734
+ it('Given mergeSubs option should merge the repay and boost pair and keep the active status', () => {
735
+ var _a, _b, _c;
736
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([repay, boost], { mergeSubs: true });
737
+ (0, chai_1.expect)(positions).to.have.length(1);
738
+ (0, chai_1.expect)((_a = positions[0]) === null || _a === void 0 ? void 0 : _a.subIds).to.eql([379, 380]);
739
+ (0, chai_1.expect)((_b = positions[0]) === null || _b === void 0 ? void 0 : _b.isEnabled).to.equal(true);
740
+ (0, chai_1.expect)((_c = positions[0]) === null || _c === void 0 ? void 0 : _c.status).to.equal(enums_1.SubscriptionStatus.Active);
741
+ });
742
+ it('Given enabledOnly option should drop disabled records before parsing', () => {
743
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([repay, boost], { enabledOnly: true });
744
+ (0, chai_1.expect)(positions.map((p) => p === null || p === void 0 ? void 0 : p.subId)).to.eql([379]);
745
+ });
746
+ it('Given a record of an unknown strategy should return null for it', () => {
747
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([Object.assign(Object.assign({}, repay), { strategy_or_bundle_id: 9999 })]);
748
+ (0, chai_1.expect)(positions).to.eql([null]);
749
+ });
750
+ });
696
751
  });
@@ -0,0 +1,7 @@
1
+ import type { ApiSubscriptionRecord, ParseData } from '../types';
2
+ import type { ChainId } from '../types/enums';
3
+ /**
4
+ * @description Maps a subscription record returned by the automation API to the data
5
+ * the parser expects from Subscribe events and SubStorage (trigger data is stored without 0x prefix)
6
+ */
7
+ export declare function parseDataFromApiSubscription(record: ApiSubscriptionRecord, chainId: ChainId): ParseData;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseDataFromApiSubscription = void 0;
4
+ function addHexPrefix(value) {
5
+ return value.startsWith('0x') ? value : `0x${value}`;
6
+ }
7
+ /**
8
+ * @description Maps a subscription record returned by the automation API to the data
9
+ * the parser expects from Subscribe events and SubStorage (trigger data is stored without 0x prefix)
10
+ */
11
+ function parseDataFromApiSubscription(record, chainId) {
12
+ return {
13
+ chainId,
14
+ blockNumber: record.block_number,
15
+ subscriptionEventData: {
16
+ subId: record.id.toString(),
17
+ proxy: record.wallet,
18
+ subHash: record.sub_data_hash,
19
+ subStruct: {
20
+ strategyOrBundleId: record.strategy_or_bundle_id.toString(),
21
+ isBundle: record.is_bundle,
22
+ triggerData: record.trigger_data.map(addHexPrefix),
23
+ subData: record.sub_data.map(addHexPrefix),
24
+ },
25
+ },
26
+ strategiesSubsData: {
27
+ userProxy: record.wallet,
28
+ isEnabled: record.is_enabled,
29
+ strategySubHash: record.sub_data_hash,
30
+ },
31
+ };
32
+ }
33
+ exports.parseDataFromApiSubscription = parseDataFromApiSubscription;
@@ -0,0 +1 @@
1
+ import '../configuration';
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const enums_1 = require("../types/enums");
5
+ require("../configuration");
6
+ const apiSubscriptionsService_1 = require("./apiSubscriptionsService");
7
+ const strategiesService_1 = require("./strategiesService");
8
+ describe('Feature: apiSubscriptionsService.ts', () => {
9
+ describe('When testing apiSubscriptionsService.parseDataFromApiSubscription', () => {
10
+ // same subscription as in strategiesService.test.ts, in the shape the automation API returns it
11
+ const record = {
12
+ id: 379,
13
+ wallet: '0x9cB7E19861665366011899d74E75d4F2A419aEeD',
14
+ wallet_type: 'safe',
15
+ is_enabled: true,
16
+ invalid: false,
17
+ status: enums_1.SubscriptionStatus.Active,
18
+ is_bundle: true,
19
+ strategy_or_bundle_id: 8,
20
+ strategy_ids: [34, 35],
21
+ sub_data_hash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
22
+ trigger_data: [
23
+ '0000000000000000000000009cb7e19861665366011899d74e75d4f2a419aeed0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e00000000000000000000000000000000000000000000000019ac8532c27900000000000000000000000000000000000000000000000000000000000000000001',
24
+ ],
25
+ sub_data: [
26
+ '0x0000000000000000000000000000000000000000000000001bc16d674ec80000',
27
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
28
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
29
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
30
+ ],
31
+ block_number: 18015756,
32
+ additional_triggers: null,
33
+ };
34
+ const fromEvents = {
35
+ chainId: enums_1.ChainId.Ethereum,
36
+ blockNumber: 18015756,
37
+ subscriptionEventData: {
38
+ subId: '379',
39
+ proxy: '0x9cB7E19861665366011899d74E75d4F2A419aEeD',
40
+ subHash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
41
+ subStruct: {
42
+ strategyOrBundleId: '8',
43
+ isBundle: true,
44
+ triggerData: [
45
+ '0x0000000000000000000000009cb7e19861665366011899d74e75d4f2a419aeed0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e00000000000000000000000000000000000000000000000019ac8532c27900000000000000000000000000000000000000000000000000000000000000000001',
46
+ ],
47
+ subData: record.sub_data,
48
+ },
49
+ },
50
+ strategiesSubsData: {
51
+ userProxy: '0x9cB7E19861665366011899d74E75d4F2A419aEeD',
52
+ isEnabled: true,
53
+ strategySubHash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
54
+ },
55
+ };
56
+ it('Given an API record should return the same parse data as Subscribe event and SubStorage would', () => {
57
+ (0, chai_1.expect)((0, apiSubscriptionsService_1.parseDataFromApiSubscription)(record, enums_1.ChainId.Ethereum)).to.eql(fromEvents);
58
+ });
59
+ it('Given an API record the parsed position should match the one parsed from events', () => {
60
+ const fromApi = (0, strategiesService_1.parseStrategiesAutomatedPosition)((0, apiSubscriptionsService_1.parseDataFromApiSubscription)(record, enums_1.ChainId.Ethereum));
61
+ (0, chai_1.expect)(fromApi).to.not.equal(null);
62
+ (0, chai_1.expect)(fromApi).to.eql((0, strategiesService_1.parseStrategiesAutomatedPosition)(fromEvents));
63
+ });
64
+ it('Given a disabled record should keep it disabled', () => {
65
+ const parseData = (0, apiSubscriptionsService_1.parseDataFromApiSubscription)(Object.assign(Object.assign({}, record), { is_enabled: false }), enums_1.ChainId.Ethereum);
66
+ (0, chai_1.expect)(parseData.strategiesSubsData.isEnabled).to.equal(false);
67
+ });
68
+ });
69
+ });
@@ -83,7 +83,6 @@ describe('Feature: strategiesService.ts', () => {
83
83
  subId: '379',
84
84
  proxy: '0x9cb7e19861665366011899d74e75d4f2a419aeed',
85
85
  subHash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
86
- // @ts-ignore
87
86
  subStruct: {
88
87
  strategyOrBundleId: '8',
89
88
  isBundle: true,
@@ -4,6 +4,12 @@ export declare enum ChainId {
4
4
  Arbitrum = 42161,
5
5
  Base = 8453
6
6
  }
7
+ export declare enum SubscriptionStatus {
8
+ Active = "active",
9
+ Disabled = "disabled",
10
+ Finished = "finished",
11
+ Invalid = "invalid"
12
+ }
7
13
  export declare enum RatioState {
8
14
  OVER = 0,
9
15
  UNDER = 1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Bundles = exports.Strategies = exports.ProtocolIdentifiers = exports.CloseToAssetType = exports.CloseStrategyType = exports.DebtActionType = exports.CollActionType = exports.BundleProtocols = exports.OrderType = exports.RatioState = exports.ChainId = void 0;
3
+ exports.Bundles = exports.Strategies = exports.ProtocolIdentifiers = exports.CloseToAssetType = exports.CloseStrategyType = exports.DebtActionType = exports.CollActionType = exports.BundleProtocols = exports.OrderType = exports.RatioState = exports.SubscriptionStatus = exports.ChainId = void 0;
4
4
  var ChainId;
5
5
  (function (ChainId) {
6
6
  ChainId[ChainId["Ethereum"] = 1] = "Ethereum";
@@ -8,6 +8,13 @@ var ChainId;
8
8
  ChainId[ChainId["Arbitrum"] = 42161] = "Arbitrum";
9
9
  ChainId[ChainId["Base"] = 8453] = "Base";
10
10
  })(ChainId = exports.ChainId || (exports.ChainId = {}));
11
+ var SubscriptionStatus;
12
+ (function (SubscriptionStatus) {
13
+ SubscriptionStatus["Active"] = "active";
14
+ SubscriptionStatus["Disabled"] = "disabled";
15
+ SubscriptionStatus["Finished"] = "finished";
16
+ SubscriptionStatus["Invalid"] = "invalid";
17
+ })(SubscriptionStatus = exports.SubscriptionStatus || (exports.SubscriptionStatus = {}));
11
18
  var RatioState;
12
19
  (function (RatioState) {
13
20
  RatioState[RatioState["OVER"] = 0] = "OVER";
@@ -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;
@@ -238,6 +255,8 @@ export declare namespace Position {
238
255
  subId: number;
239
256
  subIds?: number[];
240
257
  isEnabled?: boolean;
258
+ /** Set only for subscriptions parsed from the automation API */
259
+ status?: SubscriptionStatus;
241
260
  subHash: string;
242
261
  blockNumber: BlockNumber;
243
262
  protocol: Interfaces.Protocol;
@@ -291,10 +310,17 @@ export interface BundlesInfo {
291
310
  [ChainId.Base]: BaseBundleInfo;
292
311
  }
293
312
  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];
313
+ /** Fields of the Subscribe event that parsing relies on */
314
+ export interface SubscriptionEventData {
315
+ subId: string;
316
+ proxy: EthereumAddress;
317
+ subHash: string;
318
+ subStruct: StrategyModel.StrategySubStructOutputStruct;
319
+ }
294
320
  export interface ParseData {
295
321
  chainId: ChainId;
296
322
  blockNumber: BlockNumber;
297
- subscriptionEventData: Subscribe['returnValues'];
323
+ subscriptionEventData: SubscriptionEventData;
298
324
  strategiesSubsData: StrategyModel.StoredSubDataStructOutputStruct;
299
325
  }
300
326
  type MapToProtocolVersion<T> = {
@@ -1,6 +1,6 @@
1
1
  import type Web3 from 'web3';
2
2
  import type { PastEventOptions } from 'web3-eth-contract';
3
- import type { Position, Interfaces, EthereumAddress, SubscriptionOptions, Contract, ParseData, BlockNumber } from '../../types';
3
+ import type { Position, Interfaces, EthereumAddress, SubscriptionOptions, Contract, ParseData, BlockNumber, ApiSubscriptionRecord } from '../../types';
4
4
  import type { StrategyModel, Subscribe, SubStorage, UpdateData } from '../../types/contracts/generated/SubStorage';
5
5
  import type { ChainId } from '../../types/enums';
6
6
  import Automation from './Automation';
@@ -30,6 +30,10 @@ export default class StrategiesAutomation extends Automation {
30
30
  protected mergeSubs(_subscriptions: (Position.Automated | null)[]): Position.Automated[];
31
31
  protected _getSubscriptions(addresses?: EthereumAddress[], options?: SubscriptionOptions): Promise<(Position.Automated | null)[]>;
32
32
  getSubscriptions(options?: SubscriptionOptions): Promise<(Position.Automated | null)[]>;
33
+ /**
34
+ * @description Parses subscriptions returned by the automation API (GET /v1/subscriptions)
35
+ */
36
+ parseSubscriptionsFromApi(records: ApiSubscriptionRecord[], options?: SubscriptionOptions): (Position.Automated | null)[];
33
37
  getSubscriptionsFor(addresses: EthereumAddress[], options?: SubscriptionOptions): Promise<(Position.Automated | null)[]>;
34
38
  }
35
39
  export {};
@@ -9,11 +9,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import Dec from 'decimal.js';
11
11
  import PromisePool from 'es6-promise-pool';
12
- import { Strategies, ProtocolIdentifiers } from '../../types/enums';
12
+ import { Strategies, ProtocolIdentifiers, SubscriptionStatus } from '../../types/enums';
13
13
  import { addToObjectIf, isDefined, isUndefined } from '../../services/utils';
14
14
  import { getAbiItem, makeSubStorageContract } from '../../services/contractService';
15
15
  import { getEventsFromContract, multicall } from '../../services/ethereumService';
16
16
  import { parseStrategiesAutomatedPosition } from '../../services/strategiesService';
17
+ import { parseDataFromApiSubscription } from '../../services/apiSubscriptionsService';
17
18
  import Automation from './Automation';
18
19
  export default class StrategiesAutomation extends Automation {
19
20
  constructor(args) {
@@ -121,9 +122,11 @@ export default class StrategiesAutomation extends Automation {
121
122
  if (mergePairIndex !== -1) {
122
123
  const mergePair = mergeExtension[mergePairIndex];
123
124
  mergeExtension.splice(mergePairIndex, 1);
124
- subscriptions.push(Object.assign(Object.assign(Object.assign({}, mergePair), current), {
125
+ subscriptions.push(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, mergePair), current), {
125
126
  // @ts-ignore
126
- blockNumber: Dec.max(mergePair.blockNumber, current.blockNumber).toNumber(), subIds: [current.subId, mergePair.subId], isEnabled: mergePair.isEnabled || current.isEnabled, specific: Object.assign(Object.assign({}, mergePair.specific), current.specific) }));
127
+ blockNumber: Dec.max(mergePair.blockNumber, current.blockNumber).toNumber(), subIds: [current.subId, mergePair.subId], isEnabled: mergePair.isEnabled || current.isEnabled }), addToObjectIf(isDefined(current.status), {
128
+ status: mergePair.status === SubscriptionStatus.Active ? SubscriptionStatus.Active : current.status,
129
+ })), { specific: Object.assign(Object.assign({}, mergePair.specific), current.specific) }));
127
130
  }
128
131
  else {
129
132
  subscriptions.push(Object.assign(Object.assign({}, current), { subIds: [current.subId] }));
@@ -184,6 +187,20 @@ export default class StrategiesAutomation extends Automation {
184
187
  return this._getSubscriptions(undefined, options);
185
188
  });
186
189
  }
190
+ /**
191
+ * @description Parses subscriptions returned by the automation API (GET /v1/subscriptions)
192
+ */
193
+ parseSubscriptionsFromApi(records, options) {
194
+ const filtered = (options === null || options === void 0 ? void 0 : options.enabledOnly) ? records.filter((record) => record.is_enabled) : records;
195
+ let subscriptions = filtered.map((record) => {
196
+ const position = this.getParsedSubscriptions(parseDataFromApiSubscription(record, this.chainId));
197
+ return position && Object.assign(Object.assign({}, position), { status: record.status });
198
+ });
199
+ if (options === null || options === void 0 ? void 0 : options.mergeSubs) {
200
+ subscriptions = this.mergeSubs(subscriptions);
201
+ }
202
+ return (options === null || options === void 0 ? void 0 : options.unexpiredOnly) ? this.removeExpiredSubscriptions(subscriptions) : subscriptions;
203
+ }
187
204
  getSubscriptionsFor(addresses, options) {
188
205
  return __awaiter(this, void 0, void 0, function* () {
189
206
  return this._getSubscriptions(addresses, options);
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import Web3 from 'web3';
11
11
  import { expect } from 'chai';
12
- import { ChainId } from '../../types/enums';
12
+ import { ChainId, SubscriptionStatus } from '../../types/enums';
13
13
  import '../../configuration';
14
14
  import StrategiesAutomation from './StrategiesAutomation';
15
15
  require('dotenv').config({ path: '.env' });
@@ -688,4 +688,59 @@ describe('Feature: StrategiesAutomation.ts', () => {
688
688
  expect(loneBoost === null || loneBoost === void 0 ? void 0 : loneBoost.subId).to.equal(1);
689
689
  });
690
690
  }));
691
+ describe('When testing StrategiesAutomation.parseSubscriptionsFromApi', () => {
692
+ const strategiesAutomation = new StrategiesAutomation({
693
+ chainId: ChainId.Ethereum,
694
+ provider: Web3_1,
695
+ providerFork: null,
696
+ });
697
+ const owner = '0x9cB7E19861665366011899d74E75d4F2A419aEeD';
698
+ const subData = [
699
+ '0x0000000000000000000000000000000000000000000000001bc16d674ec80000',
700
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
701
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
702
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
703
+ ];
704
+ // Aave V3 leverage management: repay (bundle 8, ratio under) and boost (bundle 9, ratio over) for the same owner and market
705
+ const repay = {
706
+ id: 379,
707
+ wallet: owner,
708
+ wallet_type: 'safe',
709
+ is_enabled: true,
710
+ invalid: false,
711
+ status: SubscriptionStatus.Active,
712
+ is_bundle: true,
713
+ strategy_or_bundle_id: 8,
714
+ strategy_ids: [34, 35],
715
+ sub_data_hash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
716
+ trigger_data: ['0000000000000000000000009cb7e19861665366011899d74e75d4f2a419aeed0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e00000000000000000000000000000000000000000000000019ac8532c27900000000000000000000000000000000000000000000000000000000000000000001'],
717
+ sub_data: subData,
718
+ block_number: 18015756,
719
+ additional_triggers: null,
720
+ };
721
+ const boost = Object.assign(Object.assign({}, repay), { id: 380, is_enabled: false, status: SubscriptionStatus.Disabled, strategy_or_bundle_id: 9, sub_data_hash: '0x1111111111111111111111111111111111111111111111111111111111111111', trigger_data: ['0000000000000000000000009cb7e19861665366011899d74e75d4f2a419aeed0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e0000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000000000'] });
722
+ it('Given API records should return parsed positions carrying the API status', () => {
723
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([repay, boost]);
724
+ expect(positions).to.have.length(2);
725
+ expect(positions.map((p) => p === null || p === void 0 ? void 0 : p.subId)).to.eql([379, 380]);
726
+ expect(positions.map((p) => p === null || p === void 0 ? void 0 : p.status)).to.eql([SubscriptionStatus.Active, SubscriptionStatus.Disabled]);
727
+ expect(positions.map((p) => p === null || p === void 0 ? void 0 : p.isEnabled)).to.eql([true, false]);
728
+ });
729
+ it('Given mergeSubs option should merge the repay and boost pair and keep the active status', () => {
730
+ var _a, _b, _c;
731
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([repay, boost], { mergeSubs: true });
732
+ expect(positions).to.have.length(1);
733
+ expect((_a = positions[0]) === null || _a === void 0 ? void 0 : _a.subIds).to.eql([379, 380]);
734
+ expect((_b = positions[0]) === null || _b === void 0 ? void 0 : _b.isEnabled).to.equal(true);
735
+ expect((_c = positions[0]) === null || _c === void 0 ? void 0 : _c.status).to.equal(SubscriptionStatus.Active);
736
+ });
737
+ it('Given enabledOnly option should drop disabled records before parsing', () => {
738
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([repay, boost], { enabledOnly: true });
739
+ expect(positions.map((p) => p === null || p === void 0 ? void 0 : p.subId)).to.eql([379]);
740
+ });
741
+ it('Given a record of an unknown strategy should return null for it', () => {
742
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([Object.assign(Object.assign({}, repay), { strategy_or_bundle_id: 9999 })]);
743
+ expect(positions).to.eql([null]);
744
+ });
745
+ });
691
746
  });
@@ -0,0 +1,7 @@
1
+ import type { ApiSubscriptionRecord, ParseData } from '../types';
2
+ import type { ChainId } from '../types/enums';
3
+ /**
4
+ * @description Maps a subscription record returned by the automation API to the data
5
+ * the parser expects from Subscribe events and SubStorage (trigger data is stored without 0x prefix)
6
+ */
7
+ export declare function parseDataFromApiSubscription(record: ApiSubscriptionRecord, chainId: ChainId): ParseData;
@@ -0,0 +1,29 @@
1
+ function addHexPrefix(value) {
2
+ return value.startsWith('0x') ? value : `0x${value}`;
3
+ }
4
+ /**
5
+ * @description Maps a subscription record returned by the automation API to the data
6
+ * the parser expects from Subscribe events and SubStorage (trigger data is stored without 0x prefix)
7
+ */
8
+ export function parseDataFromApiSubscription(record, chainId) {
9
+ return {
10
+ chainId,
11
+ blockNumber: record.block_number,
12
+ subscriptionEventData: {
13
+ subId: record.id.toString(),
14
+ proxy: record.wallet,
15
+ subHash: record.sub_data_hash,
16
+ subStruct: {
17
+ strategyOrBundleId: record.strategy_or_bundle_id.toString(),
18
+ isBundle: record.is_bundle,
19
+ triggerData: record.trigger_data.map(addHexPrefix),
20
+ subData: record.sub_data.map(addHexPrefix),
21
+ },
22
+ },
23
+ strategiesSubsData: {
24
+ userProxy: record.wallet,
25
+ isEnabled: record.is_enabled,
26
+ strategySubHash: record.sub_data_hash,
27
+ },
28
+ };
29
+ }
@@ -0,0 +1 @@
1
+ import '../configuration';
@@ -0,0 +1,67 @@
1
+ import { expect } from 'chai';
2
+ import { ChainId, SubscriptionStatus } from '../types/enums';
3
+ import '../configuration';
4
+ import { parseDataFromApiSubscription } from './apiSubscriptionsService';
5
+ import { parseStrategiesAutomatedPosition } from './strategiesService';
6
+ describe('Feature: apiSubscriptionsService.ts', () => {
7
+ describe('When testing apiSubscriptionsService.parseDataFromApiSubscription', () => {
8
+ // same subscription as in strategiesService.test.ts, in the shape the automation API returns it
9
+ const record = {
10
+ id: 379,
11
+ wallet: '0x9cB7E19861665366011899d74E75d4F2A419aEeD',
12
+ wallet_type: 'safe',
13
+ is_enabled: true,
14
+ invalid: false,
15
+ status: SubscriptionStatus.Active,
16
+ is_bundle: true,
17
+ strategy_or_bundle_id: 8,
18
+ strategy_ids: [34, 35],
19
+ sub_data_hash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
20
+ trigger_data: [
21
+ '0000000000000000000000009cb7e19861665366011899d74e75d4f2a419aeed0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e00000000000000000000000000000000000000000000000019ac8532c27900000000000000000000000000000000000000000000000000000000000000000001',
22
+ ],
23
+ sub_data: [
24
+ '0x0000000000000000000000000000000000000000000000001bc16d674ec80000',
25
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
26
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
27
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
28
+ ],
29
+ block_number: 18015756,
30
+ additional_triggers: null,
31
+ };
32
+ const fromEvents = {
33
+ chainId: ChainId.Ethereum,
34
+ blockNumber: 18015756,
35
+ subscriptionEventData: {
36
+ subId: '379',
37
+ proxy: '0x9cB7E19861665366011899d74E75d4F2A419aEeD',
38
+ subHash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
39
+ subStruct: {
40
+ strategyOrBundleId: '8',
41
+ isBundle: true,
42
+ triggerData: [
43
+ '0x0000000000000000000000009cb7e19861665366011899d74e75d4f2a419aeed0000000000000000000000002f39d218133afab8f2b819b1066c7e434ad94e9e00000000000000000000000000000000000000000000000019ac8532c27900000000000000000000000000000000000000000000000000000000000000000001',
44
+ ],
45
+ subData: record.sub_data,
46
+ },
47
+ },
48
+ strategiesSubsData: {
49
+ userProxy: '0x9cB7E19861665366011899d74E75d4F2A419aEeD',
50
+ isEnabled: true,
51
+ strategySubHash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
52
+ },
53
+ };
54
+ it('Given an API record should return the same parse data as Subscribe event and SubStorage would', () => {
55
+ expect(parseDataFromApiSubscription(record, ChainId.Ethereum)).to.eql(fromEvents);
56
+ });
57
+ it('Given an API record the parsed position should match the one parsed from events', () => {
58
+ const fromApi = parseStrategiesAutomatedPosition(parseDataFromApiSubscription(record, ChainId.Ethereum));
59
+ expect(fromApi).to.not.equal(null);
60
+ expect(fromApi).to.eql(parseStrategiesAutomatedPosition(fromEvents));
61
+ });
62
+ it('Given a disabled record should keep it disabled', () => {
63
+ const parseData = parseDataFromApiSubscription(Object.assign(Object.assign({}, record), { is_enabled: false }), ChainId.Ethereum);
64
+ expect(parseData.strategiesSubsData.isEnabled).to.equal(false);
65
+ });
66
+ });
67
+ });
@@ -81,7 +81,6 @@ describe('Feature: strategiesService.ts', () => {
81
81
  subId: '379',
82
82
  proxy: '0x9cb7e19861665366011899d74e75d4f2a419aeed',
83
83
  subHash: '0xafa4d200be62f171b57b1ae0f4e8348d1ac3f6d0812ad6da74a2adae8037dde1',
84
- // @ts-ignore
85
84
  subStruct: {
86
85
  strategyOrBundleId: '8',
87
86
  isBundle: true,
@@ -4,6 +4,12 @@ export declare enum ChainId {
4
4
  Arbitrum = 42161,
5
5
  Base = 8453
6
6
  }
7
+ export declare enum SubscriptionStatus {
8
+ Active = "active",
9
+ Disabled = "disabled",
10
+ Finished = "finished",
11
+ Invalid = "invalid"
12
+ }
7
13
  export declare enum RatioState {
8
14
  OVER = 0,
9
15
  UNDER = 1
@@ -5,6 +5,13 @@ export var ChainId;
5
5
  ChainId[ChainId["Arbitrum"] = 42161] = "Arbitrum";
6
6
  ChainId[ChainId["Base"] = 8453] = "Base";
7
7
  })(ChainId || (ChainId = {}));
8
+ export var SubscriptionStatus;
9
+ (function (SubscriptionStatus) {
10
+ SubscriptionStatus["Active"] = "active";
11
+ SubscriptionStatus["Disabled"] = "disabled";
12
+ SubscriptionStatus["Finished"] = "finished";
13
+ SubscriptionStatus["Invalid"] = "invalid";
14
+ })(SubscriptionStatus || (SubscriptionStatus = {}));
8
15
  export var RatioState;
9
16
  (function (RatioState) {
10
17
  RatioState[RatioState["OVER"] = 0] = "OVER";
@@ -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;
@@ -238,6 +255,8 @@ export declare namespace Position {
238
255
  subId: number;
239
256
  subIds?: number[];
240
257
  isEnabled?: boolean;
258
+ /** Set only for subscriptions parsed from the automation API */
259
+ status?: SubscriptionStatus;
241
260
  subHash: string;
242
261
  blockNumber: BlockNumber;
243
262
  protocol: Interfaces.Protocol;
@@ -291,10 +310,17 @@ export interface BundlesInfo {
291
310
  [ChainId.Base]: BaseBundleInfo;
292
311
  }
293
312
  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];
313
+ /** Fields of the Subscribe event that parsing relies on */
314
+ export interface SubscriptionEventData {
315
+ subId: string;
316
+ proxy: EthereumAddress;
317
+ subHash: string;
318
+ subStruct: StrategyModel.StrategySubStructOutputStruct;
319
+ }
294
320
  export interface ParseData {
295
321
  chainId: ChainId;
296
322
  blockNumber: BlockNumber;
297
- subscriptionEventData: Subscribe['returnValues'];
323
+ subscriptionEventData: SubscriptionEventData;
298
324
  strategiesSubsData: StrategyModel.StoredSubDataStructOutputStruct;
299
325
  }
300
326
  type MapToProtocolVersion<T> = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defisaver/automation-sdk",
3
- "version": "3.3.18-spark-eoa-v2-dev",
3
+ "version": "3.3.19-sub-fetching-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,72 @@ 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 API status', () => {
747
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([repay, boost]);
748
+ expect(positions).to.have.length(2);
749
+ expect(positions.map((p) => p?.subId)).to.eql([379, 380]);
750
+ expect(positions.map((p) => p?.status)).to.eql([SubscriptionStatus.Active, SubscriptionStatus.Disabled]);
751
+ expect(positions.map((p) => p?.isEnabled)).to.eql([true, false]);
752
+ });
753
+
754
+ it('Given mergeSubs option should merge the repay and boost pair and keep the active status', () => {
755
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([repay, boost], { mergeSubs: true });
756
+ expect(positions).to.have.length(1);
757
+ expect(positions[0]?.subIds).to.eql([379, 380]);
758
+ expect(positions[0]?.isEnabled).to.equal(true);
759
+ expect(positions[0]?.status).to.equal(SubscriptionStatus.Active);
760
+ });
761
+
762
+ it('Given enabledOnly option should drop disabled records before parsing', () => {
763
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([repay, boost], { enabledOnly: true });
764
+ expect(positions.map((p) => p?.subId)).to.eql([379]);
765
+ });
766
+
767
+ it('Given a record of an unknown strategy should return null for it', () => {
768
+ const positions = strategiesAutomation.parseSubscriptionsFromApi([{ ...repay, strategy_or_bundle_id: 9999 }]);
769
+ expect(positions).to.eql([null]);
770
+ });
771
+ });
703
772
  });
@@ -4,17 +4,19 @@ 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,
10
11
  } from '../../types/contracts/generated/SubStorage';
11
12
  import type { ChainId } from '../../types/enums';
12
- import { Strategies, ProtocolIdentifiers } from '../../types/enums';
13
+ import { Strategies, ProtocolIdentifiers, SubscriptionStatus } from '../../types/enums';
13
14
 
14
15
  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
 
@@ -167,6 +169,10 @@ export default class StrategiesAutomation extends Automation {
167
169
  blockNumber: Dec.max(mergePair.blockNumber, current.blockNumber).toNumber(),
168
170
  subIds: [current.subId, mergePair.subId],
169
171
  isEnabled: mergePair.isEnabled || current.isEnabled,
172
+ // status is set only for subscriptions parsed from the automation API
173
+ ...addToObjectIf(isDefined(current.status), {
174
+ status: mergePair.status === SubscriptionStatus.Active ? SubscriptionStatus.Active : current.status,
175
+ }),
170
176
  specific: {
171
177
  ...mergePair.specific,
172
178
  ...current.specific,
@@ -259,6 +265,24 @@ export default class StrategiesAutomation extends Automation {
259
265
  return this._getSubscriptions(undefined, options);
260
266
  }
261
267
 
268
+ /**
269
+ * @description Parses subscriptions returned by the automation API (GET /v1/subscriptions)
270
+ */
271
+ public parseSubscriptionsFromApi(records: ApiSubscriptionRecord[], options?: SubscriptionOptions): (Position.Automated | null)[] {
272
+ const filtered = options?.enabledOnly ? records.filter((record) => record.is_enabled) : records;
273
+
274
+ let subscriptions: (Position.Automated | null)[] = filtered.map((record) => {
275
+ const position = this.getParsedSubscriptions(parseDataFromApiSubscription(record, this.chainId));
276
+ return position && { ...position, status: record.status };
277
+ });
278
+
279
+ if (options?.mergeSubs) {
280
+ subscriptions = this.mergeSubs(subscriptions);
281
+ }
282
+
283
+ return options?.unexpiredOnly ? this.removeExpiredSubscriptions(subscriptions) : subscriptions;
284
+ }
285
+
262
286
  public async getSubscriptionsFor(addresses: EthereumAddress[], options?: SubscriptionOptions): Promise<(Position.Automated | null)[]> {
263
287
  return this._getSubscriptions(addresses, options);
264
288
  }
@@ -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
+ }
@@ -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',
@@ -5,6 +5,13 @@ export enum ChainId {
5
5
  Base = 8453,
6
6
  }
7
7
 
8
+ export enum SubscriptionStatus {
9
+ Active = 'active',
10
+ Disabled = 'disabled',
11
+ Finished = 'finished',
12
+ Invalid = 'invalid',
13
+ }
14
+
8
15
  export enum RatioState {
9
16
  OVER = 0,
10
17
  UNDER = 1,
@@ -1,11 +1,12 @@
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';
4
+ import type { StrategyModel } from './contracts/generated/SubStorage';
5
5
  import type {
6
6
  ChainId, Strategies, Bundles, ProtocolIdentifiers,
7
7
  RatioState,
8
8
  CloseToAssetType,
9
+ SubscriptionStatus,
9
10
  } from './enums';
10
11
 
11
12
  export type PlaceholderType = any; // TODO - fix any types
@@ -72,6 +73,24 @@ interface _SubscriptionOptions {
72
73
 
73
74
  export type SubscriptionOptions = Partial<_SubscriptionOptions>;
74
75
 
76
+ /** Subscription record as returned by the automation API (GET /v1/subscriptions) */
77
+ export interface ApiSubscriptionRecord {
78
+ id: number,
79
+ wallet: EthereumAddress,
80
+ wallet_type: string,
81
+ is_enabled: boolean,
82
+ invalid: boolean,
83
+ status: SubscriptionStatus,
84
+ is_bundle: boolean,
85
+ strategy_or_bundle_id: number,
86
+ strategy_ids: number[],
87
+ sub_data_hash: string,
88
+ trigger_data: string[],
89
+ sub_data: string[],
90
+ block_number: number,
91
+ additional_triggers: string[] | null,
92
+ }
93
+
75
94
  export declare namespace Interfaces {
76
95
  interface ProtocolBase {
77
96
  id: ProtocolIdentifiers.StrategiesAutomation | ProtocolIdentifiers.LegacyAutomation,
@@ -301,6 +320,8 @@ export declare namespace Position {
301
320
  subId: number,
302
321
  subIds?: number[],
303
322
  isEnabled?: boolean,
323
+ /** Set only for subscriptions parsed from the automation API */
324
+ status?: SubscriptionStatus,
304
325
  subHash: string,
305
326
  blockNumber: BlockNumber,
306
327
  protocol: Interfaces.Protocol,
@@ -369,10 +390,18 @@ export type StrategyOrBundleIds =
369
390
  | typeof Bundles.ArbitrumIds[keyof typeof Bundles.ArbitrumIds]
370
391
  | typeof Bundles.BaseIds[keyof typeof Bundles.BaseIds];
371
392
 
393
+ /** Fields of the Subscribe event that parsing relies on */
394
+ export interface SubscriptionEventData {
395
+ subId: string,
396
+ proxy: EthereumAddress,
397
+ subHash: string,
398
+ subStruct: StrategyModel.StrategySubStructOutputStruct,
399
+ }
400
+
372
401
  export interface ParseData {
373
402
  chainId: ChainId,
374
403
  blockNumber: BlockNumber,
375
- subscriptionEventData: Subscribe['returnValues']
404
+ subscriptionEventData: SubscriptionEventData,
376
405
  strategiesSubsData: StrategyModel.StoredSubDataStructOutputStruct,
377
406
  }
378
407