-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Post order using cow-py #580
base: main
Are you sure you want to change the base?
Changes from 1 commit
05c64c3
0ce94cb
d16a7e5
9da31c5
ea544b0
c87e583
5a26dd5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,153 @@ | ||||||||||||||||||||||||||||||||||||||
import asyncio | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
from cow_py.common.chains import Chain | ||||||||||||||||||||||||||||||||||||||
from cow_py.common.config import SupportedChainId | ||||||||||||||||||||||||||||||||||||||
from cow_py.common.constants import CowContractAddress | ||||||||||||||||||||||||||||||||||||||
from cow_py.contracts.domain import domain | ||||||||||||||||||||||||||||||||||||||
from cow_py.contracts.order import Order | ||||||||||||||||||||||||||||||||||||||
from cow_py.contracts.sign import EcdsaSignature, SigningScheme | ||||||||||||||||||||||||||||||||||||||
from cow_py.contracts.sign import sign_order as _sign_order | ||||||||||||||||||||||||||||||||||||||
from cow_py.order_book.api import OrderBookApi | ||||||||||||||||||||||||||||||||||||||
from cow_py.order_book.config import Envs, OrderBookAPIConfigFactory | ||||||||||||||||||||||||||||||||||||||
from cow_py.order_book.generated.model import ( | ||||||||||||||||||||||||||||||||||||||
UID, | ||||||||||||||||||||||||||||||||||||||
OrderCreation, | ||||||||||||||||||||||||||||||||||||||
OrderQuoteRequest, | ||||||||||||||||||||||||||||||||||||||
OrderQuoteResponse, | ||||||||||||||||||||||||||||||||||||||
OrderQuoteSide1, | ||||||||||||||||||||||||||||||||||||||
OrderQuoteSideKindSell, | ||||||||||||||||||||||||||||||||||||||
TokenAmount, | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
from eth_account.signers.local import LocalAccount | ||||||||||||||||||||||||||||||||||||||
from pydantic import BaseModel | ||||||||||||||||||||||||||||||||||||||
from web3 import Web3 | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.config import APIKeys | ||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.gtypes import ChecksumAddress, xDai | ||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain | ||||||||||||||||||||||||||||||||||||||
from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
ZERO_APP_DATA = "0x0000000000000000000000000000000000000000000000000000000000000000" | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
class CompletedOrder(BaseModel): | ||||||||||||||||||||||||||||||||||||||
uid: UID | ||||||||||||||||||||||||||||||||||||||
url: str | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def swap_tokens( | ||||||||||||||||||||||||||||||||||||||
amount: xDai, | ||||||||||||||||||||||||||||||||||||||
sell_token: ChecksumAddress, | ||||||||||||||||||||||||||||||||||||||
buy_token: ChecksumAddress, | ||||||||||||||||||||||||||||||||||||||
api_keys: APIKeys, | ||||||||||||||||||||||||||||||||||||||
chain: Chain = Chain.GNOSIS, | ||||||||||||||||||||||||||||||||||||||
app_data: str = ZERO_APP_DATA, | ||||||||||||||||||||||||||||||||||||||
env: Envs = "prod", | ||||||||||||||||||||||||||||||||||||||
web3: Web3 | None = None, | ||||||||||||||||||||||||||||||||||||||
) -> CompletedOrder: | ||||||||||||||||||||||||||||||||||||||
# CoW library uses async, so we need to wrap the call in asyncio.run for us to use it. | ||||||||||||||||||||||||||||||||||||||
return asyncio.run( | ||||||||||||||||||||||||||||||||||||||
swap_tokens_async( | ||||||||||||||||||||||||||||||||||||||
amount, sell_token, buy_token, api_keys, chain, app_data, env, web3 | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid using Using Apply this diff to refactor the function: -def swap_tokens(
+async def swap_tokens(
amount: xDai,
sell_token: ChecksumAddress,
buy_token: ChecksumAddress,
api_keys: APIKeys,
chain: Chain = Chain.GNOSIS,
app_data: str = ZERO_APP_DATA,
env: Envs = "prod",
web3: Web3 | None = None,
) -> CompletedOrder:
- # CoW library uses async, so we need to wrap the call in asyncio.run for us to use it.
- return asyncio.run(
- swap_tokens_async(
- amount, sell_token, buy_token, api_keys, chain, app_data, env, web3
- )
- )
+ return await swap_tokens_async(
+ amount, sell_token, buy_token, api_keys, chain, app_data, env, web3
+ ) 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
async def swap_tokens_async( | ||||||||||||||||||||||||||||||||||||||
amount: xDai, | ||||||||||||||||||||||||||||||||||||||
sell_token: ChecksumAddress, | ||||||||||||||||||||||||||||||||||||||
buy_token: ChecksumAddress, | ||||||||||||||||||||||||||||||||||||||
api_keys: APIKeys, | ||||||||||||||||||||||||||||||||||||||
chain: Chain, | ||||||||||||||||||||||||||||||||||||||
app_data: str, | ||||||||||||||||||||||||||||||||||||||
env: Envs, | ||||||||||||||||||||||||||||||||||||||
web3: Web3 | None, | ||||||||||||||||||||||||||||||||||||||
) -> CompletedOrder: | ||||||||||||||||||||||||||||||||||||||
account = api_keys.get_account() | ||||||||||||||||||||||||||||||||||||||
amount_wei = xdai_to_wei(amount) | ||||||||||||||||||||||||||||||||||||||
chain_id = SupportedChainId(chain.value[0]) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
order_book_api = OrderBookApi(OrderBookAPIConfigFactory.get_config(env, chain_id)) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
# Approve the CoW Swap Vault Relayer to get the sell token. | ||||||||||||||||||||||||||||||||||||||
ContractERC20OnGnosisChain(address=sell_token).approve( | ||||||||||||||||||||||||||||||||||||||
api_keys, | ||||||||||||||||||||||||||||||||||||||
Web3.to_checksum_address(CowContractAddress.VAULT_RELAYER.value), | ||||||||||||||||||||||||||||||||||||||
amount_wei=amount_wei, | ||||||||||||||||||||||||||||||||||||||
web3=web3, | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling for token approval The Apply this diff to add error handling: try:
ContractERC20OnGnosisChain(address=sell_token).approve(
api_keys,
Web3.to_checksum_address(CowContractAddress.VAULT_RELAYER.value),
amount_wei=amount_wei,
web3=web3,
)
except Exception as e:
# Handle exception appropriately
raise RuntimeError(f"Token approval failed: {e}") |
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
order_quote_request = OrderQuoteRequest.model_validate( | ||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||
"sellToken": sell_token, | ||||||||||||||||||||||||||||||||||||||
"buyToken": buy_token, | ||||||||||||||||||||||||||||||||||||||
"from": api_keys.bet_from_address, | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
order_side = OrderQuoteSide1( | ||||||||||||||||||||||||||||||||||||||
kind=OrderQuoteSideKindSell.sell, | ||||||||||||||||||||||||||||||||||||||
sellAmountBeforeFee=TokenAmount(str(amount_wei)), | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
order_quote = await get_order_quote(order_quote_request, order_side, order_book_api) | ||||||||||||||||||||||||||||||||||||||
order = Order( | ||||||||||||||||||||||||||||||||||||||
sell_token=sell_token, | ||||||||||||||||||||||||||||||||||||||
buy_token=buy_token, | ||||||||||||||||||||||||||||||||||||||
receiver=api_keys.bet_from_address, | ||||||||||||||||||||||||||||||||||||||
valid_to=order_quote.quote.validTo, | ||||||||||||||||||||||||||||||||||||||
app_data=app_data, | ||||||||||||||||||||||||||||||||||||||
sell_amount=amount_wei, # Since it is a sell order, the sellAmountBeforeFee is the same as the sellAmount. | ||||||||||||||||||||||||||||||||||||||
buy_amount=int(order_quote.quote.buyAmount.root), | ||||||||||||||||||||||||||||||||||||||
fee_amount=0, # CoW Swap does not charge fees. | ||||||||||||||||||||||||||||||||||||||
kind=OrderQuoteSideKindSell.sell.value, | ||||||||||||||||||||||||||||||||||||||
sell_token_balance="erc20", | ||||||||||||||||||||||||||||||||||||||
buy_token_balance="erc20", | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
signature = sign_order(chain, account, order) | ||||||||||||||||||||||||||||||||||||||
order_uid = await post_order(api_keys, order, signature, order_book_api) | ||||||||||||||||||||||||||||||||||||||
order_link = order_book_api.get_order_link(order_uid) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
return CompletedOrder(uid=order_uid, url=order_link) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
async def get_order_quote( | ||||||||||||||||||||||||||||||||||||||
order_quote_request: OrderQuoteRequest, | ||||||||||||||||||||||||||||||||||||||
order_side: OrderQuoteSide1, | ||||||||||||||||||||||||||||||||||||||
order_book_api: OrderBookApi, | ||||||||||||||||||||||||||||||||||||||
) -> OrderQuoteResponse: | ||||||||||||||||||||||||||||||||||||||
return await order_book_api.post_quote(order_quote_request, order_side) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def sign_order(chain: Chain, account: LocalAccount, order: Order) -> EcdsaSignature: | ||||||||||||||||||||||||||||||||||||||
order_domain = domain( | ||||||||||||||||||||||||||||||||||||||
chain=chain, verifying_contract=CowContractAddress.SETTLEMENT_CONTRACT.value | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
return _sign_order(order_domain, order, account, SigningScheme.EIP712) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
async def post_order( | ||||||||||||||||||||||||||||||||||||||
api_keys: APIKeys, | ||||||||||||||||||||||||||||||||||||||
order: Order, | ||||||||||||||||||||||||||||||||||||||
signature: EcdsaSignature, | ||||||||||||||||||||||||||||||||||||||
order_book_api: OrderBookApi, | ||||||||||||||||||||||||||||||||||||||
) -> UID: | ||||||||||||||||||||||||||||||||||||||
order_creation = OrderCreation.model_validate( | ||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||
"from": api_keys.bet_from_address, | ||||||||||||||||||||||||||||||||||||||
"sellToken": order.sellToken, | ||||||||||||||||||||||||||||||||||||||
"buyToken": order.buyToken, | ||||||||||||||||||||||||||||||||||||||
"sellAmount": str(order.sellAmount), | ||||||||||||||||||||||||||||||||||||||
"feeAmount": str(order.feeAmount), | ||||||||||||||||||||||||||||||||||||||
"buyAmount": str(order.buyAmount), | ||||||||||||||||||||||||||||||||||||||
"validTo": order.validTo, | ||||||||||||||||||||||||||||||||||||||
"kind": order.kind, | ||||||||||||||||||||||||||||||||||||||
"partiallyFillable": order.partiallyFillable, | ||||||||||||||||||||||||||||||||||||||
"appData": order.appData, | ||||||||||||||||||||||||||||||||||||||
"signature": signature.data, | ||||||||||||||||||||||||||||||||||||||
"signingScheme": "eip712", | ||||||||||||||||||||||||||||||||||||||
"receiver": order.receiver, | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
return await order_book_api.post_order(order_creation) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import pytest | ||
from web3 import Web3 | ||
|
||
from prediction_market_agent_tooling.config import APIKeys | ||
from prediction_market_agent_tooling.gtypes import xdai_type | ||
from prediction_market_agent_tooling.markets.omen.omen_contracts import ( | ||
WrappedxDaiContract, | ||
sDaiContract, | ||
) | ||
from prediction_market_agent_tooling.tools.cow_order import swap_tokens | ||
|
||
|
||
def test_swap_tokens(local_web3: Web3, test_keys: APIKeys) -> None: | ||
with pytest.raises(Exception) as e: | ||
swap_tokens( | ||
amount=xdai_type(1), | ||
sell_token=WrappedxDaiContract().address, | ||
buy_token=sDaiContract().address, | ||
api_keys=test_keys, | ||
env="staging", | ||
web3=local_web3, | ||
) | ||
# This is raised in `post_order` which is last call when swapping tokens, anvil's accounts don't have any balance on real chain, so this is expected, | ||
# but still, it tests that all the logic behind calling CoW APIs is working correctly. | ||
assert "InsufficientBalance" in str(e) | ||
Comment on lines
+23
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Secure handling of private keys
Ensure that the private key is never logged or exposed in error messages. Consider adding validation to check if the private key is in the correct format and handle any exceptions securely.