Skip to main content

Overview

App Data allows you to attach arbitrary metadata to your CoW Protocol orders. This can include referral information, custom UI parameters, partner fees, and other application-specific data. The SDK provides utilities for creating, encoding, and decoding app data.

What is App Data?

App Data consists of:
  • AppDataDoc: A JSON document containing your metadata
  • AppDataHash: A keccak256 hash of the document (used in orders)
  • AppDataCid: An IPFS Content Identifier for the document

Creating App Data

Basic App Data

from cowdao_cowpy.app_data.app_data_doc import AppDataDoc

# Create app data with default values
app_data = AppDataDoc()

# Get the hex hash (use this in orders)
app_data_hash = app_data.to_hex()
print(f"App data hash: {app_data_hash}")

# Convert to IPFS CID
app_data_cid = app_data.to_cid()
print(f"IPFS CID: {app_data_cid}")

Custom App Data

# Create custom app data
custom_data = {
    "appCode": "MyTradingApp",
    "metadata": {
        "referrer": {
            "address": "0x1234567890abcdef1234567890abcdef12345678"
        },
        "utm": {
            "utmSource": "telegram",
            "utmMedium": "social",
            "utmCampaign": "spring2024",
            "utmContent": "My custom message"
        }
    },
    "version": "1.0.0"
}

app_data = AppDataDoc(app_data_doc=custom_data)
app_data_hash = app_data.to_hex()
print(f"Custom app data hash: {app_data_hash}")

Using the Utility Functions

The SDK provides high-level functions for common app data operations:

Generate App Data

from cowdao_cowpy.app_data.utils import generate_app_data, PartnerFee

# Simple app data
result = generate_app_data(
    app_code="MyApp",
    graffiti="Hello CoW Protocol!"
)

print(f"App data hash: {result.app_data_hash.root}")

With Referrer Address

# App data with referrer
result = generate_app_data(
    app_code="MyApp",
    referrer_address="0x1234567890abcdef1234567890abcdef12345678",
    graffiti="Referral trade"
)

print(f"App data with referrer: {result.app_data_hash.root}")

With Partner Fees

# App data with partner fee
partner_fee = PartnerFee(
    bps=50,  # 0.5% fee (50 basis points)
    recipient="0x1234567890abcdef1234567890abcdef12345679"
)

result = generate_app_data(
    app_code="MyApp",
    partner_fee=partner_fee,
    graffiti="Partner trade"
)

print(f"App data with fee: {result.app_data_hash.root}")

Complete Example

# All options combined
partner_fee = PartnerFee(
    bps=100,  # 1% fee
    recipient="0xfee...address"
)

result = generate_app_data(
    app_code="MyTradingBot",
    referrer_address="0xreferrer...address",
    partner_fee=partner_fee,
    graffiti="Automated trade #42"
)

app_data_object = result.app_data_object
app_data_hash = result.app_data_hash

print(f"Hash: {app_data_hash.root}")

Uploading App Data to Order Book

App data needs to be uploaded to the CoW Protocol API:
import asyncio
from cowdao_cowpy.order_book.api import OrderBookApi
from cowdao_cowpy.app_data.utils import generate_app_data

async def upload_app_data():
    # Generate app data
    result = generate_app_data(
        app_code="MyApp",
        graffiti="Test order"
    )
    
    # Initialize API
    order_book_api = OrderBookApi()
    
    # Upload app data
    uploaded_hash = await order_book_api.put_app_data(
        app_data=result.app_data_object,
        app_data_hash=result.app_data_hash
    )
    
    print(f"Uploaded app data hash: {uploaded_hash.root}")
    
    return uploaded_hash

asyncio.run(upload_app_data())

Retrieving App Data

import asyncio
from cowdao_cowpy.order_book.api import OrderBookApi
from cowdao_cowpy.order_book.generated.model import AppDataHash

async def retrieve_app_data():
    order_book_api = OrderBookApi()
    
    # Retrieve app data by hash
    app_data_hash = AppDataHash(
        root="0x971c41b97f59534448ab833b0d83f755a4bc5c29f92b01776faa3699fcb0eeae"
    )
    
    app_data = await order_book_api.get_app_data(app_data_hash)
    print(f"Retrieved app data: {app_data}")

asyncio.run(retrieve_app_data())

Working with IPFS CIDs

Converting Hash to CID

from cowdao_cowpy.app_data.app_data_hex import AppDataHex

# Convert hex hash to CID
app_data_hex = AppDataHex(
    "971c41b97f59534448ab833b0d83f755a4bc5c29f92b01776faa3699fcb0eeae"  # Without 0x prefix
)

cid = app_data_hex.to_cid()
print(f"IPFS CID: {cid}")

Converting CID to Hash

from cowdao_cowpy.app_data.app_data_cid import AppDataCid

# Convert CID back to hex hash
app_data_cid = AppDataCid(
    "f01551220971c41b97f59534448ab833b0d83f755a4bc5c29f92b01776faa3699fcb0eeae"
)

hex_hash = app_data_cid.to_hex()
print(f"Hex hash: {hex_hash}")

Fetching from IPFS

import asyncio
from cowdao_cowpy.app_data.app_data_cid import AppDataCid

async def fetch_from_ipfs():
    app_data_cid = AppDataCid(
        "f01551220971c41b97f59534448ab833b0d83f755a4bc5c29f92b01776faa3699fcb0eeae"
    )
    
    # Fetch document from IPFS
    doc = await app_data_cid.to_doc()
    print(f"Document from IPFS: {doc}")

asyncio.run(fetch_from_ipfs())

Building and Posting for All Chains

from cowdao_cowpy.app_data.utils import (
    build_all_app_codes,
    PartnerFee
)

# Upload to all supported chains
partner_fee = PartnerFee(
    bps=50,
    recipient="0x1234567890abcdef1234567890abcdef12345679"
)

app_data_hash = build_all_app_codes(
    env="prod",
    app_code="MyMultiChainApp",
    referrer_address="0x1234567890abcdef1234567890abcdef12345678",
    partner_fee=partner_fee,
    graffiti="Multi-chain deployment"
)

print(f"App data deployed to all chains: {app_data_hash}")

Default App Data

The SDK uses a default app data hash:
from cowdao_cowpy.app_data.utils import DEFAULT_APP_DATA_HASH
from cowdao_cowpy.app_data.consts import DEFAULT_APP_DATA_DOC

print(f"Default hash: {DEFAULT_APP_DATA_HASH}")
print(f"Default doc: {DEFAULT_APP_DATA_DOC}")

Complete Example with Order

complete_app_data.py
import asyncio
import os
from dotenv import load_dotenv
from web3 import Web3, Account
from web3.types import Wei
from cowdao_cowpy.cow.swap import swap_tokens
from cowdao_cowpy.common.chains import Chain
from cowdao_cowpy.app_data.utils import (
    generate_app_data,
    build_and_post_app_data,
    PartnerFee
)
from cowdao_cowpy.order_book.api import OrderBookApi
from cowdao_cowpy.order_book.config import OrderBookAPIConfigFactory
from cowdao_cowpy.common.config import SupportedChainId

load_dotenv()

async def swap_with_custom_app_data():
    # Setup
    account = Account.from_key(os.getenv("PRIVATE_KEY"))
    chain = Chain.SEPOLIA
    
    # Create order book API
    config = OrderBookAPIConfigFactory.get_config(
        "prod",
        SupportedChainId.SEPOLIA
    )
    order_book_api = OrderBookApi(config)
    
    # Build and upload custom app data
    partner_fee = PartnerFee(
        bps=25,  # 0.25%
        recipient="0x1234567890abcdef1234567890abcdef12345679"
    )
    
    app_data_hash = build_and_post_app_data(
        orderbook=order_book_api,
        app_code="MyCustomApp",
        referrer_address="0x1234567890abcdef1234567890abcdef12345678",
        partner_fee=partner_fee,
        graffiti="Custom swap with fees and referral"
    )
    
    print(f"Using app data hash: {app_data_hash}")
    
    # Execute swap with custom app data
    completed_order = await swap_tokens(
        amount=Wei(1000000000000000000),  # 1 token
        account=account,
        chain=chain,
        sell_token=Web3.to_checksum_address(
            "0xbe72E441BF55620febc26715db68d3494213D8Cb"
        ),
        buy_token=Web3.to_checksum_address(
            "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14"
        ),
        app_data=app_data_hash,  # Use custom app data
    )
    
    print(f"Order created: {completed_order.uid}")
    print(f"View at: {completed_order.url}")

if __name__ == "__main__":
    asyncio.run(swap_with_custom_app_data())

App Data Schema

The typical app data structure:
app_data_schema = {
    "appCode": "string",  # Your app identifier
    "metadata": {
        "referrer": {
            "address": "0x...",  # Referrer address
            "version": "string"  # Optional version
        },
        "partnerFee": {
            "bps": 0,  # Basis points (100 = 1%)
            "recipient": "0x..."  # Fee recipient
        },
        "utm": {
            "utmSource": "string",
            "utmMedium": "string",
            "utmCampaign": "string",
            "utmContent": "string"  # Graffiti message
        },
        "quote": {
            "slippageBips": 0,  # Slippage in basis points
            "version": "string"
        }
    },
    "version": "string"  # Schema version
}

Deterministic JSON Serialization

from cowdao_cowpy.app_data.utils import stringify_deterministic

# Create deterministic JSON string
data = {
    "z": 3,
    "a": 1,
    "m": {"nested": "value", "another": "field"}
}

json_string = stringify_deterministic(data)
print(json_string)  # Keys are sorted: {"a":1,"m":{"another":"field","nested":"value"},"z":3}

Hash Computation

from cowdao_cowpy.app_data.utils import keccak256
import json

# Manually compute hash
data = {"appCode": "MyApp"}
json_string = json.dumps(data, sort_keys=True, separators=(',', ':'))
hash_value = keccak256(json_string.encode('utf-8'))
print(f"Hash: 0x{hash_value}")

Checking if App Data Exists

import asyncio
from cowdao_cowpy.order_book.api import OrderBookApi
from cowdao_cowpy.order_book.generated.model import AppDataHash
from cowdao_cowpy.app_data.utils import check_app_data_exists

order_book_api = OrderBookApi()
app_data_hash = AppDataHash(
    root="0x971c41b97f59534448ab833b0d83f755a4bc5c29f92b01776faa3699fcb0eeae"
)

exists = check_app_data_exists(order_book_api, app_data_hash)
if exists:
    print("App data already exists")
else:
    print("App data needs to be uploaded")

Constants

from cowdao_cowpy.app_data.consts import (
    DEFAULT_APP_CODE,
    DEFAULT_GRAFFITI,
    DEFAULT_IPFS_READ_URI,
    DEFAULT_APP_DATA_DOC
)

print(f"Default app code: {DEFAULT_APP_CODE}")
print(f"Default graffiti: {DEFAULT_GRAFFITI}")
print(f"IPFS gateway: {DEFAULT_IPFS_READ_URI}")
print(f"Default document: {DEFAULT_APP_DATA_DOC}")

Best Practices

Upload Before Use: Always upload app data to the order book API before using it in an order. The solvers need to be able to retrieve it.
Unique Hashes: Different app data content produces different hashes. Use this to create unique orders even with identical token pairs and amounts.
Partner Fees: If you include partner fees, ensure the recipient address is correct. Fees are paid from the order’s surplus.

Use Cases

  • Referral Tracking: Include referrer addresses to track who brought users
  • Partner Revenue: Add partner fees to earn from order flow
  • Campaign Attribution: Use UTM parameters to track marketing campaigns
  • Order Identification: Include unique identifiers in graffiti
  • Version Tracking: Tag orders with your app version for debugging

Next Steps

Build docs developers (and LLMs) love