Skip to main content
POST
/
onchain
/
submit
Submit Transaction
curl --request POST \
  --url https://api.example.com/onchain/submit \
  --header 'Content-Type: application/json' \
  --data '
{
  "target": "<string>",
  "calldata": "<string>",
  "value": "<string>",
  "chain_id": 123
}
'
{
  "status": "approved",
  "permit": {
    "wallet": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
    "chainId": 8453,
    "nonce": 1,
    "expiry": 1700000300,
    "target": "0x1111111111111111111111111111111111111111",
    "value": "1000000000000000000",
    "calldataHash": "0xd4fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1",
    "policyHash": "0xb2590ce26adfc7f2814ca4b72880660e2369b23d16ffb446362696d8186d6348",
    "verifyingContract": "0x3333333333333333333333333333333333333333"
  },
  "signature": "0x1234567890abcdef..." 
}
Submits a blockchain transaction request for policy evaluation and EIP-712 permit signing.

Authentication

Requires a valid session token in the Authorization header:
Authorization: Bearer <session_token>

Request Body

target
string
required
Target contract address (0x + 40 hex characters)
calldata
string
required
Transaction calldata as hex string (with or without 0x prefix)
value
string
required
Transaction value as decimal string (uint256 format)
chain_id
integer
required
Blockchain chain ID (must be in allowed chain_ids list)

Response

Approved Response (200)

status
string
"approved"
permit
object
EIP-712 permit structure
signature
string
65-byte ECDSA signature (0x + 130 hex chars), v value is 27 or 28
{
  "status": "approved",
  "permit": {
    "wallet": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
    "chainId": 8453,
    "nonce": 1,
    "expiry": 1700000300,
    "target": "0x1111111111111111111111111111111111111111",
    "value": "1000000000000000000",
    "calldataHash": "0xd4fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1",
    "policyHash": "0xb2590ce26adfc7f2814ca4b72880660e2369b23d16ffb446362696d8186d6348",
    "verifyingContract": "0x3333333333333333333333333333333333333333"
  },
  "signature": "0x1234567890abcdef..." 
}

Denied Response (200)

status
string
"denied"
reason
string
Human-readable denial reason
limit
string
Which policy limit was violated (e.g., "chain_id", "whitelist", "max_tx_value_usd", "daily_spend_cap_usd", "cooldown")
{
  "status": "denied",
  "reason": "tx value 5000.0 exceeds max_tx_value_usd 1000.0",
  "limit": "max_tx_value_usd"
}

Error Responses

Module Disabled (400)

{
  "status": "error",
  "error": "onchain_disabled",
  "message": "Onchain module is disabled. Enable in fishnet.toml"
}

Invalid Target Address (400)

{
  "status": "error",
  "error": "invalid_target",
  "message": "target must be a valid Ethereum address (0x + 40 hex characters)"
}

Invalid Calldata (400)

{
  "status": "error",
  "error": "invalid_calldata",
  "message": "calldata is not valid hex"
}

Invalid Value (400)

{
  "status": "error",
  "error": "invalid_value",
  "message": "value must be a valid uint256 decimal string"
}

Status Codes

  • 200 - Request processed (check status field for approved/denied)
  • 400 - Invalid request format or configuration error
  • 401 - Unauthorized (missing or invalid session token)
  • 500 - Internal error (nonce generation, signing failure, database error)

Policy Checks

Requests are evaluated against the following policies:
  1. Chain ID: Must be in the chain_ids whitelist
  2. Contract Whitelist: target must be in the whitelist
  3. Function Selector: First 4 bytes of calldata must match an allowed selector for the target contract
  4. Max Transaction Value: value must not exceed max_tx_value_usd
  5. Daily Spend Cap: Total approved value today + this value must not exceed daily_spend_cap_usd
  6. Cooldown: Time since last permit must exceed cooldown_seconds
If any check fails, the request is denied and an alert is created.

Examples

curl -X POST https://localhost:3978/onchain/submit \
  -H "Authorization: Bearer fn_sess_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "target": "0x1111111111111111111111111111111111111111",
    "calldata": "0x12345678000000000000000000000000000000000000000000000000000000000000007b",
    "value": "1000000000000000000",
    "chain_id": 8453
  }'

EIP-712 Signature Details

The signature is generated using the following EIP-712 structure:
  • Domain: Fishnet (version 1), with chainId and verifyingContract
  • Primary Type: FishnetPermit
  • Message Fields: wallet, chainId, nonce, expiry (uint48), target, value, calldataHash (bytes32), policyHash (bytes32)
The signature uses secp256k1 ECDSA with recovery ID encoded as v=27 or v=28.

Implementation Notes

  • Function selectors can be specified as 4-byte hex (e.g., 0x12345678) or full function signatures (e.g., swap(uint256,uint256))
  • The calldataHash is keccak256(calldata), not the raw calldata
  • Permits expire after expiry_seconds (default 300 seconds)
  • The nonce is monotonically increasing and prevents replay attacks
  • All approved and denied requests are logged to the audit trail

Build docs developers (and LLMs) love