Skip to content

Commit

Permalink
Merge pull request #90 from valory-xyz/feat/backup-owner
Browse files Browse the repository at this point in the history
Endpoints for managing safe owners
  • Loading branch information
angrybayblade authored May 16, 2024
2 parents 645a8bf + bc68b76 commit b716642
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ignore-patterns=contract.py
ignore=operate/data/contracts/

[MESSAGES CONTROL]
disable=C0103,R0801,C0301,C0201,C0204,C0209,W1203,C0302,R1735,R1729,W0511,E0611,R0903
disable=C0103,R0801,C0301,C0201,C0204,C0209,W1203,C0302,R1735,R1729,W0511,E0611,R0903,E1101

# See here for more options: https://www.codeac.io/documentation/pylint-configuration.html
R1735: use-dict-literal
Expand Down
16 changes: 13 additions & 3 deletions frontend/service/Wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,27 @@ const createEoa = async (chain: Chain) =>
body: JSON.stringify({ chain_type: chain }),
}).then((res) => res.json());

const createSafe = async (chain: Chain) =>
fetch(`${BACKEND_URL}/wallet`, {
const createSafe = async (chain: Chain, owner?: string) =>
fetch(`${BACKEND_URL}/wallet/safe`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ chain_type: chain, owner: owner }),
}).then((res) => res.json());

const addBackupOwner = async (chain: Chain, owner: string) =>
fetch(`${BACKEND_URL}/wallet/safe`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ chain_type: chain }),
body: JSON.stringify({ chain_type: chain, owner: owner }),
}).then((res) => res.json());

export const WalletService = {
getWallets,
createEoa,
createSafe,
addBackupOwner
};
83 changes: 82 additions & 1 deletion operate/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,23 @@ async def _get_wallets(request: Request) -> t.List[t.Dict]:
wallets.append(wallet.json)
return JSONResponse(content=wallets)

@app.get("/api/wallet/{chain}")
@with_retries
async def _get_wallet_by_chain(request: Request) -> t.List[t.Dict]:
"""Create wallet safe"""
ledger_type = get_ledger_type_from_chain_type(
chain=ChainType.from_string(request.path_params["chain"])
)
manager = operate.wallet_manager
if not manager.exists(ledger_type=ledger_type):
return JSONResponse(
content={"error": "Wallet does not exist"},
status_code=404,
)
return JSONResponse(
content=manager.load(ledger_type=ledger_type).json,
)

@app.post("/api/wallet")
@with_retries
async def _create_wallet(request: Request) -> t.List[t.Dict]:
Expand Down Expand Up @@ -339,7 +356,35 @@ 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.put("/api/wallet")
@app.get("/api/wallet/safe")
@with_retries
async def _get_safes(request: Request) -> t.List[t.Dict]:
"""Create wallet safe"""
safes = []
for wallet in operate.wallet_manager:
safes.append({wallet.ledger_type: wallet.safe})
return JSONResponse(content=safes)

@app.get("/api/wallet/safe/{chain}")
@with_retries
async def _get_safe(request: Request) -> t.List[t.Dict]:
"""Create wallet safe"""
ledger_type = get_ledger_type_from_chain_type(
chain=ChainType.from_string(request.path_params["chain"])
)
manager = operate.wallet_manager
if not manager.exists(ledger_type=ledger_type):
return JSONResponse(
content={"error": "Wallet does not exist"},
status_code=404,
)
return JSONResponse(
content={
"safe": manager.load(ledger_type=ledger_type).safe,
},
)

@app.post("/api/wallet/safe")
@with_retries
async def _create_safe(request: Request) -> t.List[t.Dict]:
"""Create wallet safe"""
Expand All @@ -363,10 +408,46 @@ async def _create_safe(request: Request) -> t.List[t.Dict]:
return JSONResponse(content={"error": "Wallet does not exist"})

wallet = manager.load(ledger_type=ledger_type)
if wallet.safe is not None:
return JSONResponse(
content={"safe": wallet.safe, "message": "Safe already exists!"}
)

wallet.create_safe( # pylint: disable=no-member
chain_type=chain_type,
owner=data.get("owner"),
)
return JSONResponse(content={"safe": wallet.safe, "message": "Safe created!"})

@app.put("/api/wallet/safe")
@with_retries
async def _update_safe(request: Request) -> t.List[t.Dict]:
"""Create wallet safe"""
# TODO: Extract login check as decorator
if operate.user_account is None:
return JSONResponse(
content={"error": "Cannot create safe; User account does not exist!"},
status_code=400,
)

if operate.password is None:
return JSONResponse(
content={"error": "You need to login before creating a safe"},
status_code=401,
)

data = await request.json()
chain_type = ChainType(data["chain_type"])
ledger_type = get_ledger_type_from_chain_type(chain=chain_type)
manager = operate.wallet_manager
if not manager.exists(ledger_type=ledger_type):
return JSONResponse(content={"error": "Wallet does not exist"})

wallet = manager.load(ledger_type=ledger_type)
wallet.add_or_swap_owner(
chain_type=chain_type,
owner=data.get("owner"),
)
return JSONResponse(content=wallet.json)

@app.get("/api/services")
Expand Down
111 changes: 101 additions & 10 deletions operate/utils/gnosis.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

"""Safe helpers."""

import binascii
import secrets
import typing as t
from enum import Enum
Expand Down Expand Up @@ -160,14 +161,6 @@ def create_safe(
) -> t.Tuple[str, int]:
"""Create gnosis safe."""
salt_nonce = salt_nonce or _get_nonce()
tx = registry_contracts.gnosis_safe.get_deploy_transaction(
ledger_api=ledger_api,
deployer_address=crypto.address,
owners=[crypto.address],
threshold=1,
salt_nonce=salt_nonce,
)
safe = tx.pop("contract_address")

def _build( # pylint: disable=unused-argument
*args: t.Any, **kwargs: t.Any
Expand Down Expand Up @@ -195,10 +188,108 @@ def _build( # pylint: disable=unused-argument
"build",
_build,
)
tx_settler.transact(
receipt = tx_settler.transact(
method=lambda: {},
contract="",
kwargs={},
)
instance = registry_contracts.gnosis_safe_proxy_factory.get_instance(
ledger_api=ledger_api,
contract_address="0xa6b71e26c5e0845f74c812102ca7114b6a896ab2",
)
(event,) = instance.events.ProxyCreation().process_receipt(receipt)
return event["args"]["proxy"], salt_nonce


def get_owners(ledger_api: LedgerApi, safe: str) -> t.List[str]:
"""Get list of owners."""
return registry_contracts.gnosis_safe.get_owners(
ledger_api=ledger_api,
contract_address=safe,
).get("owners", [])


return safe, salt_nonce
def send_safe_txs(
txd: bytes,
safe: str,
ledger_api: LedgerApi,
crypto: Crypto,
) -> None:
"""Send internal safe transaction."""
owner = ledger_api.api.to_checksum_address(
crypto.address,
)
safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash(
ledger_api=ledger_api,
contract_address=safe,
value=0,
safe_tx_gas=0,
to_address=safe,
data=txd,
operation=SafeOperation.CALL.value,
).get("tx_hash")
safe_tx_bytes = binascii.unhexlify(
safe_tx_hash[2:],
)
signatures = {
owner: crypto.sign_message(
message=safe_tx_bytes,
is_deprecated_mode=True,
)[2:]
}
transaction = registry_contracts.gnosis_safe.get_raw_safe_transaction(
ledger_api=ledger_api,
contract_address=safe,
sender_address=owner,
owners=(owner,), # type: ignore
to_address=safe,
value=0,
data=txd,
safe_tx_gas=0,
signatures_by_owner=signatures,
operation=SafeOperation.CALL.value,
nonce=ledger_api.api.eth.get_transaction_count(owner),
)
ledger_api.get_transaction_receipt(
ledger_api.send_signed_transaction(
crypto.sign_transaction(
transaction,
),
)
)


def add_owner(
ledger_api: LedgerApi,
crypto: Crypto,
safe: str,
owner: str,
) -> None:
"""Add owner to a safe."""
instance = registry_contracts.gnosis_safe.get_instance(
ledger_api=ledger_api,
contract_address=safe,
)
txd = instance.encodeABI(
fn_name="addOwnerWithThreshold",
args=[
owner,
1,
],
)
send_safe_txs(
txd=bytes.fromhex(txd[2:]),
safe=safe,
ledger_api=ledger_api,
crypto=crypto,
)


def swap_owner( # pylint: disable=unused-argument
ledger_api: LedgerApi,
crypto: Crypto,
safe: str,
old_owner: str,
new_owner: str,
) -> None:
"""Swap owner on a safe."""
Loading

0 comments on commit b716642

Please sign in to comment.