@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.
- package/cjs/automation/private/StrategiesAutomation.d.ts +5 -1
- package/cjs/automation/private/StrategiesAutomation.js +29 -2
- package/cjs/automation/private/StrategiesAutomation.test.js +66 -0
- package/cjs/constants/index.js +35 -0
- package/cjs/services/apiSubscriptionsService.d.ts +7 -0
- package/cjs/services/apiSubscriptionsService.js +33 -0
- package/cjs/services/apiSubscriptionsService.test.d.ts +1 -0
- package/cjs/services/apiSubscriptionsService.test.js +69 -0
- package/cjs/services/ethereumService.test.js +4 -7
- package/cjs/services/strategiesService.js +40 -9
- package/cjs/services/strategiesService.test.js +0 -1
- package/cjs/services/strategySubService.d.ts +4 -0
- package/cjs/services/strategySubService.js +24 -0
- package/cjs/services/strategySubService.test.js +295 -0
- package/cjs/services/subDataService.d.ts +38 -0
- package/cjs/services/subDataService.js +76 -2
- package/cjs/services/subDataService.test.js +494 -4
- package/cjs/types/enums.d.ts +15 -2
- package/cjs/types/enums.js +15 -1
- package/cjs/types/index.d.ts +32 -3
- package/esm/automation/private/StrategiesAutomation.d.ts +5 -1
- package/esm/automation/private/StrategiesAutomation.js +29 -2
- package/esm/automation/private/StrategiesAutomation.test.js +67 -1
- package/esm/constants/index.js +35 -0
- package/esm/services/apiSubscriptionsService.d.ts +7 -0
- package/esm/services/apiSubscriptionsService.js +29 -0
- package/esm/services/apiSubscriptionsService.test.d.ts +1 -0
- package/esm/services/apiSubscriptionsService.test.js +67 -0
- package/esm/services/ethereumService.test.js +4 -7
- package/esm/services/strategiesService.js +40 -9
- package/esm/services/strategiesService.test.js +0 -1
- package/esm/services/strategySubService.d.ts +4 -0
- package/esm/services/strategySubService.js +24 -0
- package/esm/services/strategySubService.test.js +295 -0
- package/esm/services/subDataService.d.ts +38 -0
- package/esm/services/subDataService.js +74 -0
- package/esm/services/subDataService.test.js +494 -4
- package/esm/types/enums.d.ts +15 -2
- package/esm/types/enums.js +14 -0
- package/esm/types/index.d.ts +32 -3
- package/package.json +1 -1
- package/src/automation/private/StrategiesAutomation.test.ts +84 -1
- package/src/automation/private/StrategiesAutomation.ts +33 -0
- package/src/constants/index.ts +35 -0
- package/src/services/apiSubscriptionsService.test.ts +75 -0
- package/src/services/apiSubscriptionsService.ts +33 -0
- package/src/services/ethereumService.test.ts +5 -8
- package/src/services/strategiesService.test.ts +0 -1
- package/src/services/strategiesService.ts +43 -10
- package/src/services/strategySubService.test.ts +382 -0
- package/src/services/strategySubService.ts +87 -0
- package/src/services/subDataService.test.ts +608 -4
- package/src/services/subDataService.ts +107 -0
- package/src/types/enums.ts +14 -0
- package/src/types/index.ts +34 -2
package/esm/types/index.d.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
326
|
+
subscriptionEventData: SubscriptionEventData;
|
|
298
327
|
strategiesSubsData: StrategyModel.StoredSubDataStructOutputStruct;
|
|
299
328
|
}
|
|
300
329
|
type MapToProtocolVersion<T> = {
|
package/package.json
CHANGED
|
@@ -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
|
}
|
package/src/constants/index.ts
CHANGED
|
@@ -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
|
|
255
|
-
|
|
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,
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|