Added skill doc to help agents create orders#594
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds a new comprehensive Markdown guide describing how to create, sign, submit, and monitor CoW Protocol orders via the Order Book API, including environment setup, endpoints, contract addresses, EIP-712 signing, fee/slippage guidance, tooling examples, and troubleshooting (documentation only). Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~15 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@skills/cow-swap-order-creator.md`:
- Around line 288-292: Add a guard that validates required environment variables
(at minimum $PRIVATE_KEY) before the Python block that computes FROM (the block
containing Account.from_key('$PRIVATE_KEY')). If a variable is missing or empty,
print a clear error message and exit non‑zero; do this validation after the
config section and before any use of $PRIVATE_KEY (and replicate for any other
env vars used later in the script).
- Around line 310-345: The script writes the signed raw transaction to
/tmp/cow_raw_tx.txt which is insecure; change the flow so the Python snippet
that signs the transaction (symbols: Account.from_key, rpc_call,
signed.raw_transaction.hex) feeds its hex output directly into the curl
eth_sendRawTransaction call instead of creating a world-readable temp
file—either by piping the Python stdout into curl (or command substitution) or
by using a secure temporary file created with mktemp and chmod 600 if a file is
unavoidable; also ensure the shell variable TX_HASH is produced from the curl
response as before without referencing /tmp/cow_raw_tx.txt.
🧹 Nitpick comments (2)
skills/cow-swap-order-creator.md (2)
113-127: Consider using ABI encoding libraries for calldata construction.The manual calldata construction using
printfandtris technically correct but fragile and difficult to maintain. For production use, recommend using proper ABI encoding tools:
- Python:
web3.py's contract interface oreth_abi.encode- JavaScript/TypeScript:
ethers.jsorviemcontract interfacesThis would improve readability and reduce the risk of encoding errors.
349-356: Hardcoded transaction confirmation timeout.The approval confirmation loop waits a maximum of 120 seconds (30 iterations × 4 seconds). During network congestion, this may be insufficient and cause the script to proceed before the approval is confirmed.
Consider:
- Increasing the timeout or making it configurable
- Adding a clear error message if timeout is reached
- Checking the transaction status more explicitly
♻️ Improved confirmation logic
# Wait for confirmation echo "Waiting for confirmation..." - for i in $(seq 1 30); do + MAX_WAIT=60 # iterations (4 min total) + for i in $(seq 1 $MAX_WAIT); do RECEIPT=$(curl -s "$RPC" -X POST \ -H "Content-Type: application/json" \ -d "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionReceipt\",\"params\":[\"$TX_HASH\"],\"id\":1}" \ | python3 -c "import sys,json; r=json.load(sys.stdin)['result']; print(r['status'] if r else 'pending')") [ "$RECEIPT" = "0x1" ] && echo "Approved!" && break + [ "$i" -eq "$MAX_WAIT" ] && echo "Error: Approval tx timed out" && exit 1 sleep 4 done
| FROM=$(python3 -c " | ||
| from eth_account import Account | ||
| a = Account.from_key('$PRIVATE_KEY') | ||
| print(a.address) | ||
| ") |
There was a problem hiding this comment.
Add validation for required environment variables.
The script uses $PRIVATE_KEY without checking if it's set, which will cause a cryptic Python error. Consider adding validation at the start of the script.
🛡️ Proposed validation check
Add after the config section (after line 285):
+# Validate required environment
+if [ -z "${PRIVATE_KEY:-}" ]; then
+ echo "Error: PRIVATE_KEY environment variable is required"
+ exit 1
+fi
+
# Derive from address from private key🤖 Prompt for AI Agents
In `@skills/cow-swap-order-creator.md` around lines 288 - 292, Add a guard that
validates required environment variables (at minimum $PRIVATE_KEY) before the
Python block that computes FROM (the block containing
Account.from_key('$PRIVATE_KEY')). If a variable is missing or empty, print a
clear error message and exit non‑zero; do this validation after the config
section and before any use of $PRIVATE_KEY (and replicate for any other env vars
used later in the script).
anxolin
left a comment
There was a problem hiding this comment.
Great initiative!
I understand it was IA generated, but there were some things that are not completly true or felt strange. I didn't thoroughly review, but went over this superficially.
| @@ -0,0 +1,524 @@ | |||
| --- | |||
| name: cow-swap-order-creator | |||
| description: Create and manage CoW Swap orders through the Order Book API, including quote-to-order conversion, fee=0 signing, slippage and partner-fee adjustments, EIP-712/ERC-1271/PreSign requirements, submission, status checks, and cancellation. Use when an agent needs to place, debug, or automate CoW Protocol orders from backend or script workflows. | |||
There was a problem hiding this comment.
Why not adding website and metadata?
| | Gnosis Chain | `https://api.cow.fi/xdai/api/v1` | 100 | | ||
| | Arbitrum One | `https://api.cow.fi/arbitrum_one/api/v1` | 42161 | | ||
| | Base | `https://api.cow.fi/base/api/v1` | 8453 | | ||
| | Sepolia | `https://api.cow.fi/sepolia/api/v1` | 11155111 | |
There was a problem hiding this comment.
There are a lot of missing networks
There was a problem hiding this comment.
Added networks
| | USDT | `0xdAC17F958D2ee523a2206206994597C13D831ec7` | 6 | | ||
| | DAI | `0x6B175474E89094C44Da98b954EedeAC495271d0F` | 18 | | ||
| | WBTC | `0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599` | 8 | | ||
| | COW | `0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB` | 18 | |
There was a problem hiding this comment.
Didn't review these, but is it good idea to add only these tokens.
I would link the JSON with coswap list
There was a problem hiding this comment.
Added json link while keeping a small list in md
| | Field | Value | | ||
| |---|---| | ||
| | Document | `{"appCode":"agent","metadata":{},"version":"1.6.0"}` | | ||
| | Hash | `0x6a45ce6deb3a32a35a97afa44fd544c8ebc355edc10b2a8e52ef0356b804df45` | |
There was a problem hiding this comment.
Verified. But please suggest better app code
skills/cow-swap-order-creator.md
Outdated
|
|
||
| Wait for confirmation before proceeding. | ||
|
|
||
| > Native ETH cannot be sold directly. Wrap to WETH first. |
There was a problem hiding this comment.
not true! you can use eth flow
skills/cow-swap-order-creator.md
Outdated
| "kind": "sell", | ||
| "from": "0xYOUR_ADDRESS", | ||
| "receiver": "0xYOUR_ADDRESS", | ||
| "appData": "0x6a45ce6deb3a32a35a97afa44fd544c8ebc355edc10b2a8e52ef0356b804df45", |
There was a problem hiding this comment.
I would swear appData needs to be the JSON and appDataHash the hash, but for sure there's sth weird here having the field 2 times with the same value
| ### Step 1 — Request a quote | ||
|
|
||
| ```bash | ||
| curl -s -X POST "$API_BASE/quote" \ |
There was a problem hiding this comment.
I would think an AI is good at reading swagger/open API yaml too. No? shouldt we point to it?
skills/cow-swap-order-creator.md
Outdated
|
|
||
| 1. `signingSellAmount = quote.sellAmount + quote.feeAmount` | ||
| 2. `signingBuyAmount = quote.buyAmount × (10000 − slippageBps) / 10000` | ||
| 3. Optional partner fee: `signingBuyAmount = signingBuyAmount × (10000 − partnerFeeBps) / 10000` |
There was a problem hiding this comment.
weird formulat, I get what aims to say, but mathematically wrong unless partnerFeeBps=0
Proof:
signingBuyAmount = signingBuyAmount × (10000 − partnerFeeBps) / 10000
1 = (10000 − partnerFeeBps) / 10000
10000 = 10000 − partnerFeeBps
0 = partnerFeeBps
|
|
||
| ## appData | ||
|
|
||
| All agent-created orders must include `"appCode": "agent"` in their appData document. |
There was a problem hiding this comment.
Do we want appCode to be an agent? I would use a name more meaningful. If we want to know it was IA generated for some reason, we can do a metadata for that, but not sure we care
There was a problem hiding this comment.
@anxolin can you please suggest what should we keep in appCode.
If we want to know it was IA generated for some reason, we can do a metadata for that, but not sure we care
It would be great if we could capture this, this can then become a metric in understand user category in future
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
skills/cow-swap-order-creator.md (1)
424-456: Consider using secure temporary files.The script writes sensitive trading data (token pairs, amounts, timing) to world-readable
/tmpfiles. While this avoids shell quoting issues, it exposes trading strategy in multi-user environments.🔒 Proposed improvement using mktemp
-# Write quote request to temp file (avoids shell-quoting issues with JSON) +# Write quote request to secure temp file +QUOTE_REQ=$(mktemp) +QUOTE_RESP=$(mktemp) +trap "rm -f $QUOTE_REQ $QUOTE_RESP" EXIT + python3 -c " import json payload = { @@ -440,16 +443,16 @@ 'signingScheme': 'eip712' } -with open('/tmp/cow_quote_req.json', 'w') as f: +with open('$QUOTE_REQ', 'w') as f: json.dump(payload, f) " curl -s -X POST "$API/quote" \ -H "Content-Type: application/json" \ - -d `@/tmp/cow_quote_req.json` > /tmp/cow_quote_resp.json + -d @$QUOTE_REQ > $QUOTE_RESP echo "Quote response:" python3 -c " import json -with open('/tmp/cow_quote_resp.json') as f: +with open('$QUOTE_RESP') as f: data = json.load(f)Apply similar pattern to other temp files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@skills/cow-swap-order-creator.md` around lines 424 - 456, The script writes sensitive data to world-readable static files (/tmp/cow_quote_req.json and /tmp/cow_quote_resp.json); change this to create secure temp files (e.g., use mktemp or Python's tempfile.NamedTemporaryFile with delete=False) and set restrictive permissions (600) before writing/reading, then pass that secure filename into the python3 payload dump and the curl -d/@... call (and remove the temp files after use); update both the python3 snippet that writes the quote payload and the code that reads the curl response to use the secure temp path instead of the hard-coded /tmp filenames.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@skills/cow-swap-order-creator.md`:
- Around line 492-537: The Order struct hash builds struct_hash using
ORDER_TYPE_HASH and several encode_abi calls; the encoding of the validTo field
currently uses encode_abi(['uint256'], [$VALID_TO]) which mismatches the
declared ORDER_TYPE_HASH type of uint32 — change that encode_abi call to
encode_abi(['uint32'], [$VALID_TO]) so the encoded type matches ORDER_TYPE_HASH
and CoW Protocol's reference implementation (look for ORDER_TYPE_HASH,
struct_hash, and the encode_abi for validTo to locate the code).
---
Duplicate comments:
In `@skills/cow-swap-order-creator.md`:
- Around line 388-411: The current flow writes the signed transaction to
/tmp/cow_raw_tx.txt and interpolates $PRIVATE_KEY into a shell-invoked Python
command, exposing secrets in multi-user environments; instead, stop using the
temp file and avoid command-line key exposure by running the Python snippet via
a here-document or command substitution that reads the private key from an
environment variable (e.g., os.environ['PRIVATE_KEY']) and prints the signed raw
transaction to stdout, capture that stdout into the TX variable (replace
/tmp/cow_raw_tx.txt and the inline Python invocation), then use TX in the
eth_sendRawTransaction curl payload to set TX_HASH; ensure no secrets appear in
the process list and remove any writes to /tmp/cow_raw_tx.txt.
- Around line 353-359: Validate that the PRIVATE_KEY env var is set before
deriving FROM and fail fast with a clear error; replace the current python3
invocation that interpolates '$PRIVATE_KEY' (the multi-line block that sets
FROM) with a secure here-document or stdin-based invocation so the private key
is not visible in process arguments—keep the logic that uses
eth_account.Account.from_key inside the python block but read the key from stdin
or os.environ within that block, and ensure FROM is assigned only after
successful validation.
---
Nitpick comments:
In `@skills/cow-swap-order-creator.md`:
- Around line 424-456: The script writes sensitive data to world-readable static
files (/tmp/cow_quote_req.json and /tmp/cow_quote_resp.json); change this to
create secure temp files (e.g., use mktemp or Python's
tempfile.NamedTemporaryFile with delete=False) and set restrictive permissions
(600) before writing/reading, then pass that secure filename into the python3
payload dump and the curl -d/@... call (and remove the temp files after use);
update both the python3 snippet that writes the quote payload and the code that
reads the curl response to use the secure temp path instead of the hard-coded
/tmp filenames.
| # --- Step 3: Sign (EIP-712) --- | ||
| # Manual EIP-712 hashing — works with any eth_account version. | ||
| # Only requires eth_account, eth_utils, and eth_abi (no minimum version). | ||
| SIGNATURE=$(python3 -c " | ||
| from eth_account import Account | ||
| from eth_utils import keccak | ||
| from eth_abi import encode_abi | ||
|
|
||
| # Domain separator | ||
| DOMAIN_TYPE_HASH = keccak(b'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)') | ||
| domain_separator = keccak( | ||
| DOMAIN_TYPE_HASH | ||
| + keccak(b'Gnosis Protocol') | ||
| + keccak(b'v2') | ||
| + encode_abi(['uint256'], [1]) | ||
| + encode_abi(['address'], ['$SETTLEMENT']) | ||
| ) | ||
|
|
||
| # Order struct hash | ||
| ORDER_TYPE_HASH = keccak(b'Order(address sellToken,address buyToken,address receiver,uint256 sellAmount,uint256 buyAmount,uint32 validTo,bytes32 appData,uint256 feeAmount,string kind,bool partiallyFillable,string sellTokenBalance,string buyTokenBalance)') | ||
| struct_hash = keccak( | ||
| ORDER_TYPE_HASH | ||
| + encode_abi(['address'], ['$SELL_TOKEN']) | ||
| + encode_abi(['address'], ['$BUY_TOKEN']) | ||
| + encode_abi(['address'], ['$RECEIVER']) | ||
| + encode_abi(['uint256'], [int('$SIGNING_SELL')]) | ||
| + encode_abi(['uint256'], [int('$SIGNING_BUY')]) | ||
| + encode_abi(['uint256'], [$VALID_TO]) | ||
| + bytes.fromhex('${APP_DATA_HASH#0x}') | ||
| + encode_abi(['uint256'], [0]) | ||
| + keccak(b'sell') | ||
| + encode_abi(['bool'], [False]) | ||
| + keccak(b'erc20') | ||
| + keccak(b'erc20') | ||
| ) | ||
|
|
||
| msg_hash = keccak(b'\x19\x01' + domain_separator + struct_hash) | ||
|
|
||
| acct = Account.from_key('$PRIVATE_KEY') | ||
| signed = acct.signHash(msg_hash) | ||
| sig = signed.signature.hex() | ||
| # Ensure no double 0x prefix | ||
| print(sig if not sig.startswith('0x') else sig[2:]) | ||
| ") | ||
|
|
||
| echo "Signature: 0x$SIGNATURE" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's locate and read the file to understand the full context
find . -name "cow-swap-order-creator.md" -type fRepository: cowprotocol/docs
Length of output: 94
🏁 Script executed:
# Also search for Order struct declarations to understand the type definitions
rg -n "Order\(" --type markdown -A 20 | head -100Repository: cowprotocol/docs
Length of output: 9212
🌐 Web query:
EIP-712 encode_abi uint32 encoding specification
💡 Result:
In EIP-712, encodeData encodes each atomic field into an exactly 32-byte value, using Solidity ABI-style integer encoding (big-endian, left-padded). (eips.ethereum.org)
So a uint32 is encoded the same way as any uint<M> in the ABI spec: big-endian encoding of the integer, left-padded with 0x00 to 32 bytes. (docs.soliditylang.org)
Example (uint32(1)), the 32-byte encoded word is:
0x0000000000000000000000000000000000000000000000000000000000000001
In EIP-712 hashing, this 32-byte word is what gets concatenated (along with other 32-byte encodings) inside encodeData, typically via keccak256(abi.encode(typeHash, ...)). (eips.ethereum.org)
Citations:
- 1: https://eips.ethereum.org/EIPS/eip-712?utm_source=openai
- 2: https://docs.soliditylang.org/en/latest/abi-spec.html
- 3: https://eips.ethereum.org/EIPS/eip-712?utm_source=openai
🌐 Web query:
CoW Protocol order struct EIP-712 validTo field encoding reference implementation
💡 Result:
validTo in CoW Protocol (GPv2) EIP-712 Order: how it’s encoded
Canonical on-chain reference implementation: GPv2Order.sol in cowprotocol/contracts (v1.0.0). It defines the signed order struct as:
validToisuint32inGPv2Order.Dataand in the EIP-712 type string ("uint32 validTo"). [1]
EIP-712 encoding rule that determines the actual bytes hashed:
- In EIP-712
encodeData, every field is encoded as a 32-byte word; integer types are encoded as their value (big-endian) padded to 32 bytes (i.e., effectively likeuint256(value)in ABI encoding). [2]
SovalidTo(auint32) is hashed as a 32-byte left-padded integer word containing that 32-bit value.
Order UID packing (not the EIP-712 hash, but often confused with “encoding”):
- CoW’s
orderUidisabi.encodePacked(orderDigest, owner, validTo)wherevalidTooccupies the final 4 bytes (since it’s a packeduint32). This is implemented inpackOrderUidParams/extractOrderUidParams. [1]
Practical takeaway (off-chain signing)
When producing the EIP-712 typed data, define validTo as type uint32 and pass a Unix timestamp (seconds) that fits in 32 bits; the EIP-712 hash will include it as a 32-byte ABI word per spec. [1][2]
Sources:
[1] CoW Protocol contracts GPv2Order.sol (v1.0.0) reference implementation (struct, TYPE_HASH string includes uint32 validTo, hashing & UID packing). (raw.githubusercontent.com)
[2] EIP-712 spec: encodeData uses 32-byte encoding per member; integers encoded as 256-bit words. (eips.ethereum.org)
Citations:
- 1: https://raw.githubusercontent.com/cowprotocol/contracts/v1.0.0/src/contracts/libraries/GPv2Order.sol
- 2: https://eips.ethereum.org/EIPS/eip-712?utm_source=openai
Fix EIP-712 type encoding for validTo field.
The Order type string at line 511 correctly declares validTo as uint32, but line 519 encodes it as uint256. For semantic correctness and to match CoW Protocol's reference implementation, change:
+ encode_abi(['uint256'], [$VALID_TO])to:
+ encode_abi(['uint32'], [$VALID_TO])While EIP-712 encodes all atomic types as 32-byte values (so both would produce the same bits for valid values), the type argument must match the declared type string for proper alignment with the spec and reference implementations.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@skills/cow-swap-order-creator.md` around lines 492 - 537, The Order struct
hash builds struct_hash using ORDER_TYPE_HASH and several encode_abi calls; the
encoding of the validTo field currently uses encode_abi(['uint256'],
[$VALID_TO]) which mismatches the declared ORDER_TYPE_HASH type of uint32 —
change that encode_abi call to encode_abi(['uint32'], [$VALID_TO]) so the
encoded type matches ORDER_TYPE_HASH and CoW Protocol's reference implementation
(look for ORDER_TYPE_HASH, struct_hash, and the encode_abi for validTo to locate
the code).
Description
We want to make it easier for AI agents to submit orders using our api. Added this md doc which contains details of how order can be created. We can decide to add other features our api offers as we progress.
Changes
Summary by CodeRabbit