Examples

All examples use the public Caddy gateway URL. Replace placeholders before running paid retries. An agent may use Algorand, Base, or both. Algorand examples below pass an Algorand address only. Omitting a Base address does not change rank, eligibility, price, or access. A Base 0x address appears only on the Morpho quote.

MCP discovery tool

After connecting to https://canix402-mcp.compx.io/mcp, call canix_get_discovery for the same endpoint catalog as HTTP /discovery (free tool).

# Example: MCP client invokes canix_get_discovery (no payment required)
# Paid tools: first call returns PAYMENT_REQUIRED; retry with paymentSignature

Discovery fetch (HTTP)

curl -s https://canix402-api.compx.io/discovery | jq '.data.endpoints[] | {path, access, summary}'

OpenAPI fetch

curl -s https://canix402-api.compx.io/openapi.json | jq '.info, (.paths | keys)'

MCP paid tool retry shape

Paid MCP tools mirror the HTTP x402 flow. First invocation returns payment metadata; retry passes the signed payload as paymentSignature (tool argument, not an HTTP header).

// 1) canix_list_opportunities({ limit: 10 })
//    -> PAYMENT_REQUIRED + paymentRequiredHeader metadata

// 2) canix_list_opportunities({
//      limit: 10,
//      paymentSignature: "<base64-json-payload>"
//    })
//    -> opportunity data

Signed retry shape (HTTP)

The website does not sign payments. Your agent selects an accept by network and builds that payload. Algorand is first and a complete payment on its own. Base is the same price and is optional. Base64-encode the JSON as PAYMENT-SIGNATURE.

Algorand

{
  "x402Version": 2,
  "scheme": "exact",
  "network": "algorand-mainnet",
  "accepted": { /* selected Algorand accept from PAYMENT-REQUIRED */ },
  "payload": {
    "paymentGroup": ["<base64-signed-txn>"],
    "paymentIndex": 0
  },
  "paymentRequired": { /* decoded PAYMENT-REQUIRED */ }
}

Base

{
  "x402Version": 2,
  "scheme": "exact",
  "network": "eip155:8453",
  "accepted": { /* selected Base accept from PAYMENT-REQUIRED */ },
  "payload": {
    "signature": "0x<65-byte EIP-712 signature>",
    "authorization": {
      "from": "0x<payer>",
      "to": "0x<payTo>",
      "value": "10000",
      "validAfter": "<unix seconds>",
      "validBefore": "<unix seconds>",
      "nonce": "0x<32 bytes>"
    }
  },
  "paymentRequired": { /* decoded PAYMENT-REQUIRED */ }
}

Sample response payloads

These are illustrative snapshots of the normalized response contract, not live paid data. Opportunity rows include executionShapes, compatibleExitShapes, and (for Réti) entryRequirements / capacity. Numeric values follow the precision contract (6 decimal places by default, up to 12 for small non-zero values). Live responses vary in values and row count; the shape and field semantics match. The full machine-readable schema and examples are published in OpenAPI.

Aggregated opportunities

GET /opportunities — top opportunities across all protocols, ranked by APY.

curl -i https://canix402-api.compx.io/opportunities

Sample response (200)

{
  "data": [
    {
      "protocol": "tinyman",
      "opportunityType": "lp",
      "opportunityId": "tinyman:pool:1002541853",
      "assetPair": "ALGO/USDC",
      "assetIds": [
        0,
        31566704
      ],
      "apr": 10.512,
      "apy": 12.5,
      "yieldBasis": "apy",
      "tvlUsd": 2450000.5,
      "sourceTimestamp": "2026-07-06T09:00:00.000Z",
      "fetchedAt": "2026-07-06T09:00:00.000Z",
      "notes": "sourceTimestamp reflects fetch time; upstream does not expose a per-row update time.",
      "risk": {
        "confidence": "low",
        "sourceAgeSeconds": 5097600,
        "volatilityBucket": "unknown"
      },
      "executionReady": true,
      "executionShapes": [
        {
          "shapeKey": "mainnet:tinyman:v2:addLiquidity:flexible",
          "protocol": "tinyman",
          "protocolVersion": "v2",
          "action": "addLiquidity",
          "variant": "flexible",
          "title": "Tinyman v2 flexible add liquidity",
          "summary": "Adds two-sided liquidity to an existing Tinyman AMM v2 pool using flexible amounts. Generates the documented 3-transaction group (asset1 transfer, asset2 transfer/payment, add_liquidity app call) as unsigned transactions.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "assetAId",
            "assetAAmount",
            "assetBId",
            "assetBAmount",
            "maxSlippageBps"
          ],
          "requiredAssetIds": [
            0,
            31566704
          ],
          "inputHints": {
            "assetAId": 0,
            "assetBId": 31566704,
            "poolId": "tinyman:pool:1002541853"
          }
        },
        {
          "shapeKey": "mainnet:tinyman:v2:addLiquidity:initial",
          "protocol": "tinyman",
          "protocolVersion": "v2",
          "action": "addLiquidity",
          "variant": "initial",
          "title": "Tinyman v2 initial add liquidity",
          "summary": "Adds the first liquidity to a bootstrapped but empty Tinyman AMM v2 pool. Generates the documented 3-transaction group (asset1 transfer, asset2 transfer/payment, add_initial_liquidity app call) as unsigned transactions.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "assetAId",
            "assetAAmount",
            "assetBId",
            "assetBAmount",
            "maxSlippageBps"
          ],
          "requiredAssetIds": [
            0,
            31566704
          ],
          "inputHints": {
            "assetAId": 0,
            "assetBId": 31566704,
            "poolId": "tinyman:pool:1002541853"
          }
        },
        {
          "shapeKey": "mainnet:tinyman:v2:addLiquidity:singleAsset",
          "protocol": "tinyman",
          "protocolVersion": "v2",
          "action": "addLiquidity",
          "variant": "singleAsset",
          "title": "Tinyman v2 single-asset add liquidity",
          "summary": "Adds one-sided liquidity to an existing Tinyman AMM v2 pool. Tinyman performs an internal swap to balance the deposit and mints pool tokens. Generates the documented 2-transaction group (asset transfer/payment, add_liquidity app call) as unsigned transactions.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "assetAId",
            "assetBId",
            "depositAssetId",
            "depositAmount",
            "maxSlippageBps"
          ],
          "requiredAssetIds": [
            0,
            31566704
          ],
          "inputHints": {
            "assetAId": 0,
            "assetBId": 31566704,
            "poolId": "tinyman:pool:1002541853"
          }
        }
      ],
      "compatibleExitShapes": []
    },
    {
      "protocol": "compx",
      "opportunityType": "lending",
      "opportunityId": "compx-lending-3491050310",
      "assetPair": "USDC",
      "assetIds": [
        31566704,
        3491050538
      ],
      "apy": 1.4,
      "apr": 1.4,
      "borrowApr": 8.5,
      "yieldBasis": "apr",
      "tvlUsd": 137.181191,
      "sourceTimestamp": "2026-07-14T20:47:17.000Z",
      "fetchedAt": "2026-07-17T12:41:14.931Z",
      "notes": "CompX lending market 3491050310; util=4.4%; ltv=70.0%; APR-derived yields; borrowApr is the borrow cost.",
      "risk": {
        "utilization": 4.4,
        "liquidationThreshold": 80,
        "ltv": 70,
        "borrowApr": 8.5,
        "confidence": "low",
        "sourceAgeSeconds": 4283377
      },
      "executionReady": true,
      "executionShapes": [
        {
          "shapeKey": "mainnet:compx:v1:deposit:asa",
          "protocol": "compx",
          "protocolVersion": "v1",
          "action": "deposit",
          "variant": "asa",
          "title": "CompX v1 ASA lending deposit",
          "summary": "Deposits a base ASA into a CompX lending market and receives LST tokens.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "marketAppId",
            "amount"
          ],
          "requiredAssetIds": [
            31566704
          ],
          "inputHints": {
            "assetId": 31566704,
            "marketAppId": 3491050310
          }
        },
        {
          "shapeKey": "mainnet:compx:v1:borrow:asa",
          "protocol": "compx",
          "protocolVersion": "v1",
          "action": "borrow",
          "variant": "asa",
          "title": "CompX v1 ASA lending borrow",
          "summary": "Borrows a base ASA from a CompX lending market against LST collateral.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "marketAppId",
            "borrowAmount",
            "collateralAmount"
          ],
          "requiredAssetIds": [
            31566704
          ],
          "inputHints": {
            "assetId": 31566704,
            "marketAppId": 3491050310
          }
        }
      ],
      "compatibleExitShapes": [
        {
          "shapeKey": "mainnet:compx:v1:withdraw:asa",
          "protocol": "compx",
          "protocolVersion": "v1",
          "action": "withdraw",
          "variant": "asa",
          "title": "CompX v1 ASA lending withdraw",
          "summary": "Withdraws a base ASA from a CompX lending market by redeeming LST tokens.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "marketAppId",
            "amount"
          ],
          "requiredAssetIds": [
            31566704
          ],
          "inputHints": {
            "assetId": 31566704,
            "marketAppId": 3491050310
          }
        },
        {
          "shapeKey": "mainnet:compx:v1:repay:asa",
          "protocol": "compx",
          "protocolVersion": "v1",
          "action": "repay",
          "variant": "asa",
          "title": "CompX v1 ASA lending repay",
          "summary": "Repays outstanding base-ASA debt on a CompX lending market.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "marketAppId",
            "amount"
          ],
          "requiredAssetIds": [
            31566704
          ],
          "inputHints": {
            "assetId": 31566704,
            "marketAppId": 3491050310
          }
        }
      ]
    },
    {
      "protocol": "reti",
      "opportunityType": "staking",
      "opportunityId": "reti-staking-12",
      "assetPair": "ALGO",
      "assetIds": [
        0
      ],
      "apy": 8.5,
      "apr": 8.5,
      "yieldBasis": "apr",
      "tvlUsd": 125000.5,
      "sourceTimestamp": "2026-07-23T10:00:00.000Z",
      "fetchedAt": "2026-07-23T10:00:00.000Z",
      "entryRequirements": {
        "minAmount": {
          "assetId": 0,
          "amount": "1000000000"
        },
        "eligibilityFullyCheckable": true
      },
      "capacity": {
        "stakerSlotsRemaining": 20,
        "algoRoomMicroAlgos": "50000000000",
        "acceptingStake": true
      },
      "notes": "Réti validator-level consensus staking; quote-time eligibility checks are authoritative.",
      "executionReady": true,
      "executionShapes": [
        {
          "shapeKey": "mainnet:reti:v1:stake:algo",
          "protocol": "reti",
          "protocolVersion": "v1",
          "action": "stake",
          "variant": "algo",
          "title": "Réti ALGO stake to validator",
          "summary": "Stakes ALGO to a Réti validator via ValidatorRegistry.addStake. The registry allocates into one of the validator's pools. Builds gas ×2, ALGO payment to the registry, addStake, and optional reward-token opt-in. Enforces minEntryStake, capacity, and entry gates at quote time.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "validatorId",
            "amount"
          ],
          "requiredAssetIds": [
            0
          ],
          "inputHints": {
            "validatorId": 12,
            "assetId": 0,
            "depositAssetId": 0
          }
        }
      ],
      "compatibleExitShapes": [
        {
          "shapeKey": "mainnet:reti:v1:unstake:algo",
          "protocol": "reti",
          "protocolVersion": "v1",
          "action": "unstake",
          "variant": "algo",
          "title": "Réti ALGO unstake from pool",
          "summary": "Removes ALGO stake from a Réti staking pool via StakingPool.removeStake. Builds gas ×2, removeStake, and optional reward-token opt-in. Partial unstake must leave at least minEntryStake (or unstake the full balance).",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "validatorId",
            "poolAppId",
            "amount"
          ],
          "requiredAssetIds": [
            0
          ],
          "inputHints": {
            "validatorId": 12,
            "assetId": 0,
            "depositAssetId": 0
          }
        }
      ]
    },
    {
      "protocol": "folks-finance",
      "opportunityType": "lending",
      "opportunityId": "folks-lending-971254667",
      "assetPair": "USDC",
      "assetIds": [
        31566704
      ],
      "apy": 6.06,
      "apr": 5.9,
      "borrowApr": 8.25,
      "yieldBasis": "apy",
      "tvlUsd": 29846.609471,
      "sourceTimestamp": "2026-07-06T08:55:00.000Z",
      "fetchedAt": "2026-07-06T09:00:00.000Z",
      "executionReady": true,
      "executionShapes": [
        {
          "shapeKey": "mainnet:folks-finance:v2:setup:depositEscrow",
          "protocol": "folks-finance",
          "protocolVersion": "v2",
          "action": "setup",
          "variant": "depositEscrow",
          "title": "Folks Finance v2 add deposit escrow",
          "summary": "Creates and funds a new Folks Finance deposit escrow for the user. Returns a 3-transaction group that must be signed by both the user and the generated escrow account.",
          "order": 0,
          "requiredInputs": [
            "userAddress"
          ],
          "requiredAssetIds": [
            31566704
          ],
          "inputHints": {
            "poolAppId": 971254667,
            "assetId": 31566704
          }
        },
        {
          "shapeKey": "mainnet:folks-finance:v2:setup:optEscrowAsset",
          "protocol": "folks-finance",
          "protocolVersion": "v2",
          "action": "setup",
          "variant": "optEscrowAsset",
          "title": "Folks Finance v2 opt deposit escrow into pool fAsset",
          "summary": "Funds and opts an existing Folks Finance deposit escrow into a pool fAsset so it can receive deposits.",
          "order": 1,
          "prerequisiteShapeKeys": [
            "mainnet:folks-finance:v2:setup:depositEscrow"
          ],
          "requiredInputs": [
            "userAddress",
            "escrowAddress"
          ],
          "requiredAssetIds": [
            31566704
          ],
          "inputHints": {
            "poolAppId": 971254667,
            "assetId": 31566704
          }
        },
        {
          "shapeKey": "mainnet:folks-finance:v2:deposit:escrow",
          "protocol": "folks-finance",
          "protocolVersion": "v2",
          "action": "deposit",
          "variant": "escrow",
          "title": "Folks Finance v2 escrow deposit",
          "summary": "Deposits an underlying asset into a Folks Finance lending pool via a deposit escrow. fAssets accrue in the escrow rather than the user's wallet. Generates the SDK deposit group (asset transfer + deposit app call), optionally prefixed with OpUp.",
          "order": 2,
          "prerequisiteShapeKeys": [
            "mainnet:folks-finance:v2:setup:optEscrowAsset"
          ],
          "requiredInputs": [
            "userAddress",
            "assetAmount"
          ],
          "requiredAssetIds": [
            31566704
          ],
          "inputHints": {
            "poolAppId": 971254667,
            "assetId": 31566704
          }
        }
      ],
      "compatibleExitShapes": []
    }
  ],
  "meta": {
    "limit": 10,
    "offset": 0,
    "includeInactive": false,
    "paymentRequired": true
  }
}

Filtered search

GET /opportunities/search — caller-filtered opportunities by platform, type, APY, and TVL.

curl -i 'https://canix402-api.compx.io/opportunities/search?platform=tinyman&minApy=0.05'

Sample response (200)

{
  "data": [
    {
      "protocol": "tinyman",
      "opportunityType": "lp",
      "opportunityId": "tinyman:pool:1002541853",
      "assetPair": "ALGO/USDC",
      "assetIds": [
        0,
        31566704
      ],
      "apr": 10.512,
      "apy": 12.5,
      "yieldBasis": "apy",
      "tvlUsd": 2450000.5,
      "sourceTimestamp": "2026-07-06T09:00:00.000Z",
      "fetchedAt": "2026-07-06T09:00:00.000Z",
      "executionReady": true,
      "executionShapes": [
        {
          "shapeKey": "mainnet:tinyman:v2:addLiquidity:flexible",
          "protocol": "tinyman",
          "protocolVersion": "v2",
          "action": "addLiquidity",
          "variant": "flexible",
          "title": "Tinyman v2 flexible add liquidity",
          "summary": "Adds two-sided liquidity to an existing Tinyman AMM v2 pool using flexible amounts. Generates the documented 3-transaction group (asset1 transfer, asset2 transfer/payment, add_liquidity app call) as unsigned transactions.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "assetAId",
            "assetAAmount",
            "assetBId",
            "assetBAmount",
            "maxSlippageBps"
          ],
          "requiredAssetIds": [
            0,
            31566704
          ],
          "inputHints": {
            "assetAId": 0,
            "assetBId": 31566704,
            "poolId": "tinyman:pool:1002541853"
          }
        },
        {
          "shapeKey": "mainnet:tinyman:v2:addLiquidity:initial",
          "protocol": "tinyman",
          "protocolVersion": "v2",
          "action": "addLiquidity",
          "variant": "initial",
          "title": "Tinyman v2 initial add liquidity",
          "summary": "Adds the first liquidity to a bootstrapped but empty Tinyman AMM v2 pool. Generates the documented 3-transaction group (asset1 transfer, asset2 transfer/payment, add_initial_liquidity app call) as unsigned transactions.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "assetAId",
            "assetAAmount",
            "assetBId",
            "assetBAmount",
            "maxSlippageBps"
          ],
          "requiredAssetIds": [
            0,
            31566704
          ],
          "inputHints": {
            "assetAId": 0,
            "assetBId": 31566704,
            "poolId": "tinyman:pool:1002541853"
          }
        },
        {
          "shapeKey": "mainnet:tinyman:v2:addLiquidity:singleAsset",
          "protocol": "tinyman",
          "protocolVersion": "v2",
          "action": "addLiquidity",
          "variant": "singleAsset",
          "title": "Tinyman v2 single-asset add liquidity",
          "summary": "Adds one-sided liquidity to an existing Tinyman AMM v2 pool. Tinyman performs an internal swap to balance the deposit and mints pool tokens. Generates the documented 2-transaction group (asset transfer/payment, add_liquidity app call) as unsigned transactions.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "assetAId",
            "assetBId",
            "depositAssetId",
            "depositAmount",
            "maxSlippageBps"
          ],
          "requiredAssetIds": [
            0,
            31566704
          ],
          "inputHints": {
            "assetAId": 0,
            "assetBId": 31566704,
            "poolId": "tinyman:pool:1002541853"
          }
        }
      ],
      "compatibleExitShapes": []
    }
  ],
  "meta": {
    "limit": 25,
    "offset": 0,
    "includeInactive": false,
    "paymentRequired": true
  }
}

Wallet-personalized opportunities

GET /opportunities/personalized — opportunities tuned to a wallet's held assets. Matching applies POST /eligibility rules so full or gated venues are not recommended as enterable. Rows include canEnter and eligibilityFullyCheckable; meta.eligibilityEndpoint is /eligibility.

curl -i 'https://canix402-api.compx.io/opportunities/personalized?address=YOUR_ALGORAND_ADDRESS'

Sample response (200)

{
  "data": [
    {
      "protocol": "folks-finance",
      "opportunityType": "lending",
      "opportunityId": "folks-lending-971365552",
      "assetPair": "ALGO",
      "assetIds": [
        0
      ],
      "apy": 4.25,
      "yieldBasis": "apy",
      "tvlUsd": 1875000.25,
      "sourceTimestamp": "2026-07-06T08:55:00.000Z",
      "fetchedAt": "2026-07-06T09:00:00.000Z",
      "executionReady": true,
      "executionShapes": [
        {
          "shapeKey": "mainnet:folks-finance:v2:setup:depositEscrow",
          "protocol": "folks-finance",
          "protocolVersion": "v2",
          "action": "setup",
          "variant": "depositEscrow",
          "title": "Folks Finance v2 add deposit escrow",
          "summary": "Creates and funds a new Folks Finance deposit escrow for the user. Returns a 3-transaction group that must be signed by both the user and the generated escrow account.",
          "order": 0,
          "requiredInputs": [
            "userAddress"
          ],
          "requiredAssetIds": [
            0
          ],
          "inputHints": {
            "poolAppId": 971365552,
            "assetId": 0
          }
        },
        {
          "shapeKey": "mainnet:folks-finance:v2:setup:optEscrowAsset",
          "protocol": "folks-finance",
          "protocolVersion": "v2",
          "action": "setup",
          "variant": "optEscrowAsset",
          "title": "Folks Finance v2 opt deposit escrow into pool fAsset",
          "summary": "Funds and opts an existing Folks Finance deposit escrow into a pool fAsset so it can receive deposits.",
          "order": 1,
          "prerequisiteShapeKeys": [
            "mainnet:folks-finance:v2:setup:depositEscrow"
          ],
          "requiredInputs": [
            "userAddress",
            "escrowAddress"
          ],
          "requiredAssetIds": [
            0
          ],
          "inputHints": {
            "poolAppId": 971365552,
            "assetId": 0
          }
        },
        {
          "shapeKey": "mainnet:folks-finance:v2:deposit:escrow",
          "protocol": "folks-finance",
          "protocolVersion": "v2",
          "action": "deposit",
          "variant": "escrow",
          "title": "Folks Finance v2 escrow deposit",
          "summary": "Deposits an underlying asset into a Folks Finance lending pool via a deposit escrow. fAssets accrue in the escrow rather than the user's wallet. Generates the SDK deposit group (asset transfer + deposit app call), optionally prefixed with OpUp.",
          "order": 2,
          "prerequisiteShapeKeys": [
            "mainnet:folks-finance:v2:setup:optEscrowAsset"
          ],
          "requiredInputs": [
            "userAddress",
            "assetAmount"
          ],
          "requiredAssetIds": [
            0
          ],
          "inputHints": {
            "poolAppId": 971365552,
            "assetId": 0
          }
        }
      ],
      "compatibleExitShapes": [],
      "canEnter": true,
      "eligibilityFullyCheckable": true
    },
    {
      "protocol": "tinyman",
      "opportunityType": "lp",
      "opportunityId": "tinyman:pool:1002541853",
      "assetPair": "ALGO/USDC",
      "assetIds": [
        0,
        31566704
      ],
      "apr": 10.512,
      "apy": 12.5,
      "yieldBasis": "apy",
      "tvlUsd": 2450000.5,
      "sourceTimestamp": "2026-07-06T09:00:00.000Z",
      "fetchedAt": "2026-07-06T09:00:00.000Z",
      "executionReady": true,
      "executionShapes": [
        {
          "shapeKey": "mainnet:tinyman:v2:addLiquidity:flexible",
          "protocol": "tinyman",
          "protocolVersion": "v2",
          "action": "addLiquidity",
          "variant": "flexible",
          "title": "Tinyman v2 flexible add liquidity",
          "summary": "Adds two-sided liquidity to an existing Tinyman AMM v2 pool using flexible amounts. Generates the documented 3-transaction group (asset1 transfer, asset2 transfer/payment, add_liquidity app call) as unsigned transactions.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "assetAId",
            "assetAAmount",
            "assetBId",
            "assetBAmount",
            "maxSlippageBps"
          ],
          "requiredAssetIds": [
            0,
            31566704
          ],
          "inputHints": {
            "assetAId": 0,
            "assetBId": 31566704,
            "poolId": "tinyman:pool:1002541853"
          }
        },
        {
          "shapeKey": "mainnet:tinyman:v2:addLiquidity:initial",
          "protocol": "tinyman",
          "protocolVersion": "v2",
          "action": "addLiquidity",
          "variant": "initial",
          "title": "Tinyman v2 initial add liquidity",
          "summary": "Adds the first liquidity to a bootstrapped but empty Tinyman AMM v2 pool. Generates the documented 3-transaction group (asset1 transfer, asset2 transfer/payment, add_initial_liquidity app call) as unsigned transactions.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "assetAId",
            "assetAAmount",
            "assetBId",
            "assetBAmount",
            "maxSlippageBps"
          ],
          "requiredAssetIds": [
            0,
            31566704
          ],
          "inputHints": {
            "assetAId": 0,
            "assetBId": 31566704,
            "poolId": "tinyman:pool:1002541853"
          }
        },
        {
          "shapeKey": "mainnet:tinyman:v2:addLiquidity:singleAsset",
          "protocol": "tinyman",
          "protocolVersion": "v2",
          "action": "addLiquidity",
          "variant": "singleAsset",
          "title": "Tinyman v2 single-asset add liquidity",
          "summary": "Adds one-sided liquidity to an existing Tinyman AMM v2 pool. Tinyman performs an internal swap to balance the deposit and mints pool tokens. Generates the documented 2-transaction group (asset transfer/payment, add_liquidity app call) as unsigned transactions.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "assetAId",
            "assetBId",
            "depositAssetId",
            "depositAmount",
            "maxSlippageBps"
          ],
          "requiredAssetIds": [
            0,
            31566704
          ],
          "inputHints": {
            "assetAId": 0,
            "assetBId": 31566704,
            "poolId": "tinyman:pool:1002541853"
          }
        }
      ],
      "compatibleExitShapes": [],
      "canEnter": true,
      "eligibilityFullyCheckable": true
    },
    {
      "protocol": "reti",
      "opportunityType": "staking",
      "opportunityId": "reti-staking-12",
      "assetPair": "ALGO",
      "assetIds": [
        0
      ],
      "apy": 8.5,
      "apr": 8.5,
      "yieldBasis": "apr",
      "tvlUsd": 125000.5,
      "sourceTimestamp": "2026-07-23T10:00:00.000Z",
      "fetchedAt": "2026-07-23T10:00:00.000Z",
      "entryRequirements": {
        "minAmount": {
          "assetId": 0,
          "amount": "1000000000"
        },
        "eligibilityFullyCheckable": true
      },
      "capacity": {
        "stakerSlotsRemaining": 20,
        "algoRoomMicroAlgos": "50000000000",
        "acceptingStake": true
      },
      "notes": "Réti validator-level consensus staking; quote-time eligibility checks are authoritative.",
      "executionReady": true,
      "executionShapes": [
        {
          "shapeKey": "mainnet:reti:v1:stake:algo",
          "protocol": "reti",
          "protocolVersion": "v1",
          "action": "stake",
          "variant": "algo",
          "title": "Réti ALGO stake to validator",
          "summary": "Stakes ALGO to a Réti validator via ValidatorRegistry.addStake. The registry allocates into one of the validator's pools. Builds gas ×2, ALGO payment to the registry, addStake, and optional reward-token opt-in. Enforces minEntryStake, capacity, and entry gates at quote time.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "validatorId",
            "amount"
          ],
          "requiredAssetIds": [
            0
          ],
          "inputHints": {
            "validatorId": 12,
            "assetId": 0,
            "depositAssetId": 0
          }
        }
      ],
      "compatibleExitShapes": [
        {
          "shapeKey": "mainnet:reti:v1:unstake:algo",
          "protocol": "reti",
          "protocolVersion": "v1",
          "action": "unstake",
          "variant": "algo",
          "title": "Réti ALGO unstake from pool",
          "summary": "Removes ALGO stake from a Réti staking pool via StakingPool.removeStake. Builds gas ×2, removeStake, and optional reward-token opt-in. Partial unstake must leave at least minEntryStake (or unstake the full balance).",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "validatorId",
            "poolAppId",
            "amount"
          ],
          "requiredAssetIds": [
            0
          ],
          "inputHints": {
            "validatorId": 12,
            "assetId": 0,
            "depositAssetId": 0
          }
        }
      ],
      "canEnter": true,
      "eligibilityFullyCheckable": true
    }
  ],
  "meta": {
    "limit": 10,
    "offset": 0,
    "includeInactive": false,
    "paymentRequired": true,
    "address": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    "heldAssetCount": 2,
    "eligibilityApplied": true,
    "eligibilityEndpoint": "/eligibility"
  }
}

Opportunity history series

GET /opportunities/:id/history — bounded APY/TVL snapshots for window=1d|7d|30d (default 30d). Empty until the snapshot job has run. Includes a stability signal so snapshot APY cannot dominate plan sizing.

curl -i 'https://canix402-api.compx.io/opportunities/tinyman:pool:1002541853/history?window=30d'

Sample response (200)

{
  "data": {
    "opportunityId": "tinyman:pool:1002541853",
    "window": "30d",
    "points": [
      {
        "ts": "2026-08-04T12:00:00.000Z",
        "apy": 12.1,
        "tvlUsd": 2400000
      },
      {
        "ts": "2026-08-05T12:00:00.000Z",
        "apy": 12.4,
        "tvlUsd": 2425000
      },
      {
        "ts": "2026-08-06T12:00:00.000Z",
        "apy": 12.5,
        "tvlUsd": 2450000.5
      }
    ],
    "stability": {
      "bucket": "high",
      "sampleCount": 3,
      "apyMean": 12.333333,
      "apyStdev": 0.208167
    }
  },
  "meta": {
    "paymentRequired": true,
    "windowSeconds": 2592000,
    "snapshotRetentionDays": 30,
    "snapshotCount": 3
  }
}

Eligibility and capacity

POST /eligibility — check whether a wallet can enter selected opportunities before quoting. Returns canEnter, missingAssets, gates, capacity, and optional suggestedSwap. NFD/creator gates stay unresolved (eligibilityFullyCheckable: false).

curl -i https://canix402-api.compx.io/eligibility -H 'content-type: application/json' -d '{"address":"YOUR_ALGORAND_ADDRESS","opportunityIds":["reti-staking-12"]}'

Sample response (200)

{
  "data": [
    {
      "opportunityId": "reti-staking-12",
      "protocol": "reti",
      "found": true,
      "canEnter": true,
      "eligibilityFullyCheckable": true,
      "missingAssets": [],
      "gates": [],
      "capacity": {
        "stakerSlotsRemaining": 20,
        "algoRoomMicroAlgos": "50000000000",
        "acceptingStake": true
      },
      "suggestedSwap": null,
      "reasons": []
    }
  ],
  "meta": {
    "address": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    "fetchedAt": "2026-08-14T21:00:00.000Z",
    "paymentRequired": true,
    "quoteTimeAuthoritative": true
  }
}

Intent compiler (paid)

POST /plans — flagship compiler SKU (~0.25 USDC). State an allocation intent (address, budget/asset, constraints). Returns ordered steps: eligibility, live multi-router opt-in → swap compose when requiredAssetIds differ from the budget asset, protocol setup chains, and enter quotes as independent unsigned groups (never merged). Reuses quotes[] / order / prerequisiteShapeKeys. Attaches data.simulation (predicted deltas, fail-closed reasons) when groups are compiled. Brownie and other agents should consume this route rather than forking a compiler. Canix never signs or submits.

curl -i https://canix402-api.compx.io/plans -H 'content-type: application/json' -d '{"address":"YOUR_ALGORAND_ADDRESS","budget":{"assetId":0,"amount":"1000000"},"constraints":{"noNewBorrows":true,"executionReadyOnly":true},"opportunityIds":["reti-staking-12"]}'

Sample response (200)

{
  "data": {
    "allocations": [
      {
        "opportunityId": "reti-staking-12",
        "protocol": "reti",
        "opportunityType": "staking",
        "assetPair": "ALGO",
        "apy": 8.5,
        "allocatedAmount": "1000000",
        "allocatedAssetId": 0,
        "weightBps": 10000,
        "tvlUsd": 1000000,
        "sourceTimestamp": "2026-08-16T10:00:00.000Z",
        "executionReady": true,
        "eligibility": {
          "opportunityId": "reti-staking-12",
          "protocol": "reti",
          "found": true,
          "canEnter": true,
          "eligibilityFullyCheckable": true,
          "missingAssets": [],
          "gates": [],
          "capacity": {
            "stakerSlotsRemaining": 20,
            "algoRoomMicroAlgos": "50000000000",
            "acceptingStake": true
          },
          "suggestedSwap": null,
          "reasons": []
        },
        "executionShapes": [
          {
            "shapeKey": "mainnet:reti:v1:stake:algo",
            "protocol": "reti",
            "protocolVersion": "v1",
            "action": "stake",
            "variant": "algo",
            "title": "Réti ALGO stake to validator",
            "summary": "Stakes ALGO to a Réti validator.",
            "order": 0,
            "requiredInputs": [
              "userAddress",
              "validatorId",
              "amount"
            ],
            "requiredAssetIds": [
              0
            ],
            "inputHints": {
              "validatorId": 12,
              "assetId": 0,
              "depositAssetId": 0
            }
          }
        ],
        "steps": [
          {
            "kind": "eligibility",
            "order": 0,
            "compileStatus": "compiled",
            "warnings": []
          },
          {
            "kind": "enter",
            "order": 1,
            "compileStatus": "compiled",
            "shapeKey": "mainnet:reti:v1:stake:algo",
            "quoteRequest": {
              "shapeKey": "mainnet:reti:v1:stake:algo",
              "input": {
                "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
                "validatorId": 12,
                "assetId": 0,
                "depositAssetId": 0,
                "amount": "1000000"
              }
            },
            "quote": {
              "shapeKey": "mainnet:reti:v1:stake:algo",
              "shapeVersion": "1.0.0",
              "identity": {
                "network": "mainnet",
                "protocol": "reti",
                "protocolVersion": "v1",
                "action": "stake",
                "variant": "algo"
              },
              "createdAt": "2026-08-16T10:00:00.000Z",
              "expiresAt": "2026-08-16T10:00:30.000Z",
              "transactions": [
                {
                  "type": "pay",
                  "sender": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
                  "fee": "1000",
                  "groupPresent": true
                }
              ],
              "encodedTransactions": [
                "<base64-unsigned-txn>"
              ],
              "warnings": [],
              "metadata": {}
            },
            "warnings": []
          }
        ],
        "quotes": [
          {
            "shapeKey": "mainnet:reti:v1:stake:algo",
            "input": {
              "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
              "validatorId": 12,
              "assetId": 0,
              "depositAssetId": 0,
              "amount": "1000000"
            }
          }
        ]
      }
    ],
    "blocked": [],
    "expectedPositionDelta": {
      "summary": "Enter 1000000 of asset 0 into reti-staking-12 (reti).",
      "entries": [
        {
          "opportunityId": "reti-staking-12",
          "protocol": "reti",
          "assetId": 0,
          "amount": "1000000",
          "action": "enter"
        }
      ]
    },
    "fees": {
      "x402Usdc": "0.25",
      "estimatedNetworkFeeMicroAlgos": "1000",
      "estimatedNetworkFeeUsd": null
    },
    "expiresAt": "2026-08-16T10:00:30.000Z",
    "warnings": [],
    "simulation": {
      "wouldSucceed": true,
      "reasons": [],
      "balanceDeltas": [
        {
          "address": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
          "assetId": 0,
          "before": "5000000",
          "after": "3999000",
          "delta": "-1001000"
        }
      ],
      "expectedPositionDelta": {
        "summary": "Enter 1000000 of asset 0 on reti-staking-12 (reti).",
        "entries": [
          {
            "opportunityId": "reti-staking-12",
            "protocol": "reti",
            "assetId": 0,
            "amount": "1000000",
            "action": "enter"
          }
        ]
      },
      "groups": [
        {
          "index": 0,
          "shapeKey": "mainnet:reti:v1:stake:algo",
          "opportunityId": "reti-staking-12",
          "wouldSucceed": true,
          "reasons": [],
          "balanceDeltas": [
            {
              "address": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
              "assetId": 0,
              "before": "5000000",
              "after": "3999000",
              "delta": "-1001000"
            }
          ],
          "expectedPositionDelta": {
            "summary": "Enter 1000000 of asset 0 on reti-staking-12 (reti).",
            "entries": [
              {
                "opportunityId": "reti-staking-12",
                "protocol": "reti",
                "assetId": 0,
                "amount": "1000000",
                "action": "enter"
              }
            ]
          }
        }
      ],
      "signed": false,
      "submitted": false
    }
  },
  "meta": {
    "address": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    "budget": {
      "assetId": 0,
      "amount": "1000000"
    },
    "fetchedAt": "2026-08-16T10:00:00.000Z",
    "paymentRequired": true,
    "executionSubmitted": false,
    "quoteTimeAuthoritative": true,
    "eligibilityEndpoint": "/eligibility"
  }
}

Swap-aware enter compose (paid)

POST /execution/compose — ~0.10 USDC. State “I hold asset A, I want this opportunity.” Returns sequenced unsigned groups: opt-in → winning swap → enter, driven by requiredAssetIds. Groups are never merged. Signer indexes and pre-signed members are preserved; sign only user legs and submit locally. Failure modes (stale quote, missing opt-in, slippage) appear on step warnings. Prefer POST /plans for budget allocation.

curl -i https://canix402-api.compx.io/execution/compose -H 'content-type: application/json' -d '{"address":"YOUR_ALGORAND_ADDRESS","opportunityId":"reti-staking-12","fromAssetId":1,"amount":"1000000","slippage":1}'

Sample response (200)

{
  "data": {
    "opportunityId": "reti-staking-12",
    "protocol": "reti",
    "opportunityType": "staking",
    "assetPair": "ALGO",
    "fromAssetId": 1,
    "toAssetId": 0,
    "inputAmount": "1000000",
    "enterAmount": "247500",
    "slippage": 1,
    "steps": [
      {
        "kind": "eligibility",
        "order": 0,
        "compileStatus": "compiled",
        "warnings": [
          "Eligibility is gated on missing required assets; swap-aware compose unblocks enter after the swap group confirms."
        ]
      },
      {
        "kind": "opt-in",
        "order": 1,
        "compileStatus": "compiled",
        "shapeKey": "mainnet:swap:router:optin:required",
        "quote": {
          "shapeKey": "mainnet:swap:router:optin:required",
          "encodedTransactions": [
            "<base64-unsigned-opt-in>"
          ],
          "userSignIndexes": [
            0
          ],
          "groupTransactions": [
            {
              "index": 0,
              "signer": "user",
              "encodedTransaction": "<base64-unsigned-opt-in>"
            }
          ]
        },
        "warnings": [
          "Missing opt-in: submit the opt-in group and wait for confirmation before submitting the swap group."
        ]
      },
      {
        "kind": "swap",
        "order": 2,
        "compileStatus": "compiled",
        "shapeKey": "mainnet:swap:router:swap:fixed-input",
        "prerequisiteShapeKeys": [
          "mainnet:swap:router:optin:required"
        ],
        "quote": {
          "shapeKey": "mainnet:swap:router:swap:fixed-input",
          "encodedTransactions": [
            "<base64-user-leg>"
          ],
          "userSignIndexes": [
            0
          ],
          "groupTransactions": [
            {
              "index": 0,
              "signer": "user",
              "encodedTransaction": "<base64-user-leg>"
            },
            {
              "index": 1,
              "signer": "logicsig",
              "encodedTransaction": "<base64-router-leg>",
              "signedTransaction": "<base64-presigned>"
            }
          ]
        },
        "warnings": [
          "Stale quote: submit the swap group before quote.expiresAt.",
          "Slippage: enter is compiled with a haircut of quotedAmount; re-quote enter if the confirmed swap delivers less."
        ]
      },
      {
        "kind": "enter",
        "order": 3,
        "compileStatus": "compiled",
        "shapeKey": "mainnet:reti:v1:stake:algo",
        "prerequisiteShapeKeys": [
          "mainnet:swap:router:optin:required",
          "mainnet:swap:router:swap:fixed-input"
        ]
      }
    ],
    "quotes": [
      {
        "shapeKey": "mainnet:reti:v1:stake:algo",
        "input": {
          "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
          "amount": "247500"
        }
      }
    ],
    "warnings": [
      "Groups stay unsigned and unmerged. Sign only user legs; preserve any pre-signed members."
    ]
  },
  "meta": {
    "paymentRequired": true,
    "executionSubmitted": false,
    "groupsMerged": false
  }
}

Simulate / expected delta (paid)

POST /execution/simulate — ~0.10 USDC. Pass compiled unsigned groups[] from a plan or quote. Returns predicted balance and position deltas without signing or submitting. Fail closed with machine-readable reasons[].code (stale-quote, not-opted-in, min-balance, health-factor-too-low, capacity). POST /plans already attaches data.simulation when groups are compiled. Canix never signs or submits.

curl -i https://canix402-api.compx.io/execution/simulate -H 'content-type: application/json' -d '{"address":"YOUR_ALGORAND_ADDRESS","groups":[{"shapeKey":"mainnet:reti:v1:stake:algo","encodedTransactions":["<base64-unsigned-txn>"]}]}'

Sample response (200)

{
  "data": {
    "wouldSucceed": true,
    "reasons": [],
    "balanceDeltas": [
      {
        "address": "RS7TLLQRXKBAQDAVTSZC2ZLMVMLNSCL3FOUOESJJZ5XSKFFL56UI6X33CI",
        "assetId": 0,
        "before": "5000000",
        "after": "3999000",
        "delta": "-1001000"
      }
    ],
    "expectedPositionDelta": {
      "summary": "Enter 1000000 of asset 0 on reti-staking-12 (reti).",
      "entries": [
        {
          "opportunityId": "reti-staking-12",
          "protocol": "reti",
          "assetId": 0,
          "amount": "1000000",
          "action": "enter"
        }
      ]
    },
    "groups": [
      {
        "index": 0,
        "shapeKey": "mainnet:reti:v1:stake:algo",
        "opportunityId": "reti-staking-12",
        "wouldSucceed": true,
        "reasons": [],
        "balanceDeltas": [
          {
            "address": "RS7TLLQRXKBAQDAVTSZC2ZLMVMLNSCL3FOUOESJJZ5XSKFFL56UI6X33CI",
            "assetId": 0,
            "before": "5000000",
            "after": "3999000",
            "delta": "-1001000"
          }
        ],
        "expectedPositionDelta": {
          "summary": "Enter 1000000 of asset 0 on reti-staking-12 (reti).",
          "entries": [
            {
              "opportunityId": "reti-staking-12",
              "protocol": "reti",
              "assetId": 0,
              "amount": "1000000",
              "action": "enter"
            }
          ]
        }
      }
    ],
    "signed": false,
    "submitted": false
  },
  "meta": {
    "address": "RS7TLLQRXKBAQDAVTSZC2ZLMVMLNSCL3FOUOESJJZ5XSKFFL56UI6X33CI",
    "fetchedAt": "2026-08-21T12:00:00.000Z",
    "paymentRequired": true,
    "executionSubmitted": false,
    "signed": false
  }
}

Policy-as-a-service (paid)

POST /policy/validate — ~0.25 USDC compiler SKU. Pass a compiled plan or proposed quotes[] plus an operator policy document (schemaVersion: "1.0.0"). Returns machine-readable pass and reasons[] for per-protocol weight, ALGO reserve floor, TVL/freshness floors, no-new-borrows, and execution-ready only. Reuses fields already on the compiled object and fails closed when a required field is missing — Canix does not re-quote on-chain, sign, or submit. Copy protocol/docs/policy-brownie.sample.json.

curl -i https://canix402-api.compx.io/policy/validate -H 'content-type: application/json' -d '{"policy":{"schemaVersion":"1.0.0","maxProtocolWeightBps":4000,"minAlgoReserveMicroAlgos":"1000000","minTvlUsd":25000,"maxSourceAgeSeconds":86400,"noNewBorrows":true,"executionReadyOnly":true},"quotes":[{"shapeKey":"mainnet:reti:v1:stake:algo","protocol":"reti","opportunityId":"reti-staking-12","weightBps":4000,"allocatedAmount":"1000000","allocatedAssetId":0,"tvlUsd":1000000,"sourceTimestamp":"2026-08-28T11:00:00.000Z","executionReady":true}],"walletAlgoMicroAlgos":"5000000"}'

Sample response (200)

{
  "data": {
    "pass": true,
    "reasons": [],
    "policySchemaVersion": "1.0.0",
    "evaluatedAt": "2026-08-28T12:00:00.000Z",
    "signed": false,
    "submitted": false
  },
  "meta": {
    "fetchedAt": "2026-08-28T12:00:00.000Z",
    "paymentRequired": true,
    "executionSubmitted": false,
    "signed": false,
    "quoteTimeAuthoritative": true
  }
}

Protocol-specific opportunities

GET /protocols/{protocol}/opportunities — opportunities for a single protocol. Substitute any supported protocol (e.g. tinyman, pact, folks-finance, compx, dorkfi, myth-finance, haystack, reti, alpha-arcade, stamm). AlgoFi and Humble LP holdings appear on GET /positions only.

curl -i https://canix402-api.compx.io/protocols/reti/opportunities

Sample response (200)

{
  "data": [
    {
      "protocol": "reti",
      "opportunityType": "staking",
      "opportunityId": "reti-staking-12",
      "assetPair": "ALGO",
      "assetIds": [
        0
      ],
      "apy": 8.5,
      "apr": 8.5,
      "yieldBasis": "apr",
      "tvlUsd": 125000.5,
      "sourceTimestamp": "2026-07-23T10:00:00.000Z",
      "fetchedAt": "2026-07-23T10:00:00.000Z",
      "entryRequirements": {
        "minAmount": {
          "assetId": 0,
          "amount": "1000000000"
        },
        "eligibilityFullyCheckable": true
      },
      "capacity": {
        "stakerSlotsRemaining": 20,
        "algoRoomMicroAlgos": "50000000000",
        "acceptingStake": true
      },
      "notes": "Réti validator-level consensus staking; quote-time eligibility checks are authoritative.",
      "executionReady": true,
      "executionShapes": [
        {
          "shapeKey": "mainnet:reti:v1:stake:algo",
          "protocol": "reti",
          "protocolVersion": "v1",
          "action": "stake",
          "variant": "algo",
          "title": "Réti ALGO stake to validator",
          "summary": "Stakes ALGO to a Réti validator via ValidatorRegistry.addStake. The registry allocates into one of the validator's pools. Builds gas ×2, ALGO payment to the registry, addStake, and optional reward-token opt-in. Enforces minEntryStake, capacity, and entry gates at quote time.",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "validatorId",
            "amount"
          ],
          "requiredAssetIds": [
            0
          ],
          "inputHints": {
            "validatorId": 12,
            "assetId": 0,
            "depositAssetId": 0
          }
        }
      ],
      "compatibleExitShapes": [
        {
          "shapeKey": "mainnet:reti:v1:unstake:algo",
          "protocol": "reti",
          "protocolVersion": "v1",
          "action": "unstake",
          "variant": "algo",
          "title": "Réti ALGO unstake from pool",
          "summary": "Removes ALGO stake from a Réti staking pool via StakingPool.removeStake. Builds gas ×2, removeStake, and optional reward-token opt-in. Partial unstake must leave at least minEntryStake (or unstake the full balance).",
          "order": 0,
          "requiredInputs": [
            "userAddress",
            "validatorId",
            "poolAppId",
            "amount"
          ],
          "requiredAssetIds": [
            0
          ],
          "inputHints": {
            "validatorId": 12,
            "assetId": 0,
            "depositAssetId": 0
          }
        }
      ]
    }
  ],
  "meta": {
    "limit": 25,
    "offset": 0,
    "includeInactive": false,
    "paymentRequired": true
  }
}

Execution shape catalog (free)

GET /execution/shapes — live catalog of verified shape keys, required inputs, and docs paths. Metadata only; does not compile quotes. meta.caveatsDocsPath points at protocol-specific construction caveats so agents do not guess pool discovery, opt-ins, min-balance, slippage, or app upgrades.

curl -s https://canix402-api.compx.io/execution/shapes | jq '.meta, (.data | length)'

Sample response (200, trimmed)

{
  "data": [
    {
      "shapeKey": "mainnet:compx:v1:borrow:asa",
      "network": "mainnet",
      "protocol": "compx",
      "protocolVersion": "v1",
      "action": "borrow",
      "variant": "asa",
      "shapeVersion": "1.0.1",
      "title": "CompX v1 ASA lending borrow",
      "description": "Borrows a base ASA from a CompX lending market against accepted LST collateral. Collateral must be in the market's on-chain accepted_collaterals set (often a cross-market LST).",
      "summary": "Borrows a base ASA from a CompX lending market against accepted LST collateral.",
      "opportunityRole": "enter",
      "supportedOpportunityTypes": [
        "lending"
      ],
      "requiredInputs": [
        "userAddress",
        "marketAppId",
        "borrowAmount",
        "collateralAmount"
      ],
      "sources": [
        {
          "kind": "sdk",
          "description": "@compx/sdk buildBorrowTransactions"
        }
      ],
      "docsPath": "protocol/docs/execution-shapes/compx-borrow-asa.md"
    },
    {
      "shapeKey": "mainnet:compx:v1:repay:asa",
      "network": "mainnet",
      "protocol": "compx",
      "protocolVersion": "v1",
      "action": "repay",
      "variant": "asa",
      "shapeVersion": "1.0.1",
      "title": "CompX v1 ASA lending repay",
      "description": "Repays outstanding base-ASA debt on a CompX lending market. App-call fees are floored to at least 2000 microAlgos after SDK simulation.",
      "summary": "Repays outstanding base-ASA debt on a CompX lending market.",
      "opportunityRole": "exit",
      "supportedOpportunityTypes": [
        "lending"
      ],
      "requiredInputs": [
        "userAddress",
        "marketAppId",
        "amount"
      ],
      "sources": [
        {
          "kind": "sdk",
          "description": "@compx/sdk buildRepayTransactions"
        }
      ],
      "docsPath": "protocol/docs/execution-shapes/compx-repay-asa.md"
    }
  ],
  "meta": {
    "paymentRequired": false,
    "shapeCount": 2,
    "note": "Catalog metadata only. Compile unsigned groups via paid POST /execution/quotes. Protocol construction caveats: protocol/docs/execution-shapes/protocol-caveats.md. Full catalog is larger; this sample is trimmed to CompX borrow/repay.",
    "caveatsDocsPath": "protocol/docs/execution-shapes/protocol-caveats.md"
  }
}

Execution quotes (paid)

POST /execution/quotes — batch { quotes: [{ shapeKey, input }, ...] } (~0.1 USDC flat per request). Returns unsigned groups; Canix never signs or submits. Preflight returns 402 with PAYMENT-REQUIRED; retry with PAYMENT-SIGNATURE (same pattern as opportunities).

1) Preflight (expect 402)

curl -i https://canix402-api.compx.io/execution/quotes \
  -H 'content-type: application/json' \
  -d '{
    "quotes": [{
      "shapeKey": "mainnet:tinyman:v2:addLiquidity:flexible",
      "input": {
        "userAddress": "YOUR_ALGORAND_ADDRESS",
        "assetAId": 0,
        "assetAAmount": "1000000",
        "assetBId": 31566704,
        "assetBAmount": "1000000",
        "maxSlippageBps": 50
      }
    }]
  }'

2) Paid retry (enter liquidity)

curl -i https://canix402-api.compx.io/execution/quotes \
  -H 'content-type: application/json' \
  -H 'PAYMENT-SIGNATURE: <base64-json-payload>' \
  -d '{
    "quotes": [{
      "shapeKey": "mainnet:tinyman:v2:addLiquidity:flexible",
      "input": {
        "userAddress": "YOUR_ALGORAND_ADDRESS",
        "assetAId": 0,
        "assetAAmount": "1000000",
        "assetBId": 31566704,
        "assetBAmount": "1000000",
        "maxSlippageBps": 50
      }
    }]
  }'

3) Exit from a position (use compatibleExitShapeKeys)

After GET /positions, pick a key from compatibleExitShapeKeys and fill inputs from that row's inputHints (plus amounts you choose locally).

curl -i https://canix402-api.compx.io/execution/quotes \
  -H 'content-type: application/json' \
  -H 'PAYMENT-SIGNATURE: <base64-json-payload>' \
  -d '{
    "quotes": [{
      "shapeKey": "mainnet:tinyman:v2:removeLiquidity:multipleAssetsOut",
      "input": {
        "userAddress": "YOUR_ALGORAND_ADDRESS",
        "poolId": "1002541853",
        "assetAId": 0,
        "assetBId": 31566704,
        "poolTokenAmount": "1250000",
        "maxSlippageBps": 50
      }
    }]
  }'

4) Morpho vault deposit on Base (optional)

Use this only when the agent is acting on Base. userAddress is a Base 0x address. The response is unsigned calldata (encodedTransactions hex and transactions[].evmCall). An Algorand-only agent can skip this call. Pay the same 0.1 USDC on either rail. The payment signature is not the vault calldata.

curl -i https://canix402-api.compx.io/execution/quotes \
  -H 'content-type: application/json' \
  -H 'PAYMENT-SIGNATURE: <base64-json-payload>' \
  -d '{
    "quotes": [{
      "shapeKey": "base:morpho:vault:deposit:erc4626",
      "input": {
        "userAddress": "0xYOUR_BASE_ADDRESS",
        "vaultAddress": "0xef417a2512C5a41f69AE4e021648b69a7CdE5D03",
        "amount": "1000000"
      }
    }]
  }'

Sample response (200)

{
  "data": [
    {
      "shapeKey": "mainnet:tinyman:v2:addLiquidity:flexible",
      "shapeVersion": "1.0.0",
      "network": "mainnet",
      "expiresAt": "2026-07-23T12:00:30.000Z",
      "encodedTransactions": [
        "<base64-unsigned-txn-0>",
        "<base64-unsigned-txn-1>"
      ],
      "transactions": [
        {
          "type": "axfer",
          "sender": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
          "fee": "1000",
          "groupPresent": true
        }
      ],
      "metadata": {
        "poolAddress": "TINYPQOLADDRESSPLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAA"
      },
      "warnings": []
    }
  ],
  "meta": {
    "paymentRequired": true,
    "executionSubmitted": false,
    "quoteCount": 1,
    "note": "Unsigned groups only. Sign and submit locally; Canix never receives wallet keys."
  },
  "claimBatchRequestExample": {
    "quotes": [
      {
        "shapeKey": "mainnet:haystack:v1:claim:rewards",
        "input": {
          "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"
        }
      },
      {
        "shapeKey": "mainnet:compx:v1:claim:rewards",
        "input": {
          "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
          "poolAppId": 123456
        }
      },
      {
        "shapeKey": "mainnet:alpha-arcade:v1:claimRewards:usdc",
        "input": {
          "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"
        }
      }
    ],
    "note": "Pass claimAllQuotes from GET /positions/claimable. Each quotes[] item compiles to an independent unsigned group; groups are never merged. Flat ~0.10 USDC per request."
  }
}

Haystack Launch

Bonding-curve launches are separate from yield positions. List tokens still bonding, read one token free, then compile a launch or a bonding buy with POST /execution/quotes (0.10 USDC flat). A bonding buy is not POST /swaps/transactions. Pass an ipfs:// or https:// image URL you already have. Holdings while a token is bonding are virtual and are not wallet ASAs.

1) List tokens still bonding (paid, 0.01 USDC)

GET /protocols/haystack/launches. The default window is the last 60 days. q matches name or ticker. minProgress and maxProgress are inclusive bonding percents. order is asc or desc (default desc). A research session can pay this route.

curl -i 'https://canix402-api.compx.io/protocols/haystack/launches?q=len&minProgress=10&maxProgress=90&order=desc&limit=25'

Sample list (200, trimmed)

{
  "data": {
    "launchedAfter": "2026-08-06T21:50:00.000Z",
    "launchedBefore": "2026-10-05T21:50:00.000Z",
    "order": "desc",
    "total": 1,
    "launches": [
      {
        "tokenNum": 175,
        "assetId": 3729195158,
        "name": "LENIN",
        "symbol": "LENIN",
        "assetUrl": "ipfs://bafkreiexample",
        "creator": "LOLJKAN3NMRMAWAPYKQOEJPVM37KKZCMM7DMGOU2CWN45RKSDZB6QAKSMI",
        "bondingTokenId": 0,
        "launchedAt": "2026-10-02T22:56:32.000Z",
        "progressPercent": 12.5,
        "priceBonding": "0.000001",
        "priceUsd": "0.000002",
        "realTokenReserves": "700000000000000",
        "realBondingReserves": "5000000",
        "initialRealTokenReserves": "800000000000000",
        "bondingTargetUsd": "2500000000",
        "priceMultiplier": "20",
        "buyShapeKey": "mainnet:haystack:v1:buy:bonding"
      }
    ]
  },
  "meta": {
    "paymentRequired": true,
    "shapeKey": "mainnet:haystack:v1:buy:bonding"
  }
}

2) One token (free)

GET /protocols/haystack/launches/{tokenNum}. After the first buy, ?assetId= resolves the token. ?address= adds virtual userHolding. bondingOn 0 is still on the curve. A graduated token sets buyShapeKey to null and points note at POST /swaps/quote.

curl -s https://canix402-api.compx.io/protocols/haystack/launches/175 | jq
curl -s 'https://canix402-api.compx.io/protocols/haystack/launches/0?assetId=3729195158&address=YOUR_ALGORAND_ADDRESS' | jq

Sample status (200, trimmed)

{
  "data": {
    "tokenNum": 175,
    "assetId": 3729195158,
    "name": "LENIN",
    "symbol": "LENIN",
    "assetUrl": "ipfs://bafkreiexample",
    "description": "",
    "socialWebsite": "",
    "socialX": "",
    "socialTelegram": "",
    "socialDiscord": "",
    "creator": "LOLJKAN3NMRMAWAPYKQOEJPVM37KKZCMM7DMGOU2CWN45RKSDZB6QAKSMI",
    "bondingTokenId": 0,
    "bondingOn": 0,
    "phase": "bonding",
    "progressPercent": 12.5,
    "priceBonding": "0.000001",
    "priceUsd": "0.000002",
    "realTokenReserves": "700000000000000",
    "realBondingReserves": "5000000",
    "initialRealTokenReserves": "800000000000000",
    "virtualTokenReserves": "1000000000000000",
    "virtualBondingReserves": "1000000000",
    "bondingTargetUsd": "2500000000",
    "priceMultiplier": "20",
    "poolAppId": 0,
    "lpTokenId": 0,
    "buyShapeKey": "mainnet:haystack:v1:buy:bonding",
    "userHolding": null,
    "note": null
  },
  "meta": {
    "paymentRequired": false
  }
}

3) Launch a token (paid execution quote)

Shape mainnet:haystack:v1:launch:token. bondingTokenId 0 is ALGO. Optional initialBuyAmount is bonding-asset base units. A first buy that would finish the curve is rejected.

curl -i https://canix402-api.compx.io/execution/quotes \
  -H 'content-type: application/json' \
  -d '{
    "quotes": [{
      "shapeKey": "mainnet:haystack:v1:launch:token",
      "input": {
        "userAddress": "YOUR_ALGORAND_ADDRESS",
        "symbol": "LENIN",
        "name": "Lenin",
        "assetUrl": "ipfs://bafkreiexample",
        "bondingTokenId": 0,
        "initialBuyAmount": "1000000"
      }
    }]
  }'

4) Buy a token still bonding (paid execution quote)

Shape mainnet:haystack:v1:buy:bonding. Pay the bonding asset directly, or pass another fromAssetId and the Haystack router supplies it in the same group. A routed buy that would take 90% or more of the remaining real reserves is refused; pay in the bonding asset for that size. slippageBps 50 is 0.50%.

# Direct ALGO buy (bondingTokenId 0)
curl -i https://canix402-api.compx.io/execution/quotes \
  -H 'content-type: application/json' \
  -d '{
    "quotes": [{
      "shapeKey": "mainnet:haystack:v1:buy:bonding",
      "input": {
        "userAddress": "YOUR_ALGORAND_ADDRESS",
        "tokenNum": 175,
        "fromAssetId": 0,
        "amount": "1000000",
        "slippageBps": 50
      }
    }]
  }'

# Routed from USDC (31566704) through the Haystack router
curl -i https://canix402-api.compx.io/execution/quotes \
  -H 'content-type: application/json' \
  -d '{
    "quotes": [{
      "shapeKey": "mainnet:haystack:v1:buy:bonding",
      "input": {
        "userAddress": "YOUR_ALGORAND_ADDRESS",
        "assetId": 3729195158,
        "fromAssetId": 31566704,
        "amount": "1000000",
        "slippageBps": 50
      }
    }]
  }'

Wallet positions (paid)

GET /positions?address= — normalized DeFi positions with compatibleExitShapeKeys / compatibleManageShapeKeys (~0.005 USDC). Use those keys with POST /execution/quotes to exit or manage.

1) Preflight (expect 402)

curl -i 'https://canix402-api.compx.io/positions?address=YOUR_ALGORAND_ADDRESS'

2) Paid retry

curl -i 'https://canix402-api.compx.io/positions?address=YOUR_ALGORAND_ADDRESS' \
  -H 'PAYMENT-SIGNATURE: <base64-json-payload>'

Sample response (200)

{
  "data": [
    {
      "protocol": "tinyman",
      "positionType": "lp",
      "positionId": "tinyman:lp:1002541853",
      "opportunityId": "tinyman:pool:1002541853",
      "assetId": 1002541853,
      "assetSymbol": "TMPOOL2",
      "amountRaw": "1250000",
      "amount": "1.250000",
      "usdValue": 42.5,
      "compatibleExitShapeKeys": [
        "mainnet:tinyman:v2:removeLiquidity:multipleAssetsOut",
        "mainnet:tinyman:v2:removeLiquidity:singleAssetOut"
      ],
      "compatibleManageShapeKeys": [],
      "inputHints": {
        "assetAId": 0,
        "assetBId": 31566704,
        "poolId": "1002541853"
      }
    },
    {
      "protocol": "compx",
      "positionType": "debt",
      "positionId": "compx:debt:3491050310",
      "opportunityId": "compx-lending-3491050310",
      "assetId": 31566704,
      "assetSymbol": "USDC",
      "amountRaw": "250000",
      "amount": "0.250000",
      "usdValue": 0.25,
      "healthFactor": 1.8,
      "compatibleExitShapeKeys": [
        "mainnet:compx:v1:repay:asa"
      ],
      "compatibleManageShapeKeys": [],
      "notes": "Locked collateral 500000 of asset 3491050311 (LST units).",
      "inputHints": {
        "marketAppId": 3491050310,
        "assetId": 31566704
      }
    }
  ],
  "protocols": [
    {
      "protocol": "tinyman",
      "status": "ok",
      "positionCount": 1,
      "message": null
    },
    {
      "protocol": "compx",
      "status": "ok",
      "positionCount": 1,
      "message": null
    }
  ],
  "totals": {
    "suppliedUsd": 42.5,
    "borrowedUsd": 0.25,
    "rewardsUsd": 0,
    "netUsd": 42.25
  },
  "meta": {
    "address": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    "fetchedAt": "2026-08-05T12:00:00.000Z",
    "paymentRequired": true
  }
}

Claim desk (paid)

GET /positions/claimable?address= — claimable rewards with USD value, network-fee / worth-claiming hints, and claimAllQuotes ready for POST /execution/quotes (~0.001 USDC list; compile stays ~0.10 USDC flat). Groups are never merged. Agent loop: positions (optional) → claimable → quote → local sign.

1) Preflight (expect 402)

curl -i 'https://canix402-api.compx.io/positions/claimable?address=YOUR_ALGORAND_ADDRESS'

2) Paid retry

curl -i 'https://canix402-api.compx.io/positions/claimable?address=YOUR_ALGORAND_ADDRESS' \
  -H 'PAYMENT-SIGNATURE: <base64-json-payload>'

3) Compile claim-all (use claimAllQuotes)

curl -i https://canix402-api.compx.io/execution/quotes \
  -H 'content-type: application/json' \
  -H 'PAYMENT-SIGNATURE: <base64-json-payload>' \
  -d '{
    "quotes": [
      {
        "shapeKey": "mainnet:haystack:v1:claim:rewards",
        "input": { "userAddress": "YOUR_ALGORAND_ADDRESS" }
      },
      {
        "shapeKey": "mainnet:compx:v1:claim:rewards",
        "input": { "userAddress": "YOUR_ALGORAND_ADDRESS", "poolAppId": 123456 }
      }
    ]
  }'

Sample response (200)

{
  "data": [
    {
      "protocol": "haystack",
      "positionId": "haystack:reward:3321763884:usdc",
      "opportunityId": "haystack-staking-hay",
      "positionType": "reward",
      "assetId": 31566704,
      "assetSymbol": "USDC",
      "amountRaw": "1500000",
      "amount": "1.500000",
      "usdValue": 1.5,
      "claimKey": "haystack:claim:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
      "compatibleClaimShapeKeys": [
        "mainnet:haystack:v1:claim:rewards"
      ],
      "quote": {
        "shapeKey": "mainnet:haystack:v1:claim:rewards",
        "input": {
          "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"
        }
      },
      "estimatedNetworkFeeMicroAlgos": "5000",
      "estimatedNetworkFeeUsd": 0.001,
      "worthClaiming": true,
      "caveats": [
        "Pending USDC from staker box; live accrual may be higher until the next drip/claim."
      ]
    },
    {
      "protocol": "haystack",
      "positionId": "haystack:reward:3321763884:hay",
      "opportunityId": "haystack-staking-hay",
      "positionType": "reward",
      "assetId": 3160000000,
      "assetSymbol": "HAY",
      "amountRaw": "25000000",
      "amount": "25.000000",
      "usdValue": 0.5,
      "claimKey": "haystack:claim:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
      "compatibleClaimShapeKeys": [
        "mainnet:haystack:v1:claim:rewards"
      ],
      "quote": {
        "shapeKey": "mainnet:haystack:v1:claim:rewards",
        "input": {
          "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"
        }
      },
      "estimatedNetworkFeeMicroAlgos": "5000",
      "estimatedNetworkFeeUsd": 0.001,
      "worthClaiming": true
    },
    {
      "protocol": "compx",
      "positionId": "compx:reward:123456:31566704",
      "opportunityId": "compx-staking-123456",
      "positionType": "reward",
      "assetId": 31566704,
      "assetSymbol": "USDC",
      "amountRaw": "500000",
      "amount": "0.500000",
      "usdValue": 0.5,
      "claimKey": "compx:claim:123456",
      "compatibleClaimShapeKeys": [
        "mainnet:compx:v1:claim:rewards"
      ],
      "quote": {
        "shapeKey": "mainnet:compx:v1:claim:rewards",
        "input": {
          "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
          "poolAppId": 123456
        }
      },
      "estimatedNetworkFeeMicroAlgos": "250000",
      "estimatedNetworkFeeUsd": 0.05,
      "worthClaiming": true,
      "inputHints": {
        "poolAppId": 123456,
        "assetId": 31566704
      }
    },
    {
      "protocol": "tinyman",
      "positionId": "tinyman:staked:stalgo:dummy",
      "opportunityId": "tinyman-staking-stalgo",
      "positionType": "staked",
      "assetId": 0,
      "assetSymbol": "stALGO",
      "amountRaw": "1000000",
      "amount": "1.000000",
      "usdValue": null,
      "claimKey": "tinyman:stalgo-claim:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
      "compatibleClaimShapeKeys": [
        "mainnet:tinyman:restake-v1:claimRewards:stAlgo"
      ],
      "quote": {
        "shapeKey": "mainnet:tinyman:restake-v1:claimRewards:stAlgo",
        "input": {
          "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"
        }
      },
      "estimatedNetworkFeeMicroAlgos": "2000",
      "estimatedNetworkFeeUsd": 0.0004,
      "worthClaiming": null,
      "caveats": [
        "Pending TINY restake rewards are not collected on positions; claim via manage shape."
      ]
    }
  ],
  "protocols": [
    {
      "protocol": "tinyman",
      "status": "ok",
      "positionCount": 1,
      "message": null
    },
    {
      "protocol": "compx",
      "status": "ok",
      "positionCount": 1,
      "message": null
    },
    {
      "protocol": "pact",
      "status": "ok",
      "positionCount": 0,
      "message": null
    },
    {
      "protocol": "haystack",
      "status": "ok",
      "positionCount": 2,
      "message": null
    },
    {
      "protocol": "alpha-arcade",
      "status": "ok",
      "positionCount": 0,
      "message": null
    }
  ],
  "totals": {
    "claimableUsd": null,
    "estimatedNetworkFeeUsd": 0.0514,
    "worthClaimingUsd": null
  },
  "claimAllQuotes": {
    "quotes": [
      {
        "shapeKey": "mainnet:haystack:v1:claim:rewards",
        "input": {
          "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"
        }
      },
      {
        "shapeKey": "mainnet:compx:v1:claim:rewards",
        "input": {
          "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
          "poolAppId": 123456
        }
      },
      {
        "shapeKey": "mainnet:tinyman:restake-v1:claimRewards:stAlgo",
        "input": {
          "userAddress": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"
        }
      }
    ]
  },
  "meta": {
    "address": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
    "fetchedAt": "2026-08-13T12:00:00.000Z",
    "algoUsd": 0.2,
    "paymentRequired": true
  }
}

Swaps (quote → opt-in → paid transactions)

Walletless multi-router swap flow. Amounts are integer asset base units. Free quote and opt-in; paid POST /swaps/transactions (~0.005 USDC). Canix never signs or submits.

1) Free quote

curl -i https://canix402-api.compx.io/swaps/quote \
  -H 'content-type: application/json' \
  -d '{
    "address": "YOUR_ALGORAND_ADDRESS",
    "fromAssetId": 0,
    "toAssetId": 31566704,
    "amount": "1000000",
    "type": "fixed-input"
  }'

Sample quote response (200)

{
  "data": {
    "router": "hogswap",
    "address": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    "fromAssetId": "0",
    "toAssetId": "31566704",
    "amount": "1000000",
    "type": "fixed-input",
    "quotedAmount": "185000",
    "minOut": "183150",
    "networkFeeMicroAlgos": "5000",
    "slippageBps": 100,
    "createdAt": "2026-07-30T12:00:00.000Z",
    "expiresAt": "2026-07-30T12:00:30.000Z",
    "score": {
      "expectedNetOut": "185000",
      "minOut": "183150",
      "expectedIn": "1000000",
      "maxIn": "1000000",
      "networkFeeMicroAlgos": "5000",
      "feeAlreadyNetted": true
    },
    "alternatives": [
      {
        "router": "haystack",
        "status": "quoted",
        "expectedNetOut": "180000",
        "minOut": "178200",
        "networkFeeMicroAlgos": "0"
      },
      {
        "router": "hogswap",
        "status": "quoted",
        "expectedNetOut": "185000",
        "minOut": "183150",
        "networkFeeMicroAlgos": "5000"
      }
    ],
    "legs": [
      {
        "protocol": "stamm",
        "poolId": "1002541853"
      }
    ],
    "payload": {
      "note": "opaque winner quote; pass unchanged"
    },
    "note": "Pass this quote object unchanged to /swaps/optin and /swaps/transactions. Refresh after any opt-in submit."
  },
  "meta": {
    "paymentRequired": false,
    "executionSubmitted": false
  }
}

2) Free opt-in (pass quote unchanged)

curl -i https://canix402-api.compx.io/swaps/optin \
  -H 'content-type: application/json' \
  -d '{
    "address": "YOUR_ALGORAND_ADDRESS",
    "quote": { /* full data object from /swaps/quote */ }
  }'

Sample opt-in response (200)

{
  "data": {
    "required": true,
    "transactions": [
      {
        "type": "axfer",
        "note": "asset opt-in for toAssetId",
        "encoded": "<base64-unsigned-txn-0>"
      },
      {
        "type": "appl",
        "note": "application opt-in for requiredAppOptIns[0]",
        "encoded": "<base64-unsigned-txn-1>"
      }
    ],
    "userSignIndexes": [
      0,
      1
    ],
    "createdAt": "2026-07-30T12:00:05.000Z",
    "expiresAt": "2026-07-30T12:02:00.000Z"
  },
  "meta": {
    "paymentRequired": false,
    "executionSubmitted": false,
    "note": "Empty transactions[] means no opt-ins are missing. Sign and submit locally, then fetch a fresh quote before /swaps/transactions."
  }
}

If transactions is non-empty, sign and submit locally, wait for confirmation, then fetch a fresh quote before the paid step.

3) Paid transactions — preflight (expect 402)

curl -i https://canix402-api.compx.io/swaps/transactions \
  -H 'content-type: application/json' \
  -d '{
    "address": "YOUR_ALGORAND_ADDRESS",
    "quote": { /* fresh quote data object */ },
    "slippage": 0.005
  }'

4) Paid transactions — retry with PAYMENT-SIGNATURE

curl -i https://canix402-api.compx.io/swaps/transactions \
  -H 'content-type: application/json' \
  -H 'PAYMENT-SIGNATURE: <base64-json-payload>' \
  -d '{
    "address": "YOUR_ALGORAND_ADDRESS",
    "quote": { /* fresh quote data object */ },
    "slippage": 0.005
  }'

Sample transactions response (200)

{
  "data": {
    "router": "hogswap",
    "transactions": [
      {
        "index": 0,
        "encodedTransaction": "<base64-txn-0-user-sign>",
        "signer": "user"
      },
      {
        "index": 1,
        "encodedTransaction": "<base64-txn-1-protocol>",
        "signer": "protocol"
      },
      {
        "index": 2,
        "encodedTransaction": "<base64-txn-2-user-sign>",
        "signer": "user"
      }
    ],
    "userSignIndexes": [
      0,
      2
    ],
    "createdAt": "2026-07-30T12:00:30.000Z",
    "quoteExpiresAt": "2026-07-30T12:00:30.000Z",
    "note": "Sign only userSignIndexes members. Preserve pre-signed members and atomic group order. Submit the complete group via your own Algod client."
  },
  "meta": {
    "paymentRequired": true,
    "executionSubmitted": false
  }
}

Sign only userSignIndexes, keep any pre-signed members and group order, then submit the complete atomic group through your own Algod client.

Prepaid agent sessions

One POST /sessions (~0.25 USDC) mints a walletless receipt that unlocks N research calls and M quotes/plans until TTL. Send X-Canix-Session on session-eligible routes instead of spraying tiny one-shots. Create/refresh stay one-shot only. Fail-closed on expiry or exhausted quota — omit the header to fall back to per-request payment.

1) Buy a session (expect 402, then retry with PAYMENT-SIGNATURE)

curl -i https://canix402-api.compx.io/sessions -X POST -H 'content-type: application/json' -d '{}'

2) Read remaining N/M (free)

curl -s https://canix402-api.compx.io/sessions/csess_YOUR_SESSION_ID | jq '.data.remaining'

3) Consume research with the receipt

curl -i 'https://canix402-api.compx.io/opportunities' -H 'X-Canix-Session: csess_YOUR_SESSION_ID'

Sample receipt (200)

{
  "data": {
    "uri": "canix://session/csess_demo",
    "sessionId": "csess_demo",
    "createdAt": "2026-08-24T12:00:00.000Z",
    "expiresAt": "2026-08-24T16:00:00.000Z",
    "ttlSeconds": 14400,
    "budget": {
      "research": 50,
      "quotes": 10
    },
    "remaining": {
      "research": 49,
      "quotes": 10
    },
    "consumed": {
      "research": 1,
      "quotes": 0
    },
    "status": "active"
  },
  "meta": {
    "paymentRequired": false,
    "access": "receipt",
    "receiptUri": "canix://session/csess_demo"
  }
}

Watch / webhook retainers

One POST /watch (~0.25 USDC) registers a walletless address + thresholds retainer instead of polling /positions. Optional HTTPS webhook; firings are HMAC-signed (X-Canix-Signature) and idempotent (X-Canix-Idempotency-Key). Canix never stores wallet keys. No webhook means MCP-only: read firings from the free receipt.

1) Register a watch (expect 402, then retry with PAYMENT-SIGNATURE)

curl -i https://canix402-api.compx.io/watch -X POST -H 'content-type: application/json' -d '{"address":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ","thresholds":{"healthFactor":1.25,"claimableUsd":8},"webhookUrl":"https://hooks.example/canix"}'

2) Read recent firings (free; secret never returned)

curl -s https://canix402-api.compx.io/watch/cwatch_YOUR_WATCH_ID | jq '.data.firings'

3) Rotate the HMAC secret (free; send the current secret)

curl -i https://canix402-api.compx.io/watch/cwatch_YOUR_WATCH_ID/rotate-secret -X POST -H 'X-Canix-Watch-Secret: wsec_YOUR_CURRENT_SECRET'

Sample receipt (200)

{
  "data": {
    "uri": "canix://watch/cwatch_demo",
    "watchId": "cwatch_demo",
    "address": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
    "thresholds": {
      "healthFactor": 1.25,
      "claimableUsd": 8,
      "apyDropBps": 100,
      "retiCapacity": true
    },
    "webhookUrl": "https://hooks.example/canix",
    "createdAt": "2026-09-03T12:00:00.000Z",
    "expiresAt": "2026-09-04T12:00:00.000Z",
    "ttlSeconds": 86400,
    "status": "active",
    "firings": [
      {
        "firingId": "wfire_demo",
        "idempotencyKey": "wfire_cwatch_demo_healthFactor_wep_demo",
        "kind": "healthFactor",
        "firedAt": "2026-09-03T12:05:00.000Z",
        "threshold": 1.25,
        "previous": 1.4,
        "current": 1.1,
        "delivered": true,
        "deliveryStatus": "delivered"
      }
    ]
  },
  "meta": {
    "paymentRequired": false,
    "access": "receipt",
    "receiptUri": "canix://watch/cwatch_demo",
    "secretShown": false
  }
}

Error responses

Paid routes return 402 before payment; validation and server errors use a stable envelope.

402 — payment required

{
  "error": {
    "code": "MISSING_PAYMENT_SIGNATURE",
    "message": "The paid endpoint was called without PAYMENT-SIGNATURE."
  }
}

400 — invalid Algorand address (personalized route)

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Query parameter 'address' is not a valid Algorand address."
  }
}