From 858dd4768b73899611eba9de581cb1980f5932cb Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Fri, 22 Nov 2024 18:08:51 +0100 Subject: [PATCH 1/8] chore: add enriched endpoint --- api.md | 59 ++++++++++++++++++++++++++++++++++++++++ operate/cli.py | 9 ++++++ operate/wallet/master.py | 31 +++++++++++++++++++++ 3 files changed, 99 insertions(+) diff --git a/api.md b/api.md index ab77b977..38c16082 100644 --- a/api.md +++ b/api.md @@ -198,6 +198,65 @@ Returns a list of available wallets --- +### `GET /api/enriched/wallet` + +Returns a list of available wallets with enriched information. It executes on-chain requests to populate the list of owners of each safe, and provides the attributes `all_safe_backup_owners_match` and `single_safe_backup_owner_per_chain`. + +
+ Response + +```json +[ + { + "address":"0xFafd5cb31a611C5e5aa65ea8c6226EB4328175E7", + "all_safe_backup_owners_match":false, + "ledger_type":"ethereum", + "safe_chains":[ + "gnosis", + "ethereum", + "base", + "optimistic" + ], + "safe_nonce":110558881674480320952254000342160989674913430251257716940579305238321962891821, + "safes":{ + "base":{ + "0xd56fb274ce2C66008D5c4C09980c4f36Ab81ff23":{ + "owners":[ + + ] + } + }, + "ethereum":{ + "0xd56fb274ce2C66008D5c4C09980c4f36Ab81ff23":{ + "owners":[ + "0x46eC2E77Fe3E367252f1A8a77470CE8eEd2A985b" + ] + } + }, + "gnosis":{ + "0xd56fb274ce2C66008D5c4C09980c4f36Ab81ff23":{ + "owners":[ + "0x46eC2E77Fe3E367252f1A8a77470CE8eEd2A985b" + ] + } + }, + "optimistic":{ + "0xd56fb274ce2C66008D5c4C09980c4f36Ab81ff23":{ + "owners":[ + "0x46eC2E77Fe3E367252f1A8a77470CE8eEd2A985b" + ] + } + } + }, + "single_safe_backup_owner_per_chain":false + } +] +``` + +
+ +--- + ### `POST /api/wallet` Creates a master wallet for given chain type. If a wallet already exists for a given chain type, it returns the already existing wallet without creating an additional one. diff --git a/operate/cli.py b/operate/cli.py index ad7ae745..af2af761 100644 --- a/operate/cli.py +++ b/operate/cli.py @@ -447,6 +447,15 @@ async def _create_wallet(request: Request) -> t.List[t.Dict]: wallet, mnemonic = manager.create(ledger_type=ledger_type) return JSONResponse(content={"wallet": wallet.json, "mnemonic": mnemonic}) + @app.get("/api/enriched/wallet") + @with_retries + async def _get_wallet_safe(request: Request) -> t.List[t.Dict]: + """Get wallets.""" + wallets = [] + for wallet in operate.wallet_manager: + wallets.append(wallet.enriched_json) + return JSONResponse(content=wallets) + @app.get("/api/wallet/safe") @with_retries async def _get_safes(request: Request) -> t.List[t.Dict]: diff --git a/operate/wallet/master.py b/operate/wallet/master.py index 17664103..86a3cfee 100644 --- a/operate/wallet/master.py +++ b/operate/wallet/master.py @@ -145,6 +145,12 @@ def update_backup_owner( """Update backup owner.""" raise NotImplementedError() + # TODO move to resource.py ? + @property + def enriched_json(self) -> t.Dict: + """Get JSON representation with extended information (e.g., safe owners).""" + raise NotImplementedError + @classmethod def migrate_format(cls, path: Path) -> bool: """Migrate the JSON file format if needed.""" @@ -393,6 +399,31 @@ def update_backup_owner( return False + @property + def enriched_json(self) -> t.Dict: + """Get JSON representation with extended information (e.g., safe owners).""" + rpc = None + wallet_json = self.json + + if not self.safes: + return wallet_json + + owner_sets = set() + for chain, safe in self.safes.items(): + ledger_api = self.ledger_api(chain=chain, rpc=rpc) + owners = get_owners(ledger_api=ledger_api, safe=safe) + owners.remove(self.address) + wallet_json["safes"][chain.value] = { + wallet_json["safes"][chain.value]: {"owners": owners} + } + owner_sets.add(frozenset(owners)) + + wallet_json["all_safe_backup_owners_match"] = len(owner_sets) == 1 + wallet_json["single_safe_backup_owner_per_chain"] = all( + len(owner) == 1 for owner in owner_sets + ) + return wallet_json + @classmethod def load(cls, path: Path) -> "EthereumMasterWallet": """Load master wallet.""" From 8e29636458e04d9894572b6236b261ff0e93bf07 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Fri, 22 Nov 2024 18:16:22 +0100 Subject: [PATCH 2/8] chore: doc --- api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.md b/api.md index 38c16082..e3fb97f1 100644 --- a/api.md +++ b/api.md @@ -222,7 +222,7 @@ Returns a list of available wallets with enriched information. It executes on-ch "base":{ "0xd56fb274ce2C66008D5c4C09980c4f36Ab81ff23":{ "owners":[ - + // No owners for this safe ] } }, From 5842c863356357bc9f325b51c8791d1c306d4de6 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Fri, 22 Nov 2024 18:24:51 +0100 Subject: [PATCH 3/8] refactor: rename parameter --- api.md | 4 ++-- operate/wallet/master.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api.md b/api.md index e3fb97f1..05b4cb5f 100644 --- a/api.md +++ b/api.md @@ -200,7 +200,7 @@ Returns a list of available wallets ### `GET /api/enriched/wallet` -Returns a list of available wallets with enriched information. It executes on-chain requests to populate the list of owners of each safe, and provides the attributes `all_safe_backup_owners_match` and `single_safe_backup_owner_per_chain`. +Returns a list of available wallets with enriched information. It executes on-chain requests to populate the list of owners of each safe, and provides the attributes `all_safe_backup_owners_match` and `single_backup_owner_per_safe`.
Response @@ -248,7 +248,7 @@ Returns a list of available wallets with enriched information. It executes on-ch } } }, - "single_safe_backup_owner_per_chain":false + "single_backup_owner_per_safe":false } ] ``` diff --git a/operate/wallet/master.py b/operate/wallet/master.py index 86a3cfee..f7c058e1 100644 --- a/operate/wallet/master.py +++ b/operate/wallet/master.py @@ -419,7 +419,7 @@ def enriched_json(self) -> t.Dict: owner_sets.add(frozenset(owners)) wallet_json["all_safe_backup_owners_match"] = len(owner_sets) == 1 - wallet_json["single_safe_backup_owner_per_chain"] = all( + wallet_json["single_backup_owner_per_safe"] = all( len(owner) == 1 for owner in owner_sets ) return wallet_json From ea8381f95824467bbddd29e6f359dbe45f646029 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Fri, 22 Nov 2024 18:56:58 +0100 Subject: [PATCH 4/8] chore: add all_chains_same_safe_address --- api.md | 3 ++- operate/wallet/master.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/api.md b/api.md index 05b4cb5f..6a53abf3 100644 --- a/api.md +++ b/api.md @@ -200,7 +200,7 @@ Returns a list of available wallets ### `GET /api/enriched/wallet` -Returns a list of available wallets with enriched information. It executes on-chain requests to populate the list of owners of each safe, and provides the attributes `all_safe_backup_owners_match` and `single_backup_owner_per_safe`. +Returns a list of available wallets with enriched information. It executes on-chain requests to populate the list of owners of each safe, and provides the attributes `all_chains_same_safe_address`, `all_safe_backup_owners_match` and `single_backup_owner_per_safe`.
Response @@ -209,6 +209,7 @@ Returns a list of available wallets with enriched information. It executes on-ch [ { "address":"0xFafd5cb31a611C5e5aa65ea8c6226EB4328175E7", + "all_chains_same_safe_address":true, "all_safe_backup_owners_match":false, "ledger_type":"ethereum", "safe_chains":[ diff --git a/operate/wallet/master.py b/operate/wallet/master.py index f7c058e1..649765b6 100644 --- a/operate/wallet/master.py +++ b/operate/wallet/master.py @@ -418,6 +418,7 @@ def enriched_json(self) -> t.Dict: } owner_sets.add(frozenset(owners)) + wallet_json["all_chains_same_safe_address"] = len(set(self.safes.values())) == 1 wallet_json["all_safe_backup_owners_match"] = len(owner_sets) == 1 wallet_json["single_backup_owner_per_safe"] = all( len(owner) == 1 for owner in owner_sets From 741787deb27613ee4c2760b17c55c0b6432d1f9a Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Fri, 22 Nov 2024 19:25:36 +0100 Subject: [PATCH 5/8] chore: update --- api.md | 11 ++++++++--- operate/wallet/master.py | 8 ++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/api.md b/api.md index 6a53abf3..d404e58e 100644 --- a/api.md +++ b/api.md @@ -200,7 +200,11 @@ Returns a list of available wallets ### `GET /api/enriched/wallet` -Returns a list of available wallets with enriched information. It executes on-chain requests to populate the list of owners of each safe, and provides the attributes `all_chains_same_safe_address`, `all_safe_backup_owners_match` and `single_backup_owner_per_safe`. +Returns a list of available wallets with enriched information. It executes on-chain requests to populate the list of owners of each safe, and provides the attributes + +- `consistent_backup_owner`: This flag is `true` when all safes across the chains have exactly the same set of backup owner addresses. It ensures that ownership is identical across all safes, regardless of the number of owners. +- `consistent_backup_owner_count`: This flag is `true` when all safes have the same number of owners, and that number is either 0 (no owners) or 1 (exactly one owner). It checks for uniformity in the count of owners and restricts the count to these two cases. +- `consistent_safe_address`: This flag is `true` when all chains have the same safe address. It ensures there is a single safe address consistently used across all chains.
Response @@ -209,8 +213,9 @@ Returns a list of available wallets with enriched information. It executes on-ch [ { "address":"0xFafd5cb31a611C5e5aa65ea8c6226EB4328175E7", - "all_chains_same_safe_address":true, - "all_safe_backup_owners_match":false, + "consistent_backup_owner": false, + "consistent_backup_owner_count": false, + "consistent_safe_address": true, "ledger_type":"ethereum", "safe_chains":[ "gnosis", diff --git a/operate/wallet/master.py b/operate/wallet/master.py index 649765b6..0dded776 100644 --- a/operate/wallet/master.py +++ b/operate/wallet/master.py @@ -418,11 +418,11 @@ def enriched_json(self) -> t.Dict: } owner_sets.add(frozenset(owners)) - wallet_json["all_chains_same_safe_address"] = len(set(self.safes.values())) == 1 - wallet_json["all_safe_backup_owners_match"] = len(owner_sets) == 1 - wallet_json["single_backup_owner_per_safe"] = all( + wallet_json["consistent_safe_address"] = len(set(self.safes.values())) == 1 + wallet_json["consistent_backup_owner"] = len(owner_sets) == 1 + wallet_json["consistent_backup_owner_count"] = all( len(owner) == 1 for owner in owner_sets - ) + ) or all(len(owner) == 0 for owner in owner_sets) return wallet_json @classmethod From 6c3e9d36afb89cbf03d5f2d2945603645ff8b9a1 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Fri, 22 Nov 2024 19:26:47 +0100 Subject: [PATCH 6/8] doc: fix doc --- api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.md b/api.md index d404e58e..2f9b5856 100644 --- a/api.md +++ b/api.md @@ -203,7 +203,7 @@ Returns a list of available wallets Returns a list of available wallets with enriched information. It executes on-chain requests to populate the list of owners of each safe, and provides the attributes - `consistent_backup_owner`: This flag is `true` when all safes across the chains have exactly the same set of backup owner addresses. It ensures that ownership is identical across all safes, regardless of the number of owners. -- `consistent_backup_owner_count`: This flag is `true` when all safes have the same number of owners, and that number is either 0 (no owners) or 1 (exactly one owner). It checks for uniformity in the count of owners and restricts the count to these two cases. +- `consistent_backup_owner_count`: This flag is `true` when all safes have the same number of owners, and that number is either 0 (no backup owners) or 1 (exactly one backup owner). It checks for uniformity in the count of owners and restricts the count to these two cases. - `consistent_safe_address`: This flag is `true` when all chains have the same safe address. It ensures there is a single safe address consistently used across all chains.
From b51e0080ab7baede48af4bf873252d39fe32fde2 Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Mon, 25 Nov 2024 19:00:13 +0100 Subject: [PATCH 7/8] feat: return token balances --- api.md | 2 +- operate/cli.py | 4 ++-- operate/ledger/profiles.py | 8 ++++++++ operate/wallet/master.py | 31 ++++++++++++++++++++++++++----- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/api.md b/api.md index 2f9b5856..38d1a975 100644 --- a/api.md +++ b/api.md @@ -198,7 +198,7 @@ Returns a list of available wallets --- -### `GET /api/enriched/wallet` +### `GET /api/extended/wallet` Returns a list of available wallets with enriched information. It executes on-chain requests to populate the list of owners of each safe, and provides the attributes diff --git a/operate/cli.py b/operate/cli.py index af2af761..6526e346 100644 --- a/operate/cli.py +++ b/operate/cli.py @@ -447,13 +447,13 @@ async def _create_wallet(request: Request) -> t.List[t.Dict]: wallet, mnemonic = manager.create(ledger_type=ledger_type) return JSONResponse(content={"wallet": wallet.json, "mnemonic": mnemonic}) - @app.get("/api/enriched/wallet") + @app.get("/api/extended/wallet") @with_retries async def _get_wallet_safe(request: Request) -> t.List[t.Dict]: """Get wallets.""" wallets = [] for wallet in operate.wallet_manager: - wallets.append(wallet.enriched_json) + wallets.append(wallet.extended_json) return JSONResponse(content=wallets) @app.get("/api/wallet/safe") diff --git a/operate/ledger/profiles.py b/operate/ledger/profiles.py index 9d15b866..1c66ef6c 100644 --- a/operate/ledger/profiles.py +++ b/operate/ledger/profiles.py @@ -104,3 +104,11 @@ Chain.ETHEREUM: "0x0001A500A6B18995B03f44bb040A5fFc28E45CB0", Chain.MODE: "0xcfD1D50ce23C46D3Cf6407487B2F8934e96DC8f9", } + +USDC: t.Dict[Chain, str] = { + Chain.GNOSIS: "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", + Chain.OPTIMISTIC: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", + Chain.BASE: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + Chain.ETHEREUM: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + Chain.MODE: "0xd988097fb8612cc24eeC14542bC03424c656005f", +} diff --git a/operate/wallet/master.py b/operate/wallet/master.py index 0dded776..882da696 100644 --- a/operate/wallet/master.py +++ b/operate/wallet/master.py @@ -30,6 +30,7 @@ from aea.crypto.registries import make_ledger_api from aea.helpers.logging import setup_logger from aea_ledger_ethereum.ethereum import EthereumApi, EthereumCrypto +from autonomy.chain.base import registry_contracts from autonomy.chain.config import ChainType as ChainProfile from autonomy.chain.tx import TxSettler from web3 import Account @@ -40,9 +41,10 @@ ON_CHAIN_INTERACT_TIMEOUT, ) from operate.ledger import get_default_rpc +from operate.ledger.profiles import OLAS, USDC from operate.operate_types import Chain, LedgerType from operate.resource import LocalResource -from operate.utils.gnosis import add_owner +from operate.utils.gnosis import NULL_ADDRESS, add_owner from operate.utils.gnosis import create_safe as create_gnosis_safe from operate.utils.gnosis import get_owners, remove_owner, swap_owner from operate.utils.gnosis import transfer as transfer_from_safe @@ -145,9 +147,9 @@ def update_backup_owner( """Update backup owner.""" raise NotImplementedError() - # TODO move to resource.py ? + # TODO move to resource.py if used in more resources similarly @property - def enriched_json(self) -> t.Dict: + def extended_json(self) -> t.Dict: """Get JSON representation with extended information (e.g., safe owners).""" raise NotImplementedError @@ -400,9 +402,10 @@ def update_backup_owner( return False @property - def enriched_json(self) -> t.Dict: + def extended_json(self) -> t.Dict: """Get JSON representation with extended information (e.g., safe owners).""" rpc = None + tokens = (OLAS, USDC) wallet_json = self.json if not self.safes: @@ -413,11 +416,29 @@ def enriched_json(self) -> t.Dict: ledger_api = self.ledger_api(chain=chain, rpc=rpc) owners = get_owners(ledger_api=ledger_api, safe=safe) owners.remove(self.address) + + balances: t.Dict[str, int] = {} + balances[NULL_ADDRESS] = ledger_api.get_balance(safe) or 0 + for token in tokens: + balance = ( + registry_contracts.erc20.get_instance( + ledger_api=ledger_api, + contract_address=token[chain], + ) + .functions.balanceOf(safe) + .call() + ) + balances[token[chain]] = balance + wallet_json["safes"][chain.value] = { - wallet_json["safes"][chain.value]: {"owners": owners} + wallet_json["safes"][chain.value]: { + "backup_owners": owners, + "balances": balances, + } } owner_sets.add(frozenset(owners)) + wallet_json["extended_json"] = True wallet_json["consistent_safe_address"] = len(set(self.safes.values())) == 1 wallet_json["consistent_backup_owner"] = len(owner_sets) == 1 wallet_json["consistent_backup_owner_count"] = all( From 2f36d4c3e345a3c8ed8e0dfe03f144dfde5671ce Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Mon, 25 Nov 2024 19:07:40 +0100 Subject: [PATCH 8/8] chore: update api.md --- api.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/api.md b/api.md index 38d1a975..f5f860fe 100644 --- a/api.md +++ b/api.md @@ -227,30 +227,35 @@ Returns a list of available wallets with enriched information. It executes on-ch "safes":{ "base":{ "0xd56fb274ce2C66008D5c4C09980c4f36Ab81ff23":{ - "owners":[ - // No owners for this safe - ] + "backup_owners": [], // Empty = no backup owners + "balances": {...} } }, "ethereum":{ "0xd56fb274ce2C66008D5c4C09980c4f36Ab81ff23":{ - "owners":[ + "backup_owners":[ "0x46eC2E77Fe3E367252f1A8a77470CE8eEd2A985b" - ] + ], + "balances": {...} } }, "gnosis":{ "0xd56fb274ce2C66008D5c4C09980c4f36Ab81ff23":{ - "owners":[ + "backup_owners":[ "0x46eC2E77Fe3E367252f1A8a77470CE8eEd2A985b" - ] + ], + "balances": { + "0x0000000000000000000000000000000000000000": 995899999999999999998, // xDAI + "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83": 0, // USDC + "0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f": 960000000000000000000 // OLAS } }, "optimistic":{ "0xd56fb274ce2C66008D5c4C09980c4f36Ab81ff23":{ - "owners":[ + "backup_owners":[ "0x46eC2E77Fe3E367252f1A8a77470CE8eEd2A985b" - ] + ], + "balances": {...} } } },