@defisaver/sdk 1.3.36 → 1.3.37-semi-continuous-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/README.md +55 -9
- package/docs/DEV.md +63 -0
- package/esm/src/Action.js +21 -13
- package/esm/src/ActionWithL2.js +4 -0
- package/esm/src/Recipe.js +5 -1
- package/package.json +2 -6
- package/src/Action.ts +18 -9
- package/src/ActionWithL2.ts +2 -0
- package/src/Recipe.ts +4 -1
- package/test/Action.js +83 -3
- package/test/ActionWithL2.js +65 -19
- package/test/Recipe.js +97 -0
- package/tsconfig.json +1 -5
- package/umd/index.js +24 -13
- package/ACTIONS.md +0 -1765
- package/DEV.md +0 -43
- package/docs/md/.nojekyll +0 -1
- package/docs/md/README.md +0 -12
- package/docs/md/classes/AaveBorrowAction.md +0 -458
- package/docs/md/classes/AaveClaimStkAaveAction.md +0 -455
- package/docs/md/classes/AaveCollateralSwitchAction.md +0 -455
- package/docs/md/classes/AavePaybackAction.md +0 -458
- package/docs/md/classes/AaveSupplyAction.md +0 -458
- package/docs/md/classes/AaveV2FlashLoanAction.md +0 -458
- package/docs/md/classes/AaveV2FlashLoanPaybackAction.md +0 -454
- package/docs/md/classes/AaveV3ATokenPaybackAction.md +0 -644
- package/docs/md/classes/AaveV3BorrowAction.md +0 -606
- package/docs/md/classes/AaveV3ClaimRewardsAction.md +0 -603
- package/docs/md/classes/AaveV3CollateralSwitchAction.md +0 -603
- package/docs/md/classes/AaveV3FlashLoanAction.md +0 -604
- package/docs/md/classes/AaveV3FlashLoanPaybackAction.md +0 -454
- package/docs/md/classes/AaveV3PaybackAction.md +0 -618
- package/docs/md/classes/AaveV3RatioCheckAction.md +0 -454
- package/docs/md/classes/AaveV3RatioTrigger.md +0 -454
- package/docs/md/classes/AaveV3SetEModeAction.md +0 -601
- package/docs/md/classes/AaveV3SupplyAction.md +0 -618
- package/docs/md/classes/AaveV3SwapBorrowRateModeAction.md +0 -602
- package/docs/md/classes/AaveV3WithdrawAction.md +0 -603
- package/docs/md/classes/AaveWithdrawAction.md +0 -456
- package/docs/md/classes/Action.md +0 -604
- package/docs/md/classes/ActionWithL2.md +0 -612
- package/docs/md/classes/AutomationV2Unsub.md +0 -452
- package/docs/md/classes/BalancerFlashLoanAction.md +0 -456
- package/docs/md/classes/BalancerFlashLoanPaybackAction.md +0 -454
- package/docs/md/classes/BalancerV2ClaimAction.md +0 -457
- package/docs/md/classes/BalancerV2SupplyAction.md +0 -458
- package/docs/md/classes/BalancerV2WithdrawAction.md +0 -470
- package/docs/md/classes/CBChickenInAction.md +0 -454
- package/docs/md/classes/CBChickenOutAction.md +0 -455
- package/docs/md/classes/CBCreateAction.md +0 -454
- package/docs/md/classes/CBRedeemAction.md +0 -456
- package/docs/md/classes/ChainLinkPriceTrigger.md +0 -453
- package/docs/md/classes/ChangeProxyOwnerAction.md +0 -453
- package/docs/md/classes/CompV3RatioTrigger.md +0 -454
- package/docs/md/classes/CompoundBorrowAction.md +0 -455
- package/docs/md/classes/CompoundClaimAction.md +0 -456
- package/docs/md/classes/CompoundCollateralSwitchAction.md +0 -454
- package/docs/md/classes/CompoundGetDebtAction.md +0 -452
- package/docs/md/classes/CompoundPaybackAction.md +0 -456
- package/docs/md/classes/CompoundRatioTrigger.md +0 -453
- package/docs/md/classes/CompoundSupplyAction.md +0 -456
- package/docs/md/classes/CompoundV3AllowAction.md +0 -455
- package/docs/md/classes/CompoundV3BorrowAction.md +0 -456
- package/docs/md/classes/CompoundV3ClaimAction.md +0 -456
- package/docs/md/classes/CompoundV3PaybackAction.md +0 -468
- package/docs/md/classes/CompoundV3RatioCheckAction.md +0 -456
- package/docs/md/classes/CompoundV3SupplyAction.md +0 -468
- package/docs/md/classes/CompoundV3TransferAction.md +0 -457
- package/docs/md/classes/CompoundV3WithdrawAction.md +0 -457
- package/docs/md/classes/CompoundWithdrawAction.md +0 -455
- package/docs/md/classes/ConvexClaimAction.md +0 -466
- package/docs/md/classes/ConvexDepositAction.md +0 -468
- package/docs/md/classes/ConvexWithdrawAction.md +0 -468
- package/docs/md/classes/CurveClaimFeesAction.md +0 -452
- package/docs/md/classes/CurveDepositAction.md +0 -467
- package/docs/md/classes/CurveGaugeDepositAction.md +0 -455
- package/docs/md/classes/CurveGaugeWithdrawAction.md +0 -454
- package/docs/md/classes/CurveMintCrvAction.md +0 -452
- package/docs/md/classes/CurveStethPoolDepositAction.md +0 -456
- package/docs/md/classes/CurveStethPoolWithdrawAction.md +0 -457
- package/docs/md/classes/CurveSwapAction.md +0 -457
- package/docs/md/classes/CurveWithdrawAction.md +0 -468
- package/docs/md/classes/DfsWeb3.md +0 -212
- package/docs/md/classes/DyDxFlashLoanAction.md +0 -456
- package/docs/md/classes/DyDxFlashLoanPaybackAction.md +0 -454
- package/docs/md/classes/DyDxWithdrawAction.md +0 -455
- package/docs/md/classes/EulerFlashLoanAction.md +0 -456
- package/docs/md/classes/EulerFlashLoanPaybackAction.md +0 -454
- package/docs/md/classes/GUniDeposit.md +0 -461
- package/docs/md/classes/GUniWithdraw.md +0 -458
- package/docs/md/classes/GasFeeAction.md +0 -454
- package/docs/md/classes/GasFeeActionL2.md +0 -455
- package/docs/md/classes/GasPriceTrigger.md +0 -35
- package/docs/md/classes/InstPullTokensAction.md +0 -454
- package/docs/md/classes/LidoStakeAction.md +0 -455
- package/docs/md/classes/LidoUnwrapAction.md +0 -455
- package/docs/md/classes/LidoWrapAction.md +0 -456
- package/docs/md/classes/LiquityBorrowAction.md +0 -457
- package/docs/md/classes/LiquityClaimAction.md +0 -453
- package/docs/md/classes/LiquityClaimSPRewardsAction.md +0 -454
- package/docs/md/classes/LiquityClaimStakingRewardsAction.md +0 -454
- package/docs/md/classes/LiquityCloseAction.md +0 -454
- package/docs/md/classes/LiquityEthGainToTroveAction.md +0 -455
- package/docs/md/classes/LiquityOpenAction.md +0 -459
- package/docs/md/classes/LiquityPaybackAction.md +0 -456
- package/docs/md/classes/LiquityRatioTrigger.md +0 -453
- package/docs/md/classes/LiquityRedeemAction.md +0 -461
- package/docs/md/classes/LiquitySPDepositAction.md +0 -456
- package/docs/md/classes/LiquitySPWithdrawAction.md +0 -458
- package/docs/md/classes/LiquityStakeAction.md +0 -456
- package/docs/md/classes/LiquitySupplyAction.md +0 -456
- package/docs/md/classes/LiquityUnstakeAction.md +0 -458
- package/docs/md/classes/LiquityWithdrawAction.md +0 -456
- package/docs/md/classes/MStableClaimAction.md +0 -456
- package/docs/md/classes/MStableDepositAction.md +0 -461
- package/docs/md/classes/MStableWithdrawAction.md +0 -461
- package/docs/md/classes/MakerClaimAction.md +0 -455
- package/docs/md/classes/MakerFlashLoanAction.md +0 -455
- package/docs/md/classes/MakerFlashLoanPaybackAction.md +0 -454
- package/docs/md/classes/MakerGenerateAction.md +0 -456
- package/docs/md/classes/MakerGiveAction.md +0 -456
- package/docs/md/classes/MakerMergeAction.md +0 -455
- package/docs/md/classes/MakerOpenVaultAction.md +0 -454
- package/docs/md/classes/MakerPaybackAction.md +0 -456
- package/docs/md/classes/MakerRatioAction.md +0 -453
- package/docs/md/classes/MakerRatioCheckAction.md +0 -457
- package/docs/md/classes/MakerRatioTrigger.md +0 -453
- package/docs/md/classes/MakerSupplyAction.md +0 -457
- package/docs/md/classes/MakerWithdrawAction.md +0 -457
- package/docs/md/classes/PullTokenAction.md +0 -455
- package/docs/md/classes/RariDepositAction.md +0 -458
- package/docs/md/classes/RariWithdrawAction.md +0 -459
- package/docs/md/classes/Recipe.md +0 -195
- package/docs/md/classes/ReflexerGenerateAction.md +0 -455
- package/docs/md/classes/ReflexerNativeUniV2SaviourDepositAction.md +0 -455
- package/docs/md/classes/ReflexerNativeUniV2SaviourGetReservesAction.md +0 -454
- package/docs/md/classes/ReflexerNativeUniV2SaviourWithdrawAction.md +0 -455
- package/docs/md/classes/ReflexerOpenSafeAction.md +0 -453
- package/docs/md/classes/ReflexerPaybackAction.md +0 -455
- package/docs/md/classes/ReflexerRatioTrigger.md +0 -453
- package/docs/md/classes/ReflexerSupplyAction.md +0 -456
- package/docs/md/classes/ReflexerWithdrawAction.md +0 -456
- package/docs/md/classes/SellAction.md +0 -613
- package/docs/md/classes/SendTokenAction.md +0 -467
- package/docs/md/classes/SendTokenAndUnwrapAction.md +0 -455
- package/docs/md/classes/Strategy.md +0 -195
- package/docs/md/classes/SubInputsAction.md +0 -454
- package/docs/md/classes/SumInputsAction.md +0 -454
- package/docs/md/classes/TimestampTrigger.md +0 -451
- package/docs/md/classes/ToggleSubAction.md +0 -454
- package/docs/md/classes/TokenBalanceAction.md +0 -452
- package/docs/md/classes/TrailingStopTrigger.md +0 -453
- package/docs/md/classes/TransferNFTAction.md +0 -454
- package/docs/md/classes/UniV3CurrentTickTrigger.md +0 -452
- package/docs/md/classes/UniswapSupplyAction.md +0 -461
- package/docs/md/classes/UniswapV3CollectAction.md +0 -614
- package/docs/md/classes/UniswapV3CreatePoolAction.md +0 -611
- package/docs/md/classes/UniswapV3MintAction.md +0 -610
- package/docs/md/classes/UniswapV3SupplyAction.md +0 -63
- package/docs/md/classes/UniswapV3WithdrawAction.md +0 -618
- package/docs/md/classes/UniswapWithdrawAction.md +0 -460
- package/docs/md/classes/UnwrapEthAction.md +0 -600
- package/docs/md/classes/UpdateSubAction.md +0 -454
- package/docs/md/classes/WrapEthAction.md +0 -599
- package/docs/md/classes/YearnSupplyAction.md +0 -456
- package/docs/md/classes/YearnWithdrawAction.md +0 -456
- package/docs/md/modules.md +0 -814
package/README.md
CHANGED
|
@@ -1,16 +1,62 @@
|
|
|
1
|
-
# DeFi Saver
|
|
1
|
+
# DeFi Saver SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Build actions and combine them into recipes for DeFi Saver smart wallets.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
```sh
|
|
8
|
+
npm install @defisaver/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Example
|
|
12
|
+
|
|
13
|
+
Encode a recipe that wraps 0.1 ETH into WETH on Ethereum:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
const { configure, Recipe, actions } = require('@defisaver/sdk');
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
configure({ chainId: 1, testingMode: false });
|
|
20
|
+
|
|
21
|
+
const recipe = new Recipe('Wrap ETH', [
|
|
22
|
+
new actions.basic.WrapEthAction('100000000000000000'),
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const [target, data] = recipe.encodeForDsProxyCall();
|
|
26
|
+
const value = await recipe.getEthValue();
|
|
27
|
+
console.log({ target, data, value });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
main().catch(console.error);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Amounts use token base units as decimal strings. Here, `value` is the ETH amount in wei. `target` and `data` are arguments for the smart wallet's `execute(address,bytes)` call; encoding does not submit a transaction.
|
|
34
|
+
|
|
35
|
+
- [Developer guide](docs/DEV.md): recipes, approvals, configuration, and contributing.
|
|
36
|
+
- [Action implementations](src/actions): current constructors and parameter documentation, grouped by protocol.
|
|
37
|
+
- [DeFi Saver documentation](https://docs.defisaver.com/): product and protocol guides.
|
|
11
38
|
|
|
12
39
|
## Testing
|
|
13
40
|
|
|
14
|
-
|
|
41
|
+
From a local checkout, install dependencies with `npm ci`, then run:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
# Run offline tests (no RPC required)
|
|
45
|
+
npm run test:unit
|
|
46
|
+
|
|
47
|
+
# Run all tests, including RPC-dependent wallet tests
|
|
48
|
+
npm test
|
|
49
|
+
|
|
50
|
+
# Run a single test file (test/Recipe.js)
|
|
51
|
+
npm run test-single --name=Recipe
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The full suite requires `RPC` in your environment or a local `.env` file for the wallet tests. Point it at a local or forked test chain.
|
|
55
|
+
|
|
56
|
+
## Building
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
npm run build
|
|
60
|
+
```
|
|
15
61
|
|
|
16
|
-
|
|
62
|
+
Builds the package and runs lint with automatic fixes. To check lint without changing files, use `npm run lint-check`.
|
package/docs/DEV.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Developer guide
|
|
2
|
+
|
|
3
|
+
The SDK models a transaction as an `Action`, or a sequence of actions as a `Recipe`. Action constructors describe contract inputs; the shared classes handle encoding and composition. See the [README](../README.md) for installation and a minimal example.
|
|
4
|
+
|
|
5
|
+
## Configuration and amounts
|
|
6
|
+
|
|
7
|
+
Call `configure({ chainId: 1, testingMode: false })` before constructing actions or recipes. Addresses are resolved during construction. Configuration is global, including the token library's network: avoid switching chains while working with existing objects or concurrent operations.
|
|
8
|
+
|
|
9
|
+
Use decimal strings in token base units: `'1000000'` represents 1 token with 6 decimals, and `'1000000000000000000'` represents 1 token with 18 decimals. Avoid JavaScript numbers for large amounts. Special values such as a maximum amount are action-specific; check the constructor documentation.
|
|
10
|
+
|
|
11
|
+
Supported networks are exported as `networks`. Use `getAddr(name, chainId)` to look up a deployment; it throws when the chain or named deployment is unavailable. Network support does not imply every action is deployed there.
|
|
12
|
+
|
|
13
|
+
## Composing a recipe
|
|
14
|
+
|
|
15
|
+
This example encodes pulling 1 WETH from an owner into the wallet and returning the amount received to that owner. Replace the example owner with your address when integrating it.
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
const { configure, Recipe, actions } = require('@defisaver/sdk');
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
configure({ chainId: 1, testingMode: false });
|
|
22
|
+
|
|
23
|
+
const owner = '0x0000000000000000000000000000000000000001';
|
|
24
|
+
const weth = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
|
|
25
|
+
const recipe = new Recipe('Pull and return WETH', [
|
|
26
|
+
new actions.basic.PullTokenAction(weth, owner, '1000000000000000000'),
|
|
27
|
+
new actions.basic.SendTokenAction(weth, owner, '$1'),
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const approvals = await recipe.getAssetsToApprove();
|
|
31
|
+
const value = await recipe.getEthValue();
|
|
32
|
+
const [target, data] = recipe.encodeForDsProxyCall();
|
|
33
|
+
console.log({ approvals, target, data, value });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
main().catch(console.error);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`'$1'` refers to the first action's return value, with numbering starting at **1**. References must point to an earlier action and be used in an input that supports mapping. A return value's meaning depends on the action; it is not necessarily a token amount. Nested inputs can have a custom mapping order, defined by the action's `mappableArgs`.
|
|
40
|
+
|
|
41
|
+
`getAssetsToApprove()` describes assets and owners requiring approval. For the ERC-20 example above, the owner approves the executing smart wallet to spend WETH before execution. The returned metadata does not perform approvals or check existing allowances. NFT approvals use different fields; inspect the returned type when supporting them.
|
|
42
|
+
|
|
43
|
+
For a DSProxy, pass `target` and `data` to its `execute(address,bytes)` method and attach `value` to that transaction. Other wallet integrations must use their appropriate execution path. Do not treat the returned target as a standalone user transaction destination.
|
|
44
|
+
|
|
45
|
+
A single action also exposes `encodeForDsProxyCall()`, `getAssetsToApprove()`, and `getEthValue()`. `ActionWithL2` supports specialized packed calldata; callers should normally use the public encoding methods rather than pack bytes themselves.
|
|
46
|
+
|
|
47
|
+
## Strategies
|
|
48
|
+
|
|
49
|
+
`Strategy` describes reusable automation: add subscription slots with `addSubSlot`, triggers with `addTrigger`, and actions with `addAction`. Strategy inputs can refer to named subscription slots such as `'&vaultId'`, or the special addresses `'&proxy'` and `'&eoa'`. Action return references still use `'$1'`, `'$2'`, and so on.
|
|
50
|
+
|
|
51
|
+
Strategy encoding is separate from recipe execution. See the [strategy example](../test/Strategy.js) and [implementation](../src/Strategy.ts) for the current API.
|
|
52
|
+
|
|
53
|
+
## Adding an action
|
|
54
|
+
|
|
55
|
+
1. Add a class under the relevant [protocol directory](../src/actions), extending `Action` or `ActionWithL2`.
|
|
56
|
+
2. Match the contract's action name, ABI types, and argument order in the `super` call. Define `mappableArgs` in the order expected by the contract.
|
|
57
|
+
3. Implement approval metadata and ETH value when needed. Implement packed L2 encoding only when the contract supports it.
|
|
58
|
+
4. Export the class through the protocol index and add its deployments to [addresses.ts](../src/addresses.ts).
|
|
59
|
+
5. Add tests for encoded values, mappings, approvals, and ETH value, including relevant boundary cases. Keep constructor parameter documentation beside the implementation.
|
|
60
|
+
|
|
61
|
+
[PullTokenAction](../src/actions/basic/PullTokenAction.ts) is a small example. Use existing actions for structure, and the corresponding contract as the source of truth for input semantics.
|
|
62
|
+
|
|
63
|
+
See the [README](../README.md#testing) for testing commands.
|
package/esm/src/Action.js
CHANGED
|
@@ -12,7 +12,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
12
12
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
13
13
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
14
14
|
};
|
|
15
|
-
var _Action_instances, _Action__getArgumentMappingWithSlots, _Action__getPlaceholderForType;
|
|
15
|
+
var _Action_instances, _Action__getReturnValueReference, _Action__getArgumentMappingWithSlots, _Action__getPlaceholderForType;
|
|
16
16
|
import AbiCoder from 'web3-eth-abi';
|
|
17
17
|
import { keccak256, padLeft, toHex } from 'web3-utils';
|
|
18
18
|
import { CONFIG } from './config';
|
|
@@ -52,12 +52,12 @@ export class Action {
|
|
|
52
52
|
*/
|
|
53
53
|
_getArgumentMapping() {
|
|
54
54
|
return this.mappableArgs.map(arg => {
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
throw TypeError('Input can\'t be mapped to arrays (tuples/structs). Specify `mappableArgs` array in constructor.');
|
|
58
|
-
// eslint-disable-next-line
|
|
59
|
-
return parseInt(arg.substr(1));
|
|
55
|
+
if (Array.isArray(arg) && /\$\d+/.test(arg.toString())) {
|
|
56
|
+
throw TypeError('Input can\'t be mapped to arrays (tuples/structs). Specify `mappableArgs` array in constructor.');
|
|
60
57
|
}
|
|
58
|
+
const reference = __classPrivateFieldGet(this, _Action_instances, "m", _Action__getReturnValueReference).call(this, arg);
|
|
59
|
+
if (reference !== undefined)
|
|
60
|
+
return reference;
|
|
61
61
|
return 0;
|
|
62
62
|
});
|
|
63
63
|
}
|
|
@@ -100,7 +100,7 @@ export class Action {
|
|
|
100
100
|
if (Array.isArray(arg))
|
|
101
101
|
return arg.map((_arg, i) => this._replaceWithPlaceholders(_arg, paramTypeParsed[i]));
|
|
102
102
|
if (typeof (paramType) === 'string') {
|
|
103
|
-
if (
|
|
103
|
+
if (__classPrivateFieldGet(this, _Action_instances, "m", _Action__getReturnValueReference).call(this, arg) !== undefined)
|
|
104
104
|
return __classPrivateFieldGet(this, _Action_instances, "m", _Action__getPlaceholderForType).call(this, paramType);
|
|
105
105
|
if (new RegExp(/&\w+/).test(arg))
|
|
106
106
|
return __classPrivateFieldGet(this, _Action_instances, "m", _Action__getPlaceholderForType).call(this, paramType);
|
|
@@ -196,14 +196,22 @@ export class Action {
|
|
|
196
196
|
}));
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
-
_Action_instances = new WeakSet(),
|
|
199
|
+
_Action_instances = new WeakSet(), _Action__getReturnValueReference = function _Action__getReturnValueReference(arg) {
|
|
200
|
+
if (typeof arg !== 'string' || !/\$\d+/.test(arg))
|
|
201
|
+
return undefined;
|
|
202
|
+
const index = Number(arg.slice(1));
|
|
203
|
+
if (!/^\$\d+$/.test(arg) || !Number.isSafeInteger(index) || index < 1) {
|
|
204
|
+
throw new Error(`Invalid action return reference: ${arg}`);
|
|
205
|
+
}
|
|
206
|
+
return index;
|
|
207
|
+
}, _Action__getArgumentMappingWithSlots = function _Action__getArgumentMappingWithSlots(subSlots) {
|
|
200
208
|
return this.mappableArgs.map(arg => {
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
throw TypeError('Input can\'t be mapped to arrays (tuples/structs). Specify `mappableArgs` array in constructor.');
|
|
204
|
-
// eslint-disable-next-line
|
|
205
|
-
return parseInt(arg.substr(1));
|
|
209
|
+
if (Array.isArray(arg) && /\$\d+/.test(arg.toString())) {
|
|
210
|
+
throw TypeError('Input can\'t be mapped to arrays (tuples/structs). Specify `mappableArgs` array in constructor.');
|
|
206
211
|
}
|
|
212
|
+
const reference = __classPrivateFieldGet(this, _Action_instances, "m", _Action__getReturnValueReference).call(this, arg);
|
|
213
|
+
if (reference !== undefined)
|
|
214
|
+
return reference;
|
|
207
215
|
// Handle SubSlots placeholder values in strategies
|
|
208
216
|
if (new RegExp(/&\w+/).test(arg)) {
|
|
209
217
|
if (arg === '&proxy')
|
package/esm/src/ActionWithL2.js
CHANGED
|
@@ -34,10 +34,14 @@ export class ActionWithL2 extends Action {
|
|
|
34
34
|
}
|
|
35
35
|
numberToBytes2(number) {
|
|
36
36
|
const hexNumber = number.toString(16);
|
|
37
|
+
if (!/^[0-9a-f]{1,4}$/i.test(hexNumber))
|
|
38
|
+
throw new RangeError('Value must fit in an unsigned 2-byte integer');
|
|
37
39
|
return hexNumber.padStart(4, '0');
|
|
38
40
|
}
|
|
39
41
|
numberToBytes1(number) {
|
|
40
42
|
const hexNumber = number.toString(16);
|
|
43
|
+
if (!/^[0-9a-f]{1,2}$/i.test(hexNumber))
|
|
44
|
+
throw new RangeError('Value must fit in an unsigned 1-byte integer');
|
|
41
45
|
return hexNumber.padStart(2, '0');
|
|
42
46
|
}
|
|
43
47
|
numberToBytes32(number) {
|
package/esm/src/Recipe.js
CHANGED
|
@@ -146,7 +146,9 @@ export class Recipe {
|
|
|
146
146
|
for (const pairsPerAction of assetOwnerPairs) {
|
|
147
147
|
for (const pair of pairsPerAction) {
|
|
148
148
|
const isNft = !pair.asset;
|
|
149
|
-
if (!uniqueAssetOwnerPairs.find(_pair => _pair.owner === pair.owner && (isNft
|
|
149
|
+
if (!uniqueAssetOwnerPairs.find(_pair => _pair.owner === pair.owner && (isNft
|
|
150
|
+
? 'nft' in _pair && _pair.nft === pair.nft && _pair.tokenId === pair.tokenId
|
|
151
|
+
: _pair.asset === pair.asset))) {
|
|
150
152
|
if (isNft) {
|
|
151
153
|
uniqueAssetOwnerPairs.push({
|
|
152
154
|
owner: pair.owner,
|
|
@@ -196,6 +198,8 @@ export class Recipe {
|
|
|
196
198
|
}
|
|
197
199
|
}
|
|
198
200
|
_Recipe_instances = new WeakSet(), _Recipe__encodeForCall = function _Recipe__encodeForCall() {
|
|
201
|
+
if (this.actions.length === 0)
|
|
202
|
+
throw new Error('Cannot encode an empty recipe');
|
|
199
203
|
const encoded = this.actions.map(action => action.encodeForRecipe());
|
|
200
204
|
const transposed = encoded[0].map((_, colIndex) => encoded.map(row => row[colIndex]));
|
|
201
205
|
const taskStruct = [
|
package/package.json
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defisaver/sdk",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.37-semi-continuous-dev",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./umd/index.js",
|
|
6
6
|
"module": "./esm/src/index.js",
|
|
7
7
|
"types": "./esm/src/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"test": "mocha 'test/**'",
|
|
10
|
+
"test:unit": "mocha 'test/**/*.js' --exclude test/DfsWeb3.js",
|
|
10
11
|
"test-single": "mocha ./test/$npm_config_name.js",
|
|
11
12
|
"version-bump": "git commit -am \"Version bump to $(npm version patch | cut -c 2-)\"",
|
|
12
13
|
"build:esm": "rm -rf esm && tsc -p tsconfig.esm.json",
|
|
13
14
|
"build:umd": "rm -rf umd && webpack --config=webpack.umd.js",
|
|
14
15
|
"build": "npm run lint && npm run build:umd && npm run build:esm",
|
|
15
|
-
"documentMD": "npx typedoc --plugin typedoc-plugin-markdown --out docs/md",
|
|
16
|
-
"document": "npx typedoc --out docs/default",
|
|
17
16
|
"lint": "eslint . --ext .ts --fix",
|
|
18
17
|
"lint-check": "eslint .",
|
|
19
18
|
"build-test": "npm run build && npm run test"
|
|
@@ -74,11 +73,8 @@
|
|
|
74
73
|
"chai": "^4.2.0",
|
|
75
74
|
"dotenv": "^10.0.0",
|
|
76
75
|
"eslint": "^8.57.0",
|
|
77
|
-
"jsdoc-to-markdown": "^9.1.3",
|
|
78
76
|
"mocha": "^11.8.0",
|
|
79
77
|
"ts-node": "^10.9.1",
|
|
80
|
-
"typedoc": "^0.23.20",
|
|
81
|
-
"typedoc-plugin-markdown": "^3.13.6",
|
|
82
78
|
"typescript": "^4.8.4",
|
|
83
79
|
"webpack": "^5.91.0",
|
|
84
80
|
"webpack-cli": "^4.10.0"
|
package/src/Action.ts
CHANGED
|
@@ -49,16 +49,25 @@ export class Action {
|
|
|
49
49
|
return keccak256(this.name).substr(0, 10);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
#_getReturnValueReference(arg: unknown): number | undefined {
|
|
53
|
+
if (typeof arg !== 'string' || !/\$\d+/.test(arg)) return undefined;
|
|
54
|
+
const index = Number(arg.slice(1));
|
|
55
|
+
if (!/^\$\d+$/.test(arg) || !Number.isSafeInteger(index) || index < 1) {
|
|
56
|
+
throw new Error(`Invalid action return reference: ${arg}`);
|
|
57
|
+
}
|
|
58
|
+
return index;
|
|
59
|
+
}
|
|
60
|
+
|
|
52
61
|
/**
|
|
53
62
|
*
|
|
54
63
|
*/
|
|
55
64
|
#_getArgumentMappingWithSlots(subSlots: Args) : Array<number> {
|
|
56
65
|
return this.mappableArgs.map(arg => {
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
// eslint-disable-next-line
|
|
60
|
-
return parseInt(arg.substr(1));
|
|
66
|
+
if (Array.isArray(arg) && /\$\d+/.test(arg.toString())) {
|
|
67
|
+
throw TypeError('Input can\'t be mapped to arrays (tuples/structs). Specify `mappableArgs` array in constructor.');
|
|
61
68
|
}
|
|
69
|
+
const reference = this.#_getReturnValueReference(arg);
|
|
70
|
+
if (reference !== undefined) return reference;
|
|
62
71
|
|
|
63
72
|
// Handle SubSlots placeholder values in strategies
|
|
64
73
|
if (new RegExp(/&\w+/).test(arg)) {
|
|
@@ -77,11 +86,11 @@ export class Action {
|
|
|
77
86
|
*/
|
|
78
87
|
_getArgumentMapping() : Args {
|
|
79
88
|
return this.mappableArgs.map(arg => {
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
// eslint-disable-next-line
|
|
83
|
-
return parseInt(arg.substr(1));
|
|
89
|
+
if (Array.isArray(arg) && /\$\d+/.test(arg.toString())) {
|
|
90
|
+
throw TypeError('Input can\'t be mapped to arrays (tuples/structs). Specify `mappableArgs` array in constructor.');
|
|
84
91
|
}
|
|
92
|
+
const reference = this.#_getReturnValueReference(arg);
|
|
93
|
+
if (reference !== undefined) return reference;
|
|
85
94
|
return 0;
|
|
86
95
|
});
|
|
87
96
|
}
|
|
@@ -136,7 +145,7 @@ export class Action {
|
|
|
136
145
|
const paramTypeParsed = this._parseParamType(paramType, arg);
|
|
137
146
|
if (Array.isArray(arg)) return arg.map((_arg, i) => this._replaceWithPlaceholders(_arg, paramTypeParsed[i]));
|
|
138
147
|
if (typeof (paramType) === 'string') {
|
|
139
|
-
if (
|
|
148
|
+
if (this.#_getReturnValueReference(arg) !== undefined) return this.#_getPlaceholderForType(paramType);
|
|
140
149
|
if (new RegExp(/&\w+/).test(arg)) return this.#_getPlaceholderForType(paramType);
|
|
141
150
|
}
|
|
142
151
|
return arg;
|
package/src/ActionWithL2.ts
CHANGED
|
@@ -31,11 +31,13 @@ export class ActionWithL2 extends Action {
|
|
|
31
31
|
|
|
32
32
|
numberToBytes2(number:number) {
|
|
33
33
|
const hexNumber = number.toString(16);
|
|
34
|
+
if (!/^[0-9a-f]{1,4}$/i.test(hexNumber)) throw new RangeError('Value must fit in an unsigned 2-byte integer');
|
|
34
35
|
return hexNumber.padStart(4, '0');
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
numberToBytes1(number:number) {
|
|
38
39
|
const hexNumber = number.toString(16);
|
|
40
|
+
if (!/^[0-9a-f]{1,2}$/i.test(hexNumber)) throw new RangeError('Value must fit in an unsigned 1-byte integer');
|
|
39
41
|
return hexNumber.padStart(2, '0');
|
|
40
42
|
}
|
|
41
43
|
|
package/src/Recipe.ts
CHANGED
|
@@ -57,6 +57,7 @@ export class Recipe {
|
|
|
57
57
|
*
|
|
58
58
|
*/
|
|
59
59
|
#_encodeForCall(): Array<string | Array<any>> {
|
|
60
|
+
if (this.actions.length === 0) throw new Error('Cannot encode an empty recipe');
|
|
60
61
|
const encoded = this.actions.map(action => action.encodeForRecipe());
|
|
61
62
|
const transposed = encoded[0].map((_, colIndex) => encoded.map(row => row[colIndex]));
|
|
62
63
|
const taskStruct = [
|
|
@@ -170,7 +171,9 @@ export class Recipe {
|
|
|
170
171
|
for (const pairsPerAction of assetOwnerPairs) {
|
|
171
172
|
for (const pair of pairsPerAction) {
|
|
172
173
|
const isNft = !pair.asset;
|
|
173
|
-
if (!uniqueAssetOwnerPairs.find(_pair => _pair.owner === pair.owner && (isNft
|
|
174
|
+
if (!uniqueAssetOwnerPairs.find(_pair => _pair.owner === pair.owner && (isNft
|
|
175
|
+
? 'nft' in _pair && _pair.nft === pair.nft && _pair.tokenId === pair.tokenId
|
|
176
|
+
: _pair.asset === pair.asset))) {
|
|
174
177
|
if (isNft) {
|
|
175
178
|
uniqueAssetOwnerPairs.push({
|
|
176
179
|
owner: pair.owner!,
|
package/test/Action.js
CHANGED
|
@@ -39,7 +39,8 @@ describe('Action', () => {
|
|
|
39
39
|
let action;
|
|
40
40
|
it('Constructor', () => {
|
|
41
41
|
action = new dfs.Action('MockSwap', '0x0a80C3C540eEF99811f4579fa7b1A0617294e06f', ['uint256', 'address'], ['$1', '0x6b175474e89094c44da98b954eedeac495271d0f']);
|
|
42
|
-
action.mappableArgs = [action.args[0]
|
|
42
|
+
action.mappableArgs = [action.args[0], action.args[1]];
|
|
43
|
+
assert.deepEqual(action.encodeForRecipe()[3], [1, 0]);
|
|
43
44
|
})
|
|
44
45
|
it('encodeForDsProxyCall', () => encodeForDsProxyCall(action));
|
|
45
46
|
it('encodeForRecipe', () => encodeForRecipe(action));
|
|
@@ -55,8 +56,87 @@ describe('Action', () => {
|
|
|
55
56
|
assert.throws(() => encodeForRecipe(action));
|
|
56
57
|
});
|
|
57
58
|
it('encodeForRecipe with custom mappableArgs', () => {
|
|
58
|
-
action.mappableArgs = [action.args[0][0]
|
|
59
|
-
encodeForRecipe(
|
|
59
|
+
action.mappableArgs = [action.args[0][0], action.args[1]];
|
|
60
|
+
assert.deepEqual(action.encodeForRecipe()[3], [1, 0]);
|
|
60
61
|
});
|
|
61
62
|
})
|
|
62
63
|
})
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
describe('Action encoding', () => {
|
|
67
|
+
const AbiCoder = require('web3-eth-abi');
|
|
68
|
+
const target = '0x0000000000000000000000000000000000000001';
|
|
69
|
+
const recipient = '0x0000000000000000000000000000000000000002';
|
|
70
|
+
const zeroWord = '0'.repeat(64);
|
|
71
|
+
|
|
72
|
+
it('encodes the target, selector, arguments, ID and unmapped inputs', () => {
|
|
73
|
+
const action = new dfs.Action('MockSwap', target, ['uint256', 'address'], ['5', recipient]);
|
|
74
|
+
// Static ABI tuple: one word for the amount, one for the address.
|
|
75
|
+
const payload = `0x${'5'.padStart(64, '0')}${'2'.padStart(64, '0')}`;
|
|
76
|
+
assert.deepEqual(action.encodeForRecipe(), [payload, `0x${zeroWord}`, '0xe45f86c0', [0, 0]]);
|
|
77
|
+
const [address, data] = action.encodeForDsProxyCall();
|
|
78
|
+
assert.equal(address, target);
|
|
79
|
+
assert.equal(data.slice(0, 10), '0x389f87ff'); // executeActionDirect(bytes)
|
|
80
|
+
assert.equal(AbiCoder.decodeParameter('bytes', `0x${data.slice(10)}`), payload);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('replaces mapped inputs with zeros without changing the original arguments', () => {
|
|
84
|
+
const args = ['$2', recipient];
|
|
85
|
+
const action = new dfs.Action('MockSwap', target, ['uint256', 'address'], args);
|
|
86
|
+
const [payload, , , mapping] = action.encodeForRecipe();
|
|
87
|
+
assert.equal(payload, `0x${zeroWord}${'2'.padStart(64, '0')}`);
|
|
88
|
+
assert.deepEqual(mapping, [2, 0]);
|
|
89
|
+
assert.deepEqual(args, ['$2', recipient]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('preserves nested tuple and array values around mapped inputs', () => {
|
|
93
|
+
const args = [['7', ['$1', recipient]], ['11', '13']];
|
|
94
|
+
const action = new dfs.Action('MockSwap', target,
|
|
95
|
+
[['uint256', ['uint256', 'address']], 'uint256[]'], args);
|
|
96
|
+
action.mappableArgs = [args[0][1][0], args[0][1][1]];
|
|
97
|
+
const [payload, , , mapping] = action.encodeForRecipe();
|
|
98
|
+
const decoded = AbiCoder.decodeParameter('((uint256,(uint256,address)),uint256[])', payload);
|
|
99
|
+
assert.equal(decoded[0][0], '7');
|
|
100
|
+
assert.equal(decoded[0][1][0], '0');
|
|
101
|
+
assert.equal(decoded[0][1][1], recipient);
|
|
102
|
+
assert.deepEqual(decoded[1], ['11', '13']);
|
|
103
|
+
assert.deepEqual(mapping, [1, 0]);
|
|
104
|
+
assert.equal(args[0][1][0], '$1');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('maps strategy slots and special wallet references', () => {
|
|
108
|
+
const action = new dfs.Action('MockSwap', target,
|
|
109
|
+
['uint256', 'uint256', 'address', 'address', 'uint256'],
|
|
110
|
+
['$1', '&amount', '&proxy', '&eoa', '9']);
|
|
111
|
+
assert.deepEqual(action.encodeForStrategy({ '&amount': { index: 128 } }),
|
|
112
|
+
['0xe45f86c0', [1, 128, 254, 255, 0]]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('rejects mismatched argument counts', () => {
|
|
116
|
+
assert.throws(() => new dfs.Action('MockSwap', target, ['uint256'], []),
|
|
117
|
+
'Parameters/arguments length mismatch');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
describe('Action reference validation', () => {
|
|
123
|
+
const target = '0x0000000000000000000000000000000000000001';
|
|
124
|
+
|
|
125
|
+
for (const reference of ['$0', '$1junk', 'prefix$1', '$1.5', '$9007199254740992']) {
|
|
126
|
+
it(`rejects malformed reference ${reference} in every encoding path`, () => {
|
|
127
|
+
const action = new dfs.Action('MockSwap', target, ['uint256'], [reference]);
|
|
128
|
+
for (const encode of [
|
|
129
|
+
() => action.encodeForDsProxyCall(),
|
|
130
|
+
() => action.encodeForRecipe(),
|
|
131
|
+
() => action.encodeForStrategy({}),
|
|
132
|
+
]) {
|
|
133
|
+
assert.throws(encode, `Invalid action return reference: ${reference}`);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
it('preserves existing leading-zero references and ordinary strings', () => {
|
|
139
|
+
const action = new dfs.Action('MockSwap', target, ['uint256', 'string'], ['$01', '$price']);
|
|
140
|
+
assert.deepEqual(action.encodeForRecipe()[3], [1, 0]);
|
|
141
|
+
});
|
|
142
|
+
});
|
package/test/ActionWithL2.js
CHANGED
|
@@ -1,27 +1,73 @@
|
|
|
1
1
|
const dfs = require('../src');
|
|
2
|
-
const
|
|
3
|
-
const {assert} = require('chai');
|
|
2
|
+
const AbiCoder = require('web3-eth-abi');
|
|
3
|
+
const { assert } = require('chai');
|
|
4
4
|
|
|
5
5
|
const MAXUINT = '115792089237316195423570985008687907853269984665640564039457584007913129639935';
|
|
6
|
-
const
|
|
6
|
+
const target = '0x0000000000000000000000000000000000000001';
|
|
7
|
+
const recipient = '0x0000000000000000000000000000000000000002';
|
|
8
|
+
|
|
7
9
|
describe('ActionWithL2', () => {
|
|
10
|
+
it('packs integer boundaries, addresses and booleans to their specified widths', () => {
|
|
11
|
+
const action = new dfs.ActionWithL2('MockSwap', target, [], []);
|
|
12
|
+
for (const [value, expected] of [[0, '00'], [1, '01'], [255, 'ff']]) {
|
|
13
|
+
assert.equal(action.numberToBytes1(value), expected);
|
|
14
|
+
}
|
|
15
|
+
for (const [value, expected] of [[0, '0000'], [1, '0001'], [65535, 'ffff']]) {
|
|
16
|
+
assert.equal(action.numberToBytes2(value), expected);
|
|
17
|
+
}
|
|
18
|
+
assert.equal(action.numberToBytes32('0'), '0'.repeat(64));
|
|
19
|
+
assert.equal(action.numberToBytes32('1'), '1'.padStart(64, '0'));
|
|
20
|
+
assert.equal(action.numberToBytes32(MAXUINT), 'f'.repeat(64));
|
|
21
|
+
assert.equal(action.addressToBytes20(recipient), '2'.padStart(40, '0'));
|
|
22
|
+
assert.equal(action.boolToBytes1(false), '00');
|
|
23
|
+
assert.equal(action.boolToBytes1(true), '01');
|
|
24
|
+
});
|
|
8
25
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
});
|
|
26
|
+
it('falls back to standard ABI encoding when packed encoding is not overridden', () => {
|
|
27
|
+
const action = new dfs.ActionWithL2('MockSwap', target, ['uint256'], ['5']);
|
|
28
|
+
const data = action.encodeForL2DsProxyCall();
|
|
29
|
+
assert.equal(data.slice(0, 10), '0x389f87ff');
|
|
30
|
+
assert.equal(AbiCoder.decodeParameter('bytes', `0x${data.slice(10)}`),
|
|
31
|
+
`0x${'5'.padStart(64, '0')}`);
|
|
32
|
+
assert.equal(action.encodeForL2Recipe(), `0x${data.slice(10)}`);
|
|
17
33
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
it('
|
|
21
|
-
action = new dfs.actions.aaveV3.AaveV3WithdrawAction(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
|
|
35
|
+
for (const useDefaultMarket of [true, false]) {
|
|
36
|
+
it(`encodes Aave withdrawal with ${useDefaultMarket ? 'default' : 'explicit'} market`, () => {
|
|
37
|
+
const action = new dfs.actions.aaveV3.AaveV3WithdrawAction(
|
|
38
|
+
useDefaultMarket, target, MAXUINT, recipient, 9,
|
|
39
|
+
);
|
|
40
|
+
// uint16 asset ID, bool default market, uint256 amount, address recipient,
|
|
41
|
+
// followed by the market address only when not using the default.
|
|
42
|
+
const payload = `0009${useDefaultMarket ? '01' : '00'}${'f'.repeat(64)}${recipient.slice(2)}${useDefaultMarket ? '' : target.slice(2)}`;
|
|
43
|
+
assert.equal(action.encodeForL2DsProxyCall(), `0x2895f3aa${payload}`);
|
|
44
|
+
assert.equal(action.encodeForL2Recipe(), `0x${payload}`);
|
|
45
|
+
|
|
46
|
+
const previous = { ...dfs.CONFIG };
|
|
47
|
+
try {
|
|
48
|
+
dfs.configure({ chainId: 10, testingMode: false });
|
|
49
|
+
assert.deepEqual(action.encodeForDsProxyCall(), [action.contractAddress, `0x2895f3aa${payload}`]);
|
|
50
|
+
} finally {
|
|
51
|
+
dfs.configure(previous);
|
|
52
|
+
}
|
|
25
53
|
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
describe('ActionWithL2 integer validation', () => {
|
|
59
|
+
const action = new dfs.ActionWithL2('MockSwap', target, [], []);
|
|
60
|
+
|
|
61
|
+
for (const [method, overflow] of [['numberToBytes1', 256], ['numberToBytes2', 65536]]) {
|
|
62
|
+
it(`${method} rejects overflow, negative, fractional and non-finite values`, () => {
|
|
63
|
+
for (const value of [overflow, -1, 1.5, NaN, Infinity]) {
|
|
64
|
+
assert.throws(() => action[method](value), RangeError, 'Value must fit');
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
it('preserves hex text accepted by existing JavaScript callers', () => {
|
|
70
|
+
assert.equal(action.numberToBytes1('10'), '10');
|
|
71
|
+
assert.equal(action.numberToBytes2('10'), '0010');
|
|
26
72
|
});
|
|
27
|
-
})
|
|
73
|
+
});
|