Skip to content

Commit

Permalink
Reinstate Clef accounts.
Browse files Browse the repository at this point in the history
  • Loading branch information
calina-c committed Oct 30, 2023
1 parent 9419ffe commit 70f741c
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 19 deletions.
70 changes: 70 additions & 0 deletions READMEs/using-clef.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!--
Copyright 2023 Ocean Protocol Foundation
SPDX-License-Identifier: Apache-2.0
-->

# Using hardware wallets with ocean.py

This README describes how to setup ocean.py with hardware wallets.

We assume you've already (a) [installed Ocean](install.md), configured any environment variables necessary and created the Ocean object as described in (b) done [local setup](setup-local.md) or [remote setup](setup-remote.md).
These instructions are applicable to both local and remote setup. If you intend to use hardware wallets ONLY, then you can skip the wallet creation parts in the setup instructions.

## 1. Setting up and running Clef
ocean.py allows the use of hardware wallets via [Clef](https://geth.ethereum.org/docs/clef/tutorial), an account management tool included within [Geth](https://geth.ethereum.org/)

To use a hardware wallet with ocean.py, start by [installing Geth](https://geth.ethereum.org/docs/install-and-build/installing-geth).
Once finished, type the following command in a bash console and follow the on-screen prompts to set of Clef:

```console
clef init
```

If you need to create a new account, you can use the command `clef newaccount`. For other usefull commands, please consult the [Clef documentation](https://geth.ethereum.org/docs/tools/clef/introduction).

Once Clef is configured, run it in a bash console as needed, i.e.

```console
# you can use a different chain if needed
clef --chainid 8996
```

You can also customise your run, e.g. `clef --chainid 8996 --advanced`.

Keep the clef console open, you will be required to approve transactions and input your password when so requested.

## 2. Connect ocean.py to Clef via Brownie

In your Python console where you have setup the Ocean object:

```python
from ocean_lib.web3_internal.clef import get_clef_accounts
clef_accounts = get_clef_accounts()
```

Approve the connection from the Clef console. This will add your Clef account to the `accounts` array.
You can now use the Clef account instead of any wallet argument, e.g. when publishing or consuming DDOs.


```python
# pick up the account for convenience
clef_account = clef_accounts[index]

# make sure account is funded. Let's transfer some ether and OCEAN from alice
from ocean_lib.ocean.util import send_ether
send_ether(config, alice, clef_account.address, to_wei(4))
OCEAN.transfer(clef_account, to_wei(4), {"from": alice})

# publish and download an asset
name = "Branin dataset"
url = "https://raw.githubusercontent.com/trentmc/branin/main/branin.arff"

(data_nft, datatoken, ddo) = ocean.assets.create_url_asset(name, url, {"from": clef_account})
datatoken.mint(clef_account, to_wei(1), {"from": clef_account})
order_tx_id = ocean.assets.pay_for_access_service(ddo, {"from": clef_account})
ocean.assets.download_asset(ddo, clef_account, './', order_tx_id)

```

Please note that you need to consult your clef console periodically to approve transactions and input your password if needed.
You can use the ClefAccount object seamlessly, in any transaction, just like regular Accounts. Simply send your transaction with `{"from": clef_account}` where needed.
8 changes: 4 additions & 4 deletions ocean_lib/data_provider/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

from ocean_lib.exceptions import DataProviderException
from ocean_lib.http_requests.requests_session import get_requests_session
from ocean_lib.web3_internal.utils import sign_with_key
from ocean_lib.web3_internal.clef import ClefAccount
from ocean_lib.web3_internal.utils import sign_with_clef, sign_with_key

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -69,9 +70,8 @@ def sign_message(

print(f"signing message with nonce {nonce}: {msg}, account={wallet.address}")

# reinstate as part of #1461
# if isinstance(wallet, ClefAccount):
# return nonce, str(sign_with_clef(f"{msg}{nonce}", wallet))
if isinstance(wallet, ClefAccount):
return nonce, str(sign_with_clef(f"{msg}{nonce}", wallet))

return nonce, str(sign_with_key(f"{msg}{nonce}", wallet._private_key.hex()))

Expand Down
41 changes: 41 additions & 0 deletions ocean_lib/web3_internal/clef.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#
# Copyright 2023 Ocean Protocol Foundation
# SPDX-License-Identifier: Apache-2.0
#
import sys
from pathlib import Path

from web3 import HTTPProvider, IPCProvider
from web3.main import Web3


def get_clef_accounts(uri: str = None, timeout: int = 120) -> None:
provider = None
if uri is None:
if sys.platform == "win32":
uri = "http://localhost:8550/"
else:
uri = Path.home().joinpath(".clef/clef.ipc").as_posix()
try:
if Path(uri).exists():
provider = IPCProvider(uri, timeout=timeout)
except OSError:
if uri is not None and uri.startswith("http"):
provider = HTTPProvider(uri, {"timeout": timeout})
if provider is None:
raise ValueError(
"Unknown URI, must be IPC socket path or URL starting with 'http'"
)

response = provider.make_request("account_list", [])
if "error" in response:
raise ValueError(response["error"]["message"])

clef_accounts = [ClefAccount(address, provider) for address in response["result"]]
return clef_accounts


class ClefAccount:
def __init__(self, address: str, provider: [HTTPProvider, IPCProvider]) -> None:
self.address = Web3.to_checksum_address(address)
self.provider = provider
21 changes: 19 additions & 2 deletions ocean_lib/web3_internal/contract_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@

from enforce_typing import enforce_types
from eth_typing import ChecksumAddress
from web3._utils.abi import abi_to_signature
from web3.exceptions import MismatchedABI
from web3.logs import DISCARD
from web3.main import Web3

from ocean_lib.web3_internal.clef import ClefAccount
from ocean_lib.web3_internal.contract_utils import load_contract

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -46,6 +48,7 @@ def wrap(*args, **kwargs):

func = getattr(contract_functions, func_name)
result = func(*args2, **kwargs)
func_signature = abi_to_signature(result.abi)

# view/pure functions don't need "from" key in tx_dict
if not tx_dict and result.abi["stateMutability"] not in ["view", "pure"]:
Expand All @@ -64,8 +67,22 @@ def wrap(*args, **kwargs):
result = result.build_transaction(tx_dict2)

# sign with wallet private key and send transaction
signed_tx = web3.eth.account.sign_transaction(result, wallet._private_key)
receipt = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
if isinstance(wallet, ClefAccount):
for k, v in result.items():
result[k] = Web3.to_hex(v) if not isinstance(v, str) else v

raw_signed_tx = wallet.provider.make_request(
"account_signTransaction", [result, func_signature]
)

raw_signed_tx = raw_signed_tx["result"]["raw"]
else:
signed_tx = web3.eth.account.sign_transaction(
result, wallet._private_key
)
raw_signed_tx = signed_tx.rawTransaction

receipt = web3.eth.send_raw_transaction(raw_signed_tx)

return web3.eth.wait_for_transaction_receipt(receipt)

Expand Down
26 changes: 14 additions & 12 deletions ocean_lib/web3_internal/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from hexbytes.main import HexBytes
from web3.main import Web3

from ocean_lib.web3_internal.clef import ClefAccount

Signature = namedtuple("Signature", ("v", "r", "s"))

logger = logging.getLogger(__name__)
Expand All @@ -29,18 +31,18 @@ def to_32byte_hex(val: int) -> str:
return Web3.to_hex(Web3.to_bytes(val).rjust(32, b"\0"))


# reinstate as part of #1461
# @enforce_types
# def sign_with_clef(message_hash: str, wallet: ClefAccount) -> str:
# message_hash = Web3.solidity_keccak(
# ["bytes"],
# [Web3.to_bytes(text=message_hash)],
# )
#
# orig_sig = wallet._provider.make_request(
# "account_signData", ["data/plain", wallet.address, message_hash.hex()]
# )["result"]
# return orig_sig
@enforce_types
def sign_with_clef(message_hash: str, wallet: ClefAccount) -> str:
message_hash = Web3.solidity_keccak(
["bytes"],
[Web3.to_bytes(text=message_hash)],
)

orig_sig = wallet.provider.make_request(
"account_signData", ["data/plain", wallet.address, message_hash.hex()]
)["result"]

return orig_sig


@enforce_types
Expand Down
11 changes: 11 additions & 0 deletions setup-local.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
##
## Copyright 2023 Ocean Protocol Foundation
## SPDX-License-Identifier: Apache-2.0
##
export REMOTE_TEST_PRIVATE_KEY1=0x8c14238c0fef15881c5638c6e542b2891a9f1cc02d38c69d23724513a5369180

# address: ADDRESS2=0xD6764a9F3387B14692CC783BcF5604a23EBb779f
export REMOTE_TEST_PRIVATE_KEY2=0xc217184f76bdcd66d0070bda50905082a96b714c872a5636982f2b676667f7dd

export WEB3_INFURA_PROJECT_ID="9aa3d95b3bc440fa88ea12eaa4456161"
export MUMBAI_RPC_URL="https://polygon-mumbai.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161"
2 changes: 1 addition & 1 deletion tests/readmes/test_readmes.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def test_script_execution(self, script_name):
"publish-flow-credentials",
"publish-flow-restapi", # TODO: fix and restore
"gas-strategy-remote",
# "using-clef", # TODO: removed original clef readme, to reinstate in #1461
"using-clef", # no way to approve transactions through automatic readme
]

if script_name.replace("test_", "").replace(".py", "") in skippable:
Expand Down

0 comments on commit 70f741c

Please sign in to comment.