{
  "openapi": "3.1.0",
  "info": {
    "title": "MemeSwap Public API",
    "version": "0.1.0-beta",
    "description": "Current, unsupported beta endpoints used by the MemeSwap web interface for token research, public-address portfolio reads, route quotes, and unsigned transaction construction. The service does not sign or custody transactions. Provider availability and returned fields can change during the beta.",
    "contact": {
      "name": "Agentic Labs LLC",
      "url": "https://memeswap.fun/contact.html"
    }
  },
  "servers": [
    {
      "url": "https://memeswap.fun",
      "description": "MemeSwap beta"
    }
  ],
  "security": [],
  "tags": [
    { "name": "Research", "description": "Read-only token, chain, and public-address observations." },
    { "name": "Routing", "description": "Provider quotes and unsigned transaction construction." },
    { "name": "Solana RPC", "description": "Allowlisted reads and bounded relay of already-signed bytes." }
  ],
  "paths": {
    "/api/tokens": {
      "get": {
        "operationId": "listTokens",
        "tags": ["Research"],
        "summary": "List the current token research set",
        "description": "Returns currently listed token market and risk-context rows. The server rebuilds this representation at most once every 10 seconds; source timestamps in each row may be older.",
        "security": [],
        "responses": {
          "200": {
            "description": "Current token rows and source update context.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TokenList" }
              }
            }
          }
        }
      }
    },
    "/api/token/{chain}/{address}": {
      "get": {
        "operationId": "getToken",
        "tags": ["Research"],
        "summary": "Read a token profile",
        "description": "Returns the stored market, holder, trade, and risk-research record for one listed token. Observations carry their own timestamps and are not real-time guarantees.",
        "security": [],
        "parameters": [
          {
            "name": "chain",
            "in": "path",
            "required": true,
            "description": "MemeSwap chain key from GET /api/chains.",
            "schema": { "type": "string" }
          },
          {
            "name": "address",
            "in": "path",
            "required": true,
            "description": "Token address or chain-specific token identifier.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Stored token profile and observations.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TokenProfile" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/chains": {
      "get": {
        "operationId": "listChains",
        "tags": ["Research"],
        "summary": "List network metadata",
        "description": "Returns the network identifiers, native asset metadata, public RPC candidates, explorers, and available route provider labels used by the browser.",
        "security": [],
        "responses": {
          "200": {
            "description": "Configured network metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["chains"],
                  "properties": {
                    "chains": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Chain" }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/portfolio": {
      "get": {
        "operationId": "readPortfolio",
        "tags": ["Research"],
        "summary": "Read holdings for a public address",
        "description": "Reads on-chain holdings for a caller-supplied EVM or Solana public address and attaches available MemeSwap or provider prices. At least address or sol is required. Missing prices remain unpriced. Identical requests may reuse a response for 30 seconds. Cache misses share a fixed 60-second budget of 12 requests per client address and 300 globally.",
        "security": [],
        "parameters": [
          {
            "name": "address",
            "in": "query",
            "required": false,
            "description": "Public EVM address. Required when sol is omitted.",
            "schema": { "type": "string", "pattern": "^0x[0-9a-fA-F]{40}$" }
          },
          {
            "name": "sol",
            "in": "query",
            "required": false,
            "description": "Public Solana address. Required when address is omitted.",
            "schema": { "type": "string", "minLength": 32, "maxLength": 44 }
          },
          {
            "name": "chains",
            "in": "query",
            "required": false,
            "description": "Comma-separated EVM chain keys. Defaults to chains represented in the current token set.",
            "schema": { "type": "string" }
          },
          {
            "name": "dust",
            "in": "query",
            "required": false,
            "description": "USD display threshold for unlisted holdings. Defaults to 0.01.",
            "schema": { "type": "number", "minimum": 0, "default": 0.01 }
          }
        ],
        "responses": {
          "200": {
            "description": "Holdings, available prices, and aggregate totals as observed by the server.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Portfolio" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/quote": {
      "get": {
        "operationId": "getSwapQuote",
        "tags": ["Routing"],
        "summary": "Request a swap quote",
        "description": "Requests an indicative route from the configured provider for a supported chain. Quotes can expire or become stale and do not execute a transaction. This route shares a fixed 60-second budget with build and xquote: 60 requests per client address and 1,200 globally.",
        "security": [],
        "parameters": [
          {
            "name": "chain",
            "in": "query",
            "required": true,
            "description": "MemeSwap chain key from GET /api/chains.",
            "schema": { "type": "string" }
          },
          {
            "name": "side",
            "in": "query",
            "required": true,
            "schema": { "type": "string", "enum": ["buy", "sell"] }
          },
          {
            "name": "token",
            "in": "query",
            "required": true,
            "description": "Chain-specific token address or identifier.",
            "schema": { "type": "string" }
          },
          {
            "name": "amount",
            "in": "query",
            "required": true,
            "description": "Positive decimal in native units for a buy or token units for a sell.",
            "schema": { "type": "string", "pattern": "^\\d*\\.?\\d+$" }
          },
          {
            "name": "slippageBps",
            "in": "query",
            "required": false,
            "description": "Slippage tolerance in basis points. Defaults to 300.",
            "schema": { "type": "integer", "minimum": 1, "maximum": 5000, "default": 300 }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Public sender address used for sender/recipient-aware routing. Required later by build.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Provider route quote.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Quote" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/QuoteBadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamFailure" },
          "default": { "$ref": "#/components/responses/ProviderStatus" }
        }
      }
    },
    "/api/build": {
      "post": {
        "operationId": "buildSwapTransaction",
        "tags": ["Routing"],
        "summary": "Build unsigned swap transaction material",
        "description": "Builds unsigned, caller-bound transaction material from validated swap fields and a prior quote. The service does not authorize, sign, submit, or confirm it; the caller must review it in a compatible wallet. This route shares the quote-build fixed 60-second budget of 60 requests per client address and 1,200 globally.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BuildRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Unsigned chain-specific transaction material for wallet review.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BuildResult" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamFailure" },
          "default": { "$ref": "#/components/responses/ProviderStatus" }
        }
      }
    },
    "/api/xquote": {
      "get": {
        "operationId": "getCrossChainQuote",
        "tags": ["Routing"],
        "summary": "Request a cross-chain quote",
        "description": "Requests a LI.FI route and returns its estimate plus chain-specific transaction material for caller review and signing. It does not submit or confirm a transaction. This route shares the quote-build fixed 60-second budget of 60 requests per client address and 1,200 globally.",
        "security": [],
        "parameters": [
          {
            "name": "fromChain",
            "in": "query",
            "required": true,
            "description": "MemeSwap chain key or numeric LI.FI chain identifier.",
            "schema": { "type": "string" }
          },
          {
            "name": "toChain",
            "in": "query",
            "required": true,
            "description": "MemeSwap chain key or numeric LI.FI chain identifier.",
            "schema": { "type": "string" }
          },
          {
            "name": "fromToken",
            "in": "query",
            "required": false,
            "description": "Source token address or native. Defaults to native.",
            "schema": { "type": "string", "default": "native" }
          },
          {
            "name": "toToken",
            "in": "query",
            "required": false,
            "description": "Destination token address or native. Defaults to native.",
            "schema": { "type": "string", "default": "native" }
          },
          {
            "name": "amount",
            "in": "query",
            "required": true,
            "description": "Positive decimal in source-token units.",
            "schema": { "type": "string", "pattern": "^\\d*\\.?\\d+$" }
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "description": "Public source-chain address used as the transaction sender.",
            "schema": { "type": "string" }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Public destination-chain recipient. Defaults to from and is required when address formats differ.",
            "schema": { "type": "string" }
          },
          {
            "name": "slippage",
            "in": "query",
            "required": false,
            "description": "Decimal slippage fraction forwarded to the route provider.",
            "schema": { "type": "number", "minimum": 0 }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Optional LI.FI route ordering preference.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Cross-chain estimate and caller-bound transaction material.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CrossChainQuote" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamFailure" },
          "default": { "$ref": "#/components/responses/ProviderStatus" }
        }
      }
    },
    "/api/xstatus": {
      "get": {
        "operationId": "getCrossChainStatus",
        "tags": ["Routing"],
        "summary": "Read bridge transaction status",
        "description": "Passes a transaction identifier and optional route hints to LI.FI and returns its current status response. A provider status is observational and may lag final chain state.",
        "security": [],
        "parameters": [
          {
            "name": "txHash",
            "in": "query",
            "required": true,
            "description": "EVM transaction hash or Solana signature.",
            "schema": { "type": "string" }
          },
          {
            "name": "fromChain",
            "in": "query",
            "required": false,
            "description": "MemeSwap chain key or numeric LI.FI chain identifier.",
            "schema": { "type": "string" }
          },
          {
            "name": "toChain",
            "in": "query",
            "required": false,
            "description": "MemeSwap chain key or numeric LI.FI chain identifier.",
            "schema": { "type": "string" }
          },
          {
            "name": "bridge",
            "in": "query",
            "required": false,
            "description": "Optional LI.FI bridge/provider hint.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Current provider status payload.",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": true }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "502": { "$ref": "#/components/responses/UpstreamFailure" },
          "default": { "$ref": "#/components/responses/ProviderStatus" }
        }
      }
    },
    "/api/solrpc": {
      "post": {
        "operationId": "callBoundedSolanaRpc",
        "tags": ["Solana RPC"],
        "summary": "Call the bounded Solana RPC relay",
        "description": "Forwards an allowlisted JSON-RPC read or simulation method. sendTransaction and sendTransactions accept only already-signed serialized Solana transactions: the relay verifies signatures, enforces the 1,232-byte transaction limit and preflight, and cannot sign, alter, or authorize a transaction. Reads use a fixed 60-second budget of 60 per client address and 1,200 globally; single submissions use 6 and 120; batches of 2-10 use 2 and 20.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SolanaRpcRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result returned by the configured Solana provider.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SolanaRpcResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamFailure" },
          "default": { "$ref": "#/components/responses/ProviderStatus" }
        }
      }
    }
  },
  "components": {
    "responses": {
      "BadRequest": {
        "description": "The request failed validation.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "QuoteBadRequest": {
        "description": "The quote request failed validation, no route was available, or no aggregator is configured for the requested chain.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "NotFound": {
        "description": "The requested token was not found.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "RateLimited": {
        "description": "The current fixed-window client or global budget is exhausted.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "UpstreamFailure": {
        "description": "A route or RPC provider was unavailable or returned an unusable response.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "ProviderStatus": {
        "description": "Another HTTP error status associated with a route or RPC provider failure. Depending on the operation and provider path, an unlisted upstream status may be passed through. Failures normalized locally return the documented 502 response; the JSON body may be a local error envelope or the provider's response.",
        "content": {
          "application/json": {
            "schema": { "type": "object", "additionalProperties": true }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string" },
          "scope": { "type": "string" }
        },
        "additionalProperties": true
      },
      "TokenList": {
        "type": "object",
        "required": ["tokens", "books", "updatedAt"],
        "properties": {
          "tokens": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/TokenRow" }
          },
          "books": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "gated": { "type": "integer" },
          "updatedAt": { "type": "integer", "description": "Unix epoch milliseconds when the cached representation was built." }
        }
      },
      "TokenRow": {
        "type": "object",
        "required": ["chain", "address", "symbol", "name"],
        "properties": {
          "chain": { "type": "string" },
          "address": { "type": "string" },
          "symbol": { "type": ["string", "null"] },
          "name": { "type": ["string", "null"] },
          "price": { "type": ["number", "null"] },
          "priceTs": { "type": ["integer", "null"] },
          "priceStale": { "type": "boolean" },
          "mcap": { "type": ["number", "null"] },
          "liq": { "type": ["number", "null"] },
          "vol24": { "type": ["number", "null"] },
          "holders": { "type": ["number", "null"] },
          "security": { "type": ["object", "null"], "additionalProperties": true },
          "lp": { "type": ["object", "null"], "additionalProperties": true },
          "book": { "type": "string" },
          "metaTs": { "type": ["integer", "null"] },
          "profileTs": { "type": ["integer", "null"] }
        },
        "additionalProperties": true
      },
      "TokenProfile": {
        "type": "object",
        "required": ["chain", "token", "updatedAt"],
        "properties": {
          "chain": { "type": "string" },
          "token": { "type": "string" },
          "updatedAt": { "type": "integer" },
          "row": { "oneOf": [{ "$ref": "#/components/schemas/TokenRow" }, { "type": "null" }] },
          "profile": { "type": ["object", "null"], "additionalProperties": true },
          "holders": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "early": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "candles": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "trades": { "type": "array", "items": { "type": "object", "additionalProperties": true } }
        },
        "additionalProperties": true
      },
      "Chain": {
        "type": "object",
        "required": ["key", "name", "native", "decimals", "explorer"],
        "properties": {
          "key": { "type": "string" },
          "evm": { "type": "boolean" },
          "sui": { "type": "boolean" },
          "ton": { "type": "boolean" },
          "chainId": { "type": ["integer", "null"] },
          "chainIdHex": { "type": ["string", "null"] },
          "name": { "type": "string" },
          "native": { "type": "string" },
          "decimals": { "type": "integer" },
          "rpc": { "type": "array", "items": { "type": "string", "format": "uri" } },
          "explorer": { "type": "string", "format": "uri" },
          "aggregator": { "type": ["string", "null"] },
          "lifiChainId": { "type": ["integer", "null"] }
        }
      },
      "Portfolio": {
        "type": "object",
        "required": ["chains", "totalUsd", "tokenCount", "unpriced", "at"],
        "properties": {
          "address": { "type": ["string", "null"] },
          "sol": { "type": ["string", "null"] },
          "chains": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "totalUsd": { "type": "number" },
          "tokenCount": { "type": "integer" },
          "unpriced": { "type": "integer" },
          "at": { "type": "string", "format": "date-time" }
        }
      },
      "Quote": {
        "type": "object",
        "properties": {
          "provider": { "type": "string" },
          "chain": { "type": "string" },
          "chainId": { "type": ["integer", "null"] },
          "side": { "type": "string", "enum": ["buy", "sell"] },
          "from": { "type": ["string", "null"] },
          "amountIn": { "type": "string" },
          "amountInHuman": { "type": "number" },
          "expectedOut": { "type": ["string", "null"] },
          "expectedOutHuman": { "type": ["number", "null"] },
          "minOut": { "type": ["string", "null"] },
          "slippageBps": { "type": "integer" },
          "priceImpactPct": { "type": ["number", "null"] },
          "route": { "type": "object", "additionalProperties": true },
          "error": false
        },
        "additionalProperties": true
      },
      "BuildRequest": {
        "type": "object",
        "required": ["chain", "side", "token", "amount", "from", "quote"],
        "properties": {
          "chain": { "type": "string" },
          "side": { "type": "string", "enum": ["buy", "sell"] },
          "token": { "type": "string" },
          "amount": { "type": "string", "pattern": "^\\d*\\.?\\d+$" },
          "slippageBps": { "type": "integer", "minimum": 1, "maximum": 5000, "default": 300 },
          "from": { "type": "string", "description": "Public signing-wallet address; the output is bound to this caller." },
          "quote": { "type": "object", "description": "The prior /api/quote response.", "additionalProperties": true }
        },
        "additionalProperties": false
      },
      "BuildResult": {
        "type": "object",
        "required": ["provider", "chain", "from"],
        "properties": {
          "provider": { "type": "string" },
          "chain": { "type": "string" },
          "chainId": { "type": ["integer", "null"] },
          "from": { "type": "string" },
          "to": { "type": "string" },
          "data": { "type": "string" },
          "value": { "type": "string" },
          "approval": { "type": ["object", "null"], "additionalProperties": true },
          "swapTransaction": { "type": "string", "contentEncoding": "base64" },
          "solanaTransaction": { "type": "string", "contentEncoding": "base64" },
          "suiTransaction": { "type": "string", "contentEncoding": "base64" },
          "tonMessage": { "type": "object", "additionalProperties": true },
          "lastValidBlockHeight": { "type": "integer" }
        },
        "additionalProperties": true
      },
      "CrossChainQuote": {
        "type": "object",
        "required": ["provider", "kind", "fromChain", "toChain", "from", "to", "estimate"],
        "properties": {
          "provider": { "const": "lifi" },
          "id": { "type": "string" },
          "kind": { "type": "string", "enum": ["evm", "solana", "sui"] },
          "fromChain": { "type": "integer" },
          "toChain": { "type": "integer" },
          "from": { "type": "string" },
          "to": { "type": "string" },
          "estimate": { "type": "object", "additionalProperties": true },
          "transactionRequest": { "type": ["object", "null"], "additionalProperties": true },
          "solanaTransaction": { "type": ["string", "null"], "contentEncoding": "base64" },
          "suiTransaction": { "type": ["string", "null"], "contentEncoding": "base64" }
        },
        "additionalProperties": true
      },
      "SolanaRpcRequest": {
        "type": "object",
        "required": ["jsonrpc", "method", "params"],
        "properties": {
          "jsonrpc": { "const": "2.0" },
          "id": {},
          "method": {
            "type": "string",
            "enum": [
              "getBalance",
              "getTokenAccountsByOwner",
              "getLatestBlockhash",
              "getSignatureStatuses",
              "getAccountInfo",
              "getMultipleAccounts",
              "getTokenAccountBalance",
              "sendTransaction",
              "sendTransactions",
              "simulateTransaction",
              "getTransaction",
              "getBlockHeight",
              "getSlot",
              "getRecentPrioritizationFees"
            ]
          },
          "params": { "type": "array" }
        },
        "additionalProperties": false
      },
      "SolanaRpcResponse": {
        "type": "object",
        "required": ["jsonrpc"],
        "properties": {
          "jsonrpc": { "const": "2.0" },
          "id": {},
          "result": {},
          "error": {}
        },
        "additionalProperties": true
      }
    }
  }
}
