Date: April 24, 2026 Participants: Suso (Founder), Coordination (Research Assistant) Context: With trading permissions now enabled on our Kraken API key, we need comprehensive documentation of the AddOrder and Withdraw endpoints. This completes our Kraken knowledge base (RS017-RS019, RS036 covered query/ticker).
Status: Live testing required - this is theoretical research based on Kraken API docs
Document the exact API calls needed for:
This enables the settlement_engine.py to complete the two-step settlement MVP.
URL: https://api.kraken.com/0/private/AddOrder
Method: POST
Authentication: Required (API-Key + API-Sign headers)
Permission: “Create & modify orders” (✅ enabled on our API key)
For our use case (buy BCH with EUR using market order):
| Parameter | Value | Required | Description |
|---|---|---|---|
nonce |
int(time.time() * 1000) |
✅ Yes | Unique increasing integer |
ordertype |
"market" |
✅ Yes | Market order (instant execution) |
type |
"buy" |
✅ Yes | Buy BCH (not sell) |
volume |
"0.01" |
✅ Yes | Amount of BCH to buy (string!) |
pair |
"BCHEUR" |
✅ Yes | Trading pair |
oflags |
"fciq" |
Optional | “fciq” = fee in quote currency (EUR) |
validate |
true |
Testing | Validate only, don’t execute |
Critical: Volume is in base currency (BCH), not quote currency (EUR)!
Problem: We know EUR amount (€5), need BCH volume for order.
Solution: Use Ticker API first (RS036):
# Step 1: Get current price
response = requests.get("https://api.kraken.com/0/public/Ticker?pair=BCHEUR")
ask_price = float(response.json()["result"]["BCHEUR"]["a"][0])
# Step 2: Calculate BCH volume
eur_amount = 5.0
bch_volume = eur_amount / ask_price
# Step 3: Round to 8 decimals (BCH precision)
bch_volume = round(bch_volume, 8)
# Example: €5 / €373.81 = 0.01337543 BCH
Python implementation with authentication:
import requests
import hmac
import hashlib
import base64
import urllib.parse
import time
import os
KRAKEN_API_KEY = os.environ['KRAKEN_API_KEY']
KRAKEN_API_SECRET = os.environ['KRAKEN_API_SECRET']
def get_kraken_signature(urlpath, data, secret):
"""Generate Kraken API signature for authentication."""
postdata = urllib.parse.urlencode(data)
encoded = (str(data['nonce']) + postdata).encode()
message = urlpath.encode() + hashlib.sha256(encoded).digest()
signature = hmac.new(base64.b64decode(secret), message, hashlib.sha512)
sigdigest = base64.b64encode(signature.digest())
return sigdigest.decode()
def buy_bch_market(eur_amount):
"""
Buy BCH with EUR using market order on Kraken.
Args:
eur_amount: Amount in EUR to spend (float)
Returns:
dict: Response with order details or error
"""
# Get current BCH price
ticker_resp = requests.get("https://api.kraken.com/0/public/Ticker?pair=BCHEUR")
ask_price = float(ticker_resp.json()["result"]["BCHEUR"]["a"][0])
# Calculate BCH volume
bch_volume = round(eur_amount / ask_price, 8)
# Prepare order data
urlpath = "/0/private/AddOrder"
data = {
"nonce": str(int(time.time() * 1000)),
"ordertype": "market",
"type": "buy",
"volume": str(bch_volume),
"pair": "BCHEUR",
"oflags": "fciq" # Fee in quote currency (EUR)
}
# Sign request
headers = {
"API-Key": KRAKEN_API_KEY,
"API-Sign": get_kraken_signature(urlpath, data, KRAKEN_API_SECRET)
}
# Execute order
response = requests.post(
f"https://api.kraken.com{urlpath}",
data=data,
headers=headers
)
return response.json()
{
"error": [],
"result": {
"descr": {
"order": "buy 0.01337543 BCHEUR @ market"
},
"txid": ["OUF4EM-CAUJB-TPQ6JY"]
}
}
Key fields:
error: Empty array = successresult.txid: Transaction ID array (usually single order)result.descr.order: Human-readable descriptionCommon errors with our permissions:
| Error | Cause | Fix |
|---|---|---|
EGeneral:Invalid arguments |
Missing required parameter | Check nonce, pair, volume |
EOrder:Insufficient funds |
Not enough EUR balance | Query Balance first |
EGeneral:Permission denied |
Missing trading permission | ✅ Fixed - we enabled it |
EOrder:Rate limit exceeded |
Too many orders | Implement rate limiting |
EService:Unavailable |
Kraken downtime | Retry with backoff |
Before risking real money, use validate=true:
data = {
"nonce": str(int(time.time() * 1000)),
"ordertype": "market",
"type": "buy",
"volume": "0.01337543",
"pair": "BCHEUR",
"validate": "true" # ADD THIS FOR DRY RUN
}
Validation response:
{
"error": [],
"result": {
"descr": {
"order": "buy 0.01337543 BCHEUR @ market"
}
}
}
Note: No txid in validation response (order not placed).
URL: https://api.kraken.com/0/private/Withdraw
Method: POST
Authentication: Required (API-Key + API-Sign headers)
Permission: “Withdraw” (✅ enabled on our API key)
⚠️ CRITICAL: Address must be whitelisted in Kraken account settings first!
Before using the API, manually whitelist the escrow hot wallet:
bitcoincash:qzx... (escrow hot wallet)Whitelisting delay: Usually instant, can take up to 24h for new accounts.
After whitelisting, get the “key” identifier for the address:
Endpoint: https://api.kraken.com/0/private/WithdrawInfo
Parameters:
asset: “BCH”key: Use the description name from whitelistingamount: Test amount (e.g., “0.01”)Request:
def get_withdrawal_info():
"""Query withdrawal info for whitelisted BCH address."""
urlpath = "/0/private/WithdrawInfo"
data = {
"nonce": str(int(time.time() * 1000)),
"asset": "BCH",
"key": "Asgaya Escrow Hot Wallet", # Description from whitelisting
"amount": "0.01"
}
headers = {
"API-Key": KRAKEN_API_KEY,
"API-Sign": get_kraken_signature(urlpath, data, KRAKEN_API_SECRET)
}
response = requests.post(
f"https://api.kraken.com{urlpath}",
data=data,
headers=headers
)
return response.json()
Response:
{
"error": [],
"result": {
"method": "Bitcoin Cash",
"limit": "5001.00000000",
"amount": "0.01000000",
"fee": "0.00010000"
}
}
Key insights:
fee: Network fee charged by Kraken (0.0001 BCH = ~€0.037)limit: Daily withdrawal limit remainingamount: What will be sent (before fee)Request:
def withdraw_bch_to_hot_wallet(bch_amount):
"""
Withdraw BCH from Kraken to whitelisted escrow hot wallet.
Args:
bch_amount: Amount of BCH to withdraw (float)
Returns:
dict: Response with withdrawal reference ID
"""
urlpath = "/0/private/Withdraw"
data = {
"nonce": str(int(time.time() * 1000)),
"asset": "BCH",
"key": "Asgaya Escrow Hot Wallet", # Must match whitelisted name
"amount": str(round(bch_amount, 8))
}
headers = {
"API-Key": KRAKEN_API_KEY,
"API-Sign": get_kraken_signature(urlpath, data, KRAKEN_API_SECRET)
}
response = requests.post(
f"https://api.kraken.com{urlpath}",
data=data,
headers=headers
)
return response.json()
Success Response:
{
"error": [],
"result": {
"refid": "FTQ4P7-GJHFM-QPO2K3"
}
}
Key field:
refid: Kraken’s internal withdrawal reference ID (track status with this)Endpoint: https://api.kraken.com/0/private/WithdrawStatus
Parameters:
asset: “BCH”method: “Bitcoin Cash”Request:
def check_withdrawal_status():
"""Check status of recent BCH withdrawals."""
urlpath = "/0/private/WithdrawStatus"
data = {
"nonce": str(int(time.time() * 1000)),
"asset": "BCH",
"method": "Bitcoin Cash"
}
headers = {
"API-Key": KRAKEN_API_KEY,
"API-Sign": get_kraken_signature(urlpath, data, KRAKEN_API_SECRET)
}
response = requests.post(
f"https://api.kraken.com{urlpath}",
data=data,
headers=headers
)
return response.json()
Response:
{
"error": [],
"result": [
{
"method": "Bitcoin Cash",
"aclass": "currency",
"asset": "BCH",
"refid": "FTQ4P7-GJHFM-QPO2K3",
"txid": "8cc4d6d0a4e5e6ed3e9c9f0e5a6e7d8c9b0a1f2e3d4c5b6a7d8e9f0a1b2c3d4e",
"info": "bitcoincash:qzx...",
"amount": "0.01000000",
"fee": "0.00010000",
"time": 1713974400,
"status": "Success"
}
]
}
Status values:
"Initial": Withdrawal initiated, pending internal processing"Pending": Approved, waiting to be sent to network"Success": Broadcasted to BCH network (check blockchain with txid)"Failure": Failed (check info field for reason)Timeline:
def complete_settlement(order_id, eur_amount):
"""
Execute complete two-step settlement:
1. Buy BCH on Kraken with EUR
2. Withdraw BCH to hot wallet
3. (Hot wallet forwards to merchant - separate script)
Args:
order_id: Asgaya order identifier
eur_amount: EUR amount to convert to BCH
Returns:
dict: Settlement result with txids
"""
# Step 1: Calculate capital efficiency reserves
TOTAL_FEE_PCT = 1.0
KRAKEN_FEE_ESTIMATE = 0.26
NUM_INTERMEDIARIES = 3
r_pct = (TOTAL_FEE_PCT - KRAKEN_FEE_ESTIMATE) / NUM_INTERMEDIARIES
merchant_reward = eur_amount * (r_pct / 100)
lp_reward = eur_amount * (r_pct / 100)
bch_purchase_eur = eur_amount - merchant_reward - lp_reward
print(f"💶 EUR breakdown:")
print(f" Total: €{eur_amount:.2f}")
print(f" BCH purchase: €{bch_purchase_eur:.2f}")
print(f" Merchant reserve: €{merchant_reward:.2f}")
print(f" LP reserve: €{lp_reward:.2f}")
# Step 2: Buy BCH
print(f"\n🔄 Buying BCH on Kraken...")
buy_result = buy_bch_market(bch_purchase_eur)
if buy_result["error"]:
raise Exception(f"Buy failed: {buy_result['error']}")
trade_txid = buy_result["result"]["txid"][0]
print(f"✅ Trade executed: {trade_txid}")
# Step 3: Wait for trade to settle (usually instant for market orders)
time.sleep(5)
# Step 4: Query actual BCH received (check balance)
balance_result = query_balance()
bch_balance = float(balance_result["result"]["BCH"])
print(f"💰 Current BCH balance: {bch_balance:.8f} BCH")
# Step 5: Withdraw BCH to hot wallet (minus Kraken fee)
# Leave small amount in Kraken for next trade
withdraw_amount = bch_balance - 0.0001 # Keep dust in Kraken
print(f"\n📤 Withdrawing {withdraw_amount:.8f} BCH to hot wallet...")
withdraw_result = withdraw_bch_to_hot_wallet(withdraw_amount)
if withdraw_result["error"]:
raise Exception(f"Withdrawal failed: {withdraw_result['error']}")
refid = withdraw_result["result"]["refid"]
print(f"✅ Withdrawal initiated: {refid}")
# Step 6: Poll withdrawal status until Success
print(f"\n⏳ Waiting for broadcast to BCH network...")
max_attempts = 60 # 10 minutes max
for i in range(max_attempts):
time.sleep(10) # Check every 10 seconds
status_result = check_withdrawal_status()
# Find our withdrawal by refid
for withdrawal in status_result["result"]:
if withdrawal["refid"] == refid:
status = withdrawal["status"]
print(f" Status: {status}")
if status == "Success":
bch_txid = withdrawal["txid"]
print(f"\n🎉 Settlement complete!")
print(f" BCH transaction: {bch_txid}")
print(f" View: https://blockchair.com/bitcoin-cash/transaction/{bch_txid}")
return {
"order_id": order_id,
"trade_txid": trade_txid,
"withdrawal_refid": refid,
"bch_txid": bch_txid,
"bch_amount": withdraw_amount,
"status": "complete"
}
if status == "Failure":
raise Exception(f"Withdrawal failed: {withdrawal.get('info')}")
time.sleep(10)
raise Exception("Withdrawal timeout - check status manually")
When merchant confirms ARS receipt:
@app.route('/api/merchant/confirm_ars', methods=['POST'])
def merchant_confirm_ars():
# ... existing validation code ...
# Trigger settlement
eur_amount = order['eur_amount']
try:
settlement_result = complete_settlement(order_id, eur_amount)
order['settlement'] = settlement_result
order['status'] = 'bch_sent_to_hot_wallet'
return jsonify({
"status": "success",
"message": "BCH purchased and sent to hot wallet",
"bch_txid": settlement_result['bch_txid']
})
except Exception as e:
return jsonify({
"status": "error",
"message": f"Settlement failed: {str(e)}"
}), 500
✅ Current setup (from RS018):
secrets.env.age (encrypted with age)Additional security:
| Failure Point | Detection | Recovery |
|---|---|---|
| Buy order fails | Check error array |
Retry with backoff, alert if Kraken down |
| Insufficient EUR balance | Query Balance before buy | Time extension Kraken, or use LP fallback |
| Withdrawal fails | Check error array |
May need manual intervention (security hold) |
| Withdrawal stuck | Poll status timeout | Check Kraken UI, may be security review |
| Network failure | Timeout exception | Retry with exponential backoff |
Problem: Network timeout - did order execute or not?
Solution: Query recent trades/withdrawals before retry:
def buy_with_idempotency_check(eur_amount, max_age_seconds=300):
"""Check if we already bought in last 5 minutes before placing new order."""
# Query closed orders from last 5 minutes
recent_trades = query_closed_orders(start=int(time.time() - max_age_seconds))
# Check if any match our criteria
for trade in recent_trades["result"]["closed"].values():
if (trade["descr"]["pair"] == "BCHEUR" and
trade["descr"]["type"] == "buy" and
abs(float(trade["cost"]) - eur_amount) < 0.01):
print(f"⚠️ Found recent matching trade: {trade['txid']}")
return {"error": [], "result": {"txid": [trade["txid"]]}}
# No recent match, safe to place new order
return buy_bch_market(eur_amount)
Kraken rate limits:
Our usage pattern (per transaction):
Total: <10 API calls per transaction over 10-20 minutes = well within limits
Before risking real money:
validate=trueFirst real money test:
Before going live:
Questions we’ll answer when we test:
Test Script: test_kraken_trade.py
Execution Time: 2026-04-24 15:33:52 CET
Result: ✅ SUCCESSFUL
Pre-Trade State:
Order Execution:
{
'ordertype': 'market',
'type': 'buy',
'pair': 'BCHEUR',
'volume': '4.0',
'oflags': 'viqc' # Volume in quote currency
}
Response:
{
"error": [],
"result": {
"txid": ["OXLPUF-G2FN2-LS6S3L"],
"descr": {
"order": "buy 4.00 BCHEUR @ market"
}
}
}
Trade Details (from TradesHistory):
TSLMT4-MS46Z-44BEUWPost-Trade State:
1. Actual Kraken Fee: 0.40% (NOT 0.26%)
Our initial estimate of 0.26% was incorrect. Actual maker/taker fee for our account tier is 0.40%.
Impact on capital efficiency formula:
# OLD (incorrect):
KRAKEN_FEE_ESTIMATE = 0.26
# NEW (correct):
KRAKEN_FEE_ESTIMATE = 0.40
# Updated r calculation:
r = (1.0% - 0.40%) / 3 = 0.20% per intermediary
Effect on €100 transaction:
2. ‘viqc’ Flag Works Perfectly
Volume in quote currency ('oflags': 'viqc') allows us to specify EUR amount directly:
3. Trade History Instant
Trade appeared in /0/private/TradesHistory within 3 seconds:
4. Minimal Slippage
Market order executed at €391.38 vs €390.91 ask:
Update settlement_engine.py:
# Line 41 - Change:
KRAKEN_FEE_ESTIMATE = 0.26 # Old incorrect estimate
# To:
KRAKEN_FEE_ESTIMATE = 0.40 # Actual fee from live test (April 24, 2026)
Update capital efficiency comments:
# r = [1% - 0.40%] / 3 = 0.20% per intermediary
# For €100 transaction:
# - Merchant reward: €0.20
# - LP reward: €0.20
# - BCH purchase: €99.60
From live test, we can now confirm:
| Question | Answer |
|---|---|
| Actual execution fee? | 0.40% (not 0.26% estimated) |
| Market order slippage? | 0.12% for €4 (negligible) |
| ‘viqc’ flag works? | ✅ Yes, perfectly |
| Trade history delay? | < 3 seconds (instant) |
| Balance update timing? | Immediate |
Complete Kraken integration status:
✅ Query (RS019) - Balance checking ✅ Ticker (RS036) - Price feed ✅ AddOrder (RS044 Part 1) - TESTED & VERIFIED 🎯 Withdraw (RS044 Part 2) - Next to test
Critical update needed:
KRAKEN_FEE_ESTIMATE from 0.26% to 0.40% in settlement_engine.pyNext steps:
Discovery: Our 0.40% fee is taker fee (market orders take liquidity).
Kraken Fee Structure (Tier 1: < $10k/month):
Strategy: Aggressive Maker Orders
Place limit order slightly above current ask for near-instant fill:
# Get current ask price
ask_price = 390.91
# Place limit order slightly above (aggressive maker)
limit_price = round(ask_price + 0.10, 2) # €391.01
order_data = {
'ordertype': 'limit', # Changed from 'market'
'type': 'buy',
'pair': 'BCHEUR',
'price': str(limit_price), # Specify price
'volume': str(eur_amount),
'oflags': 'viqc'
}
Benefits:
Implementation:
Fee impact on capital efficiency:
# With maker fees (0.25%):
r = (1.0% - 0.25%) / 3 = 0.25% per intermediary
# For €100 transaction:
# - Merchant reserve: €0.25 (vs €0.20 with taker)
# - LP reserve: €0.25
# - BCH purchase: €99.50
# 25% increase in intermediary rewards!
Test script: /knowledge/research_code/test_kraken_limit_order.py
Test: Limit order at ask + €0.10 Execution Time: 2026-04-24 16:45 CET Result: ✅ Filled instantly, but charged taker fee
Order Details:
{
'ordertype': 'limit',
'type': 'buy',
'pair': 'BCHEUR',
'price': '391.98', # Ask was €391.88 → placed +€0.10 above
'volume': '0.01275575' # €5.00 / 391.98
# NOTE: NO 'viqc' flag - doesn't work with limit orders!
}
Result:
Critical Discovery:
Limit order ABOVE ask = instant taker (crosses the spread)
Our limit @ €391.98 was higher than best ask (€391.88), so it matched immediately against existing sell orders = took liquidity = taker fee.
To get maker fee (0.25%), must:
Revised Strategy for 0.25% Maker Fee:
Option A: AT ask (aggressive maker)
limit_price = ask_price # Exactly at current ask
# Expected: Fills quickly when price moves or sellers arrive
# Fee: 0.25% (maker)
Option B: Below ask (passive maker - RECOMMENDED)
limit_price = ask_price * (1 - 0.0001) # 0.01% below ask
# Example: €391.88 → €391.84 (€0.04 below)
# Expected: Fills when price dips slightly
# Fee: 0.25% (maker)
# Extra cost: 0.01% price difference
# Net savings: 0.15% - 0.01% = 0.14% vs market order
Implementation Plan:
Expected outcome:
Next tests:
Prepared by: Coordination (Research) + Suso (Testing) Date: April 24, 2026 Status: Part 1 (Trading) ✅ Complete | Maker strategy refined, ready for final tests Live Tests:
Discovered: Must not cross spread to get maker fees. Testing refined strategy next. 🎯