Skip to content

Commit

Permalink
Merge pull request #258 from polywrap/nerfzael/multi-send-tx-service
Browse files Browse the repository at this point in the history
Multi-send tx service
  • Loading branch information
nerfZael authored Jun 13, 2024
2 parents bf123e1 + cc292d4 commit 9984baa
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 12 deletions.
2 changes: 1 addition & 1 deletion autotx/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def send_transactions(task_id: str, model: models.SendTransactionsParams, author
try:
app_config = AppConfig.load(smart_account_addr=task.address, subsidized_chain_id=task.chain_id, agent=agent)

app_config.manager.send_tx_batch(
app_config.manager.send_multisend_tx_batch(
task.transactions,
require_approval=False,
)
Expand Down
104 changes: 94 additions & 10 deletions autotx/utils/ethereum/SafeManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def deploy_multicall(self) -> None:
multicall_addr = deploy_multicall(self.client, self.dev_account)
self.connect_multicall(multicall_addr)

def build_multisend_tx(self, txs: list[TxParams], safe_nonce: Optional[int] = None) -> SafeTx:
def build_multisend_tx(self, txs: list[TxParams | dict[str, Any]], safe_nonce: Optional[int] = None) -> SafeTx:
if not self.multisend:
raise Exception("No multisend contract address has been set to SafeManager")

Expand All @@ -136,7 +136,7 @@ def build_multisend_tx(self, txs: list[TxParams], safe_nonce: Optional[int] = No

return safe_tx

def build_tx(self, tx: TxParams, safe_nonce: Optional[int] = None) -> SafeTx:
def build_tx(self, tx: TxParams | dict[str, Any], safe_nonce: Optional[int] = None, skip_estimate_gas: bool = False) -> SafeTx:
safe_tx = SafeTx(
self.client,
self.address.hex,
Expand All @@ -151,12 +151,14 @@ def build_tx(self, tx: TxParams, safe_nonce: Optional[int] = None) -> SafeTx:
self.address.hex,
safe_nonce=self.track_nonce(safe_nonce),
)
safe_tx.safe_tx_gas = self.safe.estimate_tx_gas(safe_tx.to, safe_tx.value, safe_tx.data, safe_tx.operation)
safe_tx.base_gas = self.safe.estimate_tx_base_gas(safe_tx.to, safe_tx.value, safe_tx.data, safe_tx.operation, NULL_ADDRESS, safe_tx.safe_tx_gas)

if not skip_estimate_gas:
safe_tx.safe_tx_gas = self.safe.estimate_tx_gas(safe_tx.to, safe_tx.value, safe_tx.data, safe_tx.operation)
safe_tx.base_gas = self.safe.estimate_tx_base_gas(safe_tx.to, safe_tx.value, safe_tx.data, safe_tx.operation, NULL_ADDRESS, safe_tx.safe_tx_gas)

return safe_tx

def execute_tx(self, tx: TxParams, safe_nonce: Optional[int] = None) -> HexBytes:
def execute_tx(self, tx: TxParams | dict[str, Any], safe_nonce: Optional[int] = None) -> HexBytes:
if not self.dev_account:
raise ValueError("Dev account not set. This function should not be called in production.")

Expand All @@ -182,7 +184,7 @@ def execute_tx(self, tx: TxParams, safe_nonce: Optional[int] = None) -> HexBytes
raise Exception("Unknown error executing transaction", e)


def execute_multisend_tx(self, txs: list[TxParams], safe_nonce: Optional[int] = None) -> HexBytes:
def execute_multisend_tx(self, txs: list[TxParams | dict[str, Any]], safe_nonce: Optional[int] = None) -> HexBytes:
if not self.dev_account:
raise ValueError("Dev account not set. This function should not be called in production.")

Expand All @@ -198,20 +200,20 @@ def execute_multisend_tx(self, txs: list[TxParams], safe_nonce: Optional[int] =

return tx_hash

def post_transaction(self, tx: TxParams, safe_nonce: Optional[int] = None) -> None:
def post_transaction(self, tx: TxParams | dict[str, Any], safe_nonce: Optional[int] = None) -> None:
if not self.network:
raise Exception("Network not defined for transaction service")

ts_api = TransactionServiceApi(
self.network, ethereum_client=self.client, base_url=self.transaction_service_url
)

safe_tx = self.build_tx(tx, safe_nonce)
safe_tx = self.build_tx(tx, safe_nonce, skip_estimate_gas=True)
safe_tx.sign(self.agent.key.hex())

ts_api.post_transaction(safe_tx)

def post_multisend_transaction(self, txs: list[TxParams], safe_nonce: Optional[int] = None) -> None:
def post_multisend_transaction(self, txs: list[TxParams | dict[str, Any]], safe_nonce: Optional[int] = None) -> None:
if not self.network:
raise Exception("Network not defined for transaction service")

Expand All @@ -231,7 +233,21 @@ def send_tx(self, tx: TxParams | dict[str, Any], safe_nonce: Optional[int] = Non
else:
hash = self.execute_tx(cast(TxParams, tx), safe_nonce)
return hash.hex()


def send_multisend_tx(self, txs: list[TxParams | dict[str, Any]], safe_nonce: Optional[int] = None) -> str | None:
if self.use_tx_service:
if len(txs) == 1:
self.post_transaction(txs[0], safe_nonce)
elif len(txs) > 1:
self.post_multisend_transaction(txs, safe_nonce)
return None
else:
if len(txs) == 1:
hash = self.execute_tx(txs[0], safe_nonce)
elif len(txs) > 1:
hash = self.execute_multisend_tx(txs, safe_nonce)
return hash.hex()

def send_tx_batch(self, txs: list[models.Transaction], require_approval: bool, safe_nonce: Optional[int] = None) -> bool | str: # True if sent, False if declined, str if feedback
print("=" * 50)

Expand Down Expand Up @@ -306,6 +322,74 @@ def send_tx_batch(self, txs: list[models.Transaction], require_approval: bool, s

return True

def send_multisend_tx_batch(self, txs: list[models.Transaction], require_approval: bool, safe_nonce: Optional[int] = None) -> bool | str: # True if sent, False if declined, str if feedback
print("=" * 50)

if not txs:
print("No transactions to send.")
return True

transactions_info = "\n".join(
[
f"{i + 1}. {tx.summary}"
for i, tx in enumerate(txs)
]
)

print(f"Prepared transactions:\n{transactions_info}")

if self.use_tx_service:
if require_approval:
response = input("Do you want the above transactions to be sent to your smart account?\nRespond (y/n) or write feedback: ")

if response.lower() == "n" or response.lower() == "no":
print("Transactions not sent to your smart account (declined).")

return False
elif response.lower() != "y" and response.lower() != "yes":

return response
else:
print("Non-interactive mode enabled. Transactions will be sent to your smart account without approval.")

print("Sending batched transactions to your smart account...")

self.send_multisend_tx([prepared_tx.params for prepared_tx in txs], safe_nonce)

if len(txs) == 1:
print("Transaction sent to your smart account for signing.")
else:
print("Transactions sent as a single multi-send transaction to your smart account for signing.")

return True
else:
if require_approval:
response = input("Do you want to execute the above transactions?\nRespond (y/n) or write feedback: ")

if response.lower() == "n" or response.lower() == "no":
print("Transactions not executed (declined).")

return False
elif response.lower() != "y" and response.lower() != "yes":

return response
else:
print("Non-interactive mode enabled. Transactions will be executed without approval.")

print("Executing transactions...")

try:
self.send_multisend_tx([prepared_tx.params for prepared_tx in txs], safe_nonce)
except ExecutionRevertedError as e:
raise Exception(f"Executing transactions failed with error: {e}")

if len(txs) == 1:
print("Transaction executed.")
else:
print("Transactions executed as a single multi-send transaction.")

return True

def send_empty_tx(self, safe_nonce: Optional[int] = None) -> str | None:
tx: TxParams = {
"to": self.address.hex,
Expand Down
2 changes: 1 addition & 1 deletion autotx/wallets/safe_smart_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ def on_transactions_prepared(self, txs: list[models.Transaction]) -> None:
pass

def on_transactions_ready(self, txs: list[models.Transaction]) -> bool | str:
return self.manager.send_tx_batch(txs, not self.auto_submit_tx)
return self.manager.send_multisend_tx_batch(txs, not self.auto_submit_tx)

0 comments on commit 9984baa

Please sign in to comment.