Skip to content

Commit

Permalink
Merge pull request #29 from api3dao/289-sponsor-wallet-top-up-watcher
Browse files Browse the repository at this point in the history
289 sponsor wallet top up watcher
  • Loading branch information
acenolaza authored Jun 15, 2023
2 parents e3add55 + 8392359 commit a701ac2
Show file tree
Hide file tree
Showing 22 changed files with 4,159 additions and 7,633 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
DEBUG=true
OPSGENIE_API_KEY=''
DEBUG=
OPSGENIE_API_KEY=
3 changes: 2 additions & 1 deletion .eslintrc.fp.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
module.exports = {
plugins: ['functional'],
rules: {
'functional/prefer-tacit': ['error', { assumeTypes: { allowFixer: false } }],
// eslint-plugin-functional
'functional/prefer-tacit': 'off',
'functional/immutable-data': ['error', { assumeTypes: true }],
},
overrides: [
Expand Down
31 changes: 7 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
# Wallet Watcher

The wallet watcher loads and ingests the contents of the [operations repository](https://github.com/api3dao/operations)
and based on the deployed beacons found in it monitors and tops up associated operational wallets.

If a wallet's balance is below the `lowBalance` configuration field in `config/config.json` the function will top up the
wallet with the amount specified in the `topUpAmount` field in the same file.
The wallet watcher loads addresses defined in wallets.json and checks that their balances are not below a defined
threshold. If they are then an alert will be sent to OpsGenie.

## Deployment

Expand All @@ -24,32 +21,13 @@ yarn sls:deploy

Be sure to watch the logs to make sure the applications are behaving as you expect.

## Important

As a safety feature the wallets handler does not top up wallets unless an env flag is set. This flag is called
`WALLET_ENABLE_SEND_FUNDS`. It can be set to `true` to enable this functionality.

The flag must be manually set by navigating through the AWS console (`lambda` > `functions` > `<walletHandler>` >
`configuration` > `Environment variables`). While it is possible to configure the flag in `serverless.yml`, the purpose
of the flag is to require that you explicitly enable this functionality and hopefully consider the consequences of doing
so. This is to prevent scenarios where dev code sends real funds to unrecoverable addresses.

## Configuration

### `config.json`

#### `chains`

- `chains.<chainId>.rpc`: The RPC provider URL.
- `chains.<chainId>.topUpAmount`: the amount to top up in the native token. The value should be in full token units
(i.e. in ethers or matics).
- `chains.<chainId>.lowBalance`: The wallet balance value below which a top up is triggered. The value should be in full
token units (i.e. in ethers or matics).
- `chains.<chainId>.globalSponsorLowBalanceWarn`: The global top up wallet balance below which an alert will be
triggered. The value should be in full token units (i.e. in ethers or matics).
- `chains.<chainId>.options`: The chain specific options used to get the gas price for top up transactions.

- `topUpMnemonic`: The mnemonic of the top up wallet.

- `opsGenieConfig.apiKey`: The Ops Genie api key.
- `opsGenieConfig.responders[n].team` (optional): The Ops Genie responder type. If left undefined this will be inferred
Expand All @@ -76,3 +54,8 @@ so. This is to prevent scenarios where dev code sends real funds to unrecoverabl
- `<chainId>[n].apiName` (optional): The name of the API provider.
- `<chainId>[n].providerXpub`: The extended public key of the sponsor address.
- `<chainId>[n].sponsor`: The sponsor address to derive the destination wallet.
- `<chainId>[n].lowBalance.value`: The value used to send an alert if wallet balance is below it.
- `<chainId>[n].lowBalance.unit`: The token units used to parse the `lowBalance.value` for balance check (i.e. ether,
wei, etc).
- `<chainId>[n].lowBalance.criticalValue`: The value used to send a _critical_ alert if wallet balance is below it. This
value must be below the `lowBalance.value` value.
64 changes: 5 additions & 59 deletions config/config.example.json
Original file line number Diff line number Diff line change
@@ -1,65 +1,12 @@
{
"chains": {
"1": {
"rpc": "https://eth-mainnet.alchemyapi.io/v2/something",
"topUpAmount": "0.2",
"lowBalance": "0.1",
"globalSponsorLowBalanceWarn": "3",
"options": {
"fulfillmentGasLimit": 500000,
"gasPriceOracle": [
{
"gasPriceStrategy": "latestBlockPercentileGasPrice",
"percentile": 60,
"minTransactionCount": 20,
"pastToCompareInBlocks": 20,
"maxDeviationMultiplier": 2
},
{
"gasPriceStrategy": "providerRecommendedGasPrice",
"recommendedGasPriceMultiplier": 1.2
},
{
"gasPriceStrategy": "constantGasPrice",
"gasPrice": {
"value": 10,
"unit": "gwei"
}
}
]
}
"rpc": "https://eth-mainnet.alchemyapi.io/v2/something"
},
"3": {
"rpc": "https://eth-ropsten.alchemyapi.io/v2/something",
"topUpAmount": "0.16",
"lowBalance": "0.15",
"globalSponsorLowBalanceWarn": "2",
"options": {
"fulfillmentGasLimit": 500000,
"gasPriceOracle": [
{
"gasPriceStrategy": "latestBlockPercentileGasPrice",
"percentile": 60,
"minTransactionCount": 20,
"pastToCompareInBlocks": 20,
"maxDeviationMultiplier": 2
},
{
"gasPriceStrategy": "providerRecommendedGasPrice",
"recommendedGasPriceMultiplier": 1.2
},
{
"gasPriceStrategy": "constantGasPrice",
"gasPrice": {
"value": 10,
"unit": "gwei"
}
}
]
}
"5": {
"rpc": "https://rpc.ankr.com/eth_goerli"
}
},
"topUpMnemonic": "test test tes...",
"opsGenieConfig": {
"apiKey": "opsgenie-api-key",
"responders": [
Expand All @@ -71,9 +18,8 @@
]
},
"explorerUrls": {
"3": "https://ropsten.etherscan.io/",
"4": "https://rinkeby.etherscan.io/",
"1": "https://etherscan.io",
"5": "https://goerli.etherscan.io/",
"80001": "https://mumbai.polygonscan.com/"
}
}
}
41 changes: 24 additions & 17 deletions config/wallets.example.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
{
"1": [
{
"apiName": "api3",
"walletType": "Provider-Sponsor",
"providerXpub": "xpub661MyMwAqRbcFeZ1CUvUpMs5bBSVLPHiuTqj7dZPertAGtd3xyTW1vrPspz7B34A7sdPahw7psrJjCXmn8KpF92jQssoqmsTk8fZ9PZN8xK",
"sponsor": "0x9fEe9F24ab79adacbB51af82fb82CFb9D818c6d9"
}
],
"3": [
{
"apiName": "api3",
"walletType": "Provider-Sponsor",
"providerXpub": "xpub661MyMwAqRbcFeZ1CUvUpMs5bBSVLPHiuTqj7dZPertAGtd3xyTW1vrPspz7B34A7sdPahw7psrJjCXmn8KpF92jQssoqmsTk8fZ9PZN8xK",
"sponsor": "0x9fEe9F24ab79adacbB51af82fb82CFb9D818c6d9"
}
]
}
"1": [
{
"apiName": "API3 top up wallet",
"walletType": "API3",
"address": "0xd77F260d5E457669E989Ea639AE662A40DC9641A",
"lowThreshold": { "value": 0.1, "unit": "ether" }
},
{
"apiName": "FunderDepository contract",
"walletType": "API3",
"address": "0xE889956fA885F42B0C294507d0e67a3168BE188b",
"lowThreshold": { "value": 0.3, "unit": "ether", "criticalValue": 0.1 }
}
],
"5": [
{
"apiName": "Some provider sponsor wallet",
"walletType": "Provider-Sponsor",
"providerXpub": "xpub661MyMwAqRbcFeZ1CUvUpMs5bBSVLPHiuTqj7dZPertAGtd3xyTW1vrPspz7B34A7sdPahw7psrJjCXmn8KpF92jQssoqmsTk8fZ9PZN8xK",
"sponsor": "0x9fEe9F24ab79adacbB51af82fb82CFb9D818c6d9",
"lowThreshold": { "value": 0.15, "unit": "ether" }
}
]
}
1 change: 0 additions & 1 deletion jest-e2e.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const config = require('./jest.config');
// eslint-disable-next-line functional/immutable-data
module.exports = {
...config,
name: 'e2e',
displayName: 'e2e',
setupFiles: ['<rootDir>/test/setup/init/set-define-property.ts'],
testMatch: ['**/?(*.)+(feature).[tj]s?(x)'],
Expand Down
1 change: 0 additions & 1 deletion jest-unit.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const config = require('./jest.config');
module.exports = {
...config,
displayName: 'unit',
name: 'unit',
setupFiles: ['<rootDir>/test/setup/init/set-define-property.ts'],
testMatch: ['**/?(*.)+(spec|test).[tj]s?(x)'],
};
16 changes: 5 additions & 11 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/
// eslint-disable-next-line functional/immutable-data
// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html

module.exports = {
projects: ['<rootDir>/jest-e2e.config.js', '<rootDir>/jest-unit.config.js'],
// All imported modules in your tests should be mocked automatically
Expand All @@ -19,22 +16,19 @@ module.exports = {
clearMocks: true,

// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// collectCoverage: true,

// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,

// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',
// coverageDirectory: 'coverage',

// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],

// Indicates which provider should be used to instrument code for coverage
coverageProvider: 'v8',

// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
Expand Down
76 changes: 38 additions & 38 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,50 +39,50 @@
"load-operations-wallets": "ts-node scripts/load-operations-wallets.ts"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.5",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@types/aws-lambda": "^8.10.97",
"@types/jest": "^27.0.3",
"@types/lodash": "^4.14.177",
"@types/mocha": "^9.0.0",
"@types/node": "^16.11.6",
"@types/prompts": "^2.0.14",
"@types/serverless": "^1.78.39",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-waffle": "^2.0.6",
"@types/aws-lambda": "^8.10.115",
"@types/jest": "^29.5.2",
"@types/lodash": "^4.14.195",
"@types/mocha": "^10.0.1",
"@types/node": "^20.2.5",
"@types/prompts": "^2.4.4",
"@types/serverless": "^3.12.12",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"aws-lambda": "^1.0.7",
"eslint": "^8.4.1",
"eslint-plugin-functional": "^4.0.2",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-jest": "^25.3.0",
"ethereum-waffle": "^3.4.0",
"hardhat": "^2.7.0",
"husky": "^7.0.4",
"jest": "^27.4.5",
"pm2": "^5.2.0",
"prettier": "^2.5.1",
"prettier-plugin-solidity": "^1.0.0-beta.19",
"eslint": "^8.42.0",
"eslint-plugin-functional": "^5.0.8",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.2.1",
"ethereum-waffle": "^4.0.10",
"hardhat": "^2.14.1",
"husky": "^8.0.3",
"jest": "^29.5.0",
"pm2": "^5.3.0",
"prettier": "^2.8.8",
"prettier-plugin-solidity": "^1.1.3",
"prompts": "^2.4.1",
"serverless": "^3",
"serverless-plugin-typescript": "^2.1.0",
"solhint": "^3.3.6",
"ts-jest": "^27.1.2",
"serverless": "^3.32.2",
"serverless-plugin-typescript": "^2.1.5",
"solhint": "^3.4.1",
"ts-jest": "^29.1.0",
"ts-node": "^10.2.1",
"typescript": "^4.5.4"
"typescript": "^5.1.3"
},
"dependencies": {
"@api3/airnode-node": "^0.8.0",
"@api3/airnode-protocol": "^0.8.0",
"@api3/airnode-utilities": "^0.8.0",
"@api3/airnode-validator": "^0.8.0",
"@api3/ois": "^1.1.2",
"@api3/operations": "^0.0.1-785b20f7ad527ae63c0f47f2a876da6c15c9a437",
"@api3/operations-utilities": "^0.0.1-e4bcdbd569e8802192b41f2bb88ed0cca7fc0c7b-1",
"@api3/promise-utils": "^0.3.0",
"@api3/airnode-node": "^0.11.1",
"@api3/airnode-protocol": "^0.11.1",
"@api3/airnode-utilities": "^0.11.1",
"@api3/airnode-validator": "^0.11.1",
"@api3/chains": "^2.1.0",
"@api3/ois": "^2.0.0",
"@api3/operations-utilities": "^0.2.0",
"@api3/promise-utils": "^0.4.0",
"@ethersproject/experimental": "^5.5.0",
"@slack/bolt": "^3.8.1",
"axios": "^0.24.0",
"dotenv": "^10.0.0",
"@openzeppelin/merkle-tree": "^1.0.4",
"@slack/bolt": "^3.13.1",
"axios": "^1.4.0",
"ethers": "^5.4.4",
"lodash": "^4.17.21",
"source-map-support": "^0.5.21",
Expand Down
57 changes: 0 additions & 57 deletions scripts/load-operations-wallets.ts

This file was deleted.

Loading

0 comments on commit a701ac2

Please sign in to comment.