From 3a0178658a64f861fc014454bb688cd0e3817ede Mon Sep 17 00:00:00 2001 From: Forrest Chew Date: Sat, 2 Aug 2025 21:23:42 -0600 Subject: [PATCH 1/5] feat: option to derive account from an encrypted keystore --- examples/config.json.example | 5 ++++- examples/example_utils.py | 35 ++++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/examples/config.json.example b/examples/config.json.example index 4e442544..e94ee91c 100644 --- a/examples/config.json.example +++ b/examples/config.json.example @@ -4,11 +4,14 @@ Fill in your secret key e.g. 0x0000000000000000000000000000000000000000000000000000000000000000 If you are using an Agent/API Wallet you MUST also specify the public address of your account, not the address of the Agent/API Wallet. - Otherwise, feel free to leave it blank and it will be automatically derived from the secret key. + Otherwise, feel free to leave it blank and it will be automatically derived from the secret key. Alternatively, + you can specify a local `keystore_path` which will prompt you for the keystore password interactively. + Note: If both `secret_key` and `keystore_path` are provided, the keystore will take precedence. You can also populate the "multi_sig" section with the secret keys of the authorized user wallets that you wish to sign multi-sig actions for. ", + "keystore_path": "", "secret_key": "", "account_address": "", "multi_sig": { diff --git a/examples/example_utils.py b/examples/example_utils.py index 907c223d..ca04f905 100644 --- a/examples/example_utils.py +++ b/examples/example_utils.py @@ -1,5 +1,6 @@ import json import os +import getpass import eth_account from eth_account.signers.local import LocalAccount @@ -12,13 +13,7 @@ def setup(base_url=None, skip_ws=False, perp_dexs=None): config_path = os.path.join(os.path.dirname(__file__), "config.json") with open(config_path) as f: config = json.load(f) - account: LocalAccount = eth_account.Account.from_key(config["secret_key"]) - address = config["account_address"] - if address == "": - address = account.address - print("Running with account address:", address) - if address != account.address: - print("Running with agent address:", account.address) + account, address = create_account(config) info = Info(base_url, skip_ws, perp_dexs=perp_dexs) user_state = info.user_state(address) spot_user_state = info.spot_user_state(address) @@ -31,6 +26,32 @@ def setup(base_url=None, skip_ws=False, perp_dexs=None): exchange = Exchange(account, base_url, account_address=address, perp_dexs=perp_dexs) return address, info, exchange +def create_account(config): + if config["keystore_path"]: + print("Using keystore to create account...") + keystore_path = config["keystore_path"] + keystore_path = os.path.expanduser(keystore_path) + if not os.path.isabs(keystore_path): + keystore_path = os.path.join(os.path.dirname(__file__), keystore_path) + if not os.path.exists(keystore_path): + raise FileNotFoundError(f"Keystore file not found: {keystore_path}") + if not os.path.isfile(keystore_path): + raise ValueError(f"Keystore path is not a file: {keystore_path}") + with open(keystore_path) as f: + keystore = json.load(f) + password = getpass.getpass("Enter keystore password: ") + private_key = eth_account.Account.decrypt(keystore, password) + account: LocalAccount = eth_account.Account.from_key(private_key) + else: + account: LocalAccount = eth_account.Account.from_key(config["secret_key"]) + address = config["account_address"] + if address == "": + address = account.address + print("Running with account address:", address) + if address != account.address: + print("Running with agent address:", account.address) + return account, address + def setup_multi_sig_wallets(): config_path = os.path.join(os.path.dirname(__file__), "config.json") From 4d2f31c613e3a712a45750205b07bddd7eabcb27 Mon Sep 17 00:00:00 2001 From: Forrest Chew Date: Sat, 2 Aug 2025 23:09:19 -0600 Subject: [PATCH 2/5] chore: consistent spacing & removes unnecessary print() --- examples/example_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example_utils.py b/examples/example_utils.py index ca04f905..7e6a91d3 100644 --- a/examples/example_utils.py +++ b/examples/example_utils.py @@ -26,9 +26,9 @@ def setup(base_url=None, skip_ws=False, perp_dexs=None): exchange = Exchange(account, base_url, account_address=address, perp_dexs=perp_dexs) return address, info, exchange + def create_account(config): if config["keystore_path"]: - print("Using keystore to create account...") keystore_path = config["keystore_path"] keystore_path = os.path.expanduser(keystore_path) if not os.path.isabs(keystore_path): From cdbcfcb9d3b8932fea05487f79dbcc451f3297e3 Mon Sep 17 00:00:00 2001 From: Forrest Chew Date: Mon, 4 Aug 2025 12:14:07 -0600 Subject: [PATCH 3/5] fix: imports ordering & var assignment to satisfy the pre-commit check --- examples/example_utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/example_utils.py b/examples/example_utils.py index 7e6a91d3..bc636b99 100644 --- a/examples/example_utils.py +++ b/examples/example_utils.py @@ -1,6 +1,6 @@ +import getpass import json import os -import getpass import eth_account from eth_account.signers.local import LocalAccount @@ -28,6 +28,7 @@ def setup(base_url=None, skip_ws=False, perp_dexs=None): def create_account(config): + account: LocalAccount if config["keystore_path"]: keystore_path = config["keystore_path"] keystore_path = os.path.expanduser(keystore_path) @@ -41,9 +42,9 @@ def create_account(config): keystore = json.load(f) password = getpass.getpass("Enter keystore password: ") private_key = eth_account.Account.decrypt(keystore, password) - account: LocalAccount = eth_account.Account.from_key(private_key) + account = eth_account.Account.from_key(private_key) else: - account: LocalAccount = eth_account.Account.from_key(config["secret_key"]) + account = eth_account.Account.from_key(config["secret_key"]) address = config["account_address"] if address == "": address = account.address From 149266248a315a9a5b7eae9146434c85291902e7 Mon Sep 17 00:00:00 2001 From: Forrest Chew Date: Tue, 5 Aug 2025 11:13:36 -0600 Subject: [PATCH 4/5] fix: addresses review --- examples/config.json.example | 2 +- examples/example_utils.py | 29 ++++++++++++++--------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/examples/config.json.example b/examples/config.json.example index e94ee91c..e7651794 100644 --- a/examples/config.json.example +++ b/examples/config.json.example @@ -6,7 +6,7 @@ address of the Agent/API Wallet. Otherwise, feel free to leave it blank and it will be automatically derived from the secret key. Alternatively, you can specify a local `keystore_path` which will prompt you for the keystore password interactively. - Note: If both `secret_key` and `keystore_path` are provided, the keystore will take precedence. + Note: If both `secret_key` and `keystore_path` are provided, the `secret_key` will take precedence. You can also populate the "multi_sig" section with the secret keys of the authorized user wallets that you wish to sign multi-sig actions for. diff --git a/examples/example_utils.py b/examples/example_utils.py index bc636b99..f93be3a1 100644 --- a/examples/example_utils.py +++ b/examples/example_utils.py @@ -13,7 +13,14 @@ def setup(base_url=None, skip_ws=False, perp_dexs=None): config_path = os.path.join(os.path.dirname(__file__), "config.json") with open(config_path) as f: config = json.load(f) - account, address = create_account(config) + secret_key = get_secret_key(config) + account: LocalAccount = eth_account.Account.from_key(secret_key) + address = config["account_address"] + if address == "": + address = account.address + print("Running with account address:", address) + if address != account.address: + print("Running with agent address:", account.address) info = Info(base_url, skip_ws, perp_dexs=perp_dexs) user_state = info.user_state(address) spot_user_state = info.spot_user_state(address) @@ -27,9 +34,10 @@ def setup(base_url=None, skip_ws=False, perp_dexs=None): return address, info, exchange -def create_account(config): - account: LocalAccount - if config["keystore_path"]: +def get_secret_key(config): + if config["secret_key"]: + secret_key = config["secret_key"] + else: keystore_path = config["keystore_path"] keystore_path = os.path.expanduser(keystore_path) if not os.path.isabs(keystore_path): @@ -41,17 +49,8 @@ def create_account(config): with open(keystore_path) as f: keystore = json.load(f) password = getpass.getpass("Enter keystore password: ") - private_key = eth_account.Account.decrypt(keystore, password) - account = eth_account.Account.from_key(private_key) - else: - account = eth_account.Account.from_key(config["secret_key"]) - address = config["account_address"] - if address == "": - address = account.address - print("Running with account address:", address) - if address != account.address: - print("Running with agent address:", account.address) - return account, address + secret_key = eth_account.Account.decrypt(keystore, password) + return secret_key def setup_multi_sig_wallets(): From 2dacc2c888456e8cb528a17262606fbc0b7df40c Mon Sep 17 00:00:00 2001 From: Forrest Chew Date: Tue, 5 Aug 2025 11:50:10 -0600 Subject: [PATCH 5/5] chore: removes unnecessary var --- examples/example_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/example_utils.py b/examples/example_utils.py index f93be3a1..390a5433 100644 --- a/examples/example_utils.py +++ b/examples/example_utils.py @@ -13,8 +13,7 @@ def setup(base_url=None, skip_ws=False, perp_dexs=None): config_path = os.path.join(os.path.dirname(__file__), "config.json") with open(config_path) as f: config = json.load(f) - secret_key = get_secret_key(config) - account: LocalAccount = eth_account.Account.from_key(secret_key) + account: LocalAccount = eth_account.Account.from_key(get_secret_key(config)) address = config["account_address"] if address == "": address = account.address