Skill: lifeofpavs/jup-skill (version 1.0.2)

--- name: jupiter-skill description: Execute Jupiter API operations on Solana - fetch quotes, sign transactions, execute swaps, prediction markets. Use when implementing token swaps, DCA, limit orders, lending, prediction markets, or any Jupiter integration. Includes scripts for Ultra and Metis swap flows. metadata: primary_credential: JUP_API_KEY required_environment_variables: - JUP_API_KEY optional_environment_variables: - SOLANA_RPC_URL required_config_paths: - ~/.config/solana/id.json sensitive_inputs: - Solana wallet JSON file containing private key material --- # Jupiter API Skill Execute Jupiter API operations through 4 utility scripts for fetching data, signing transactions, and executing swaps on Solana. **Base URL**: `https://api.jup.ag` ## Quick Reference | Task | Script | Example | |------|--------|---------| | Fetch any Jupiter API | `fetch-api.ts` | `pnpm fetch-api -e /ultra/v1/search -p '{"query":"SOL"}'` | | Sign a transaction | `wallet-sign.ts` | `pnpm wallet-sign -t "BASE64_TX" -w ~/.config/solana/id.json` | | Execute Ultra order | `execute-ultra.ts` | `pnpm execute-ultra -r "REQUEST_ID" -t "SIGNED_TX"` | | Send tx to RPC | `send-transaction.ts` | `pnpm send-transaction -t "SIGNED_TX"` | ## Setup Install dependencies before using scripts: ```bash cd /path/to/jup-skill pnpm install ``` Run `pnpm install` once per clone (and again after dependency changes) before any `pnpm fetch-api`, `pnpm wallet-sign`, `pnpm execute-ultra`, or `pnpm send-transaction` command. ## API Key Setup **ALWAYS required.** All Jupiter API endpoints require an `x-api-key` header. 1. Visit [portal.jup.ag](https://portal.jup.ag) 2. Create account and generate API key 3. Set via environment variable (recommended): ```bash export JUP_API_KEY=your_api_key_here ``` Or pass via `--api-key` flag on each command. ## Wallet Safety Signing requires access to a local Solana wallet JSON file (`--wallet`), which contains private key material. - Do not use a high-value wallet for automation. - Prefer a dedicated low-balance wallet for this workflow. - For testing, prefer ephemeral keys. - If your setup supports it, prefer hardware signing over raw key files. ## Scripts ### fetch-api.ts Fetch data from any Jupiter API endpoint. ```bash # Search for tokens pnpm fetch-api -e /ultra/v1/search -p '{"query":"SOL"}' # Get Ultra swap order (quote + unsigned transaction) pnpm fetch-api -e /ultra/v1/order -p '{ "inputMint": [REDACTED_SECRET], "outputMint": [REDACTED_SECRET], "amount": "1000000", "taker": "YOUR_WALLET_ADDRESS" }' # Get Metis quote pnpm fetch-api -e /swap/v1/quote -p '{ "inputMint": [REDACTED_SECRET], "outputMint": [REDACTED_SECRET], "amount": "1000000", "slippageBps": "50" }' # POST request (for Metis swap transaction) pnpm fetch-api -e /swap/v1/swap -m POST -b '{ "quoteResponse": {...}, "userPublicKey": "YOUR_WALLET" }' ``` **Arguments:** - `-e, --endpoint` (required): API path, e.g., `/ultra/v1/order` - `-p, --params`: Query params (GET) or body (POST) as JSON string - `-b, --body`: Request body for POST requests - `-m, --method`: HTTP method, `GET` (default) or `POST` - `-k, --api-key`: API key (or use `JUP_API_KEY` env var) ### wallet-sign.ts Sign transactions using a local wallet file. > **SECURITY NOTE**: The `--wallet` flag is required. This script does not accept private keys via command line arguments to prevent exposure in shell history and process listings. ```bash # Using Solana CLI wallet (JSON array format) pnpm wallet-sign -t "BASE64_UNSIGNED_TX" --wallet ~/.config/solana/id.json # Tilde expansion is supported pnpm wallet-sign -t "BASE64_UNSIGNED_TX" --wallet ~/my-wallets/trading.json ``` **Arguments:** - `-t, --unsigned-tx` (required): Base64-encoded unsigned transaction - `-w, --wallet` (required): Path to Solana CLI JSON wallet file (supports ~ for home directory) **Output:** Signed transaction (base64) to stdout. ### execute-ultra.ts Execute Ultra orders after signing. ```bash pnpm execute-ultra -r "REQUEST_ID_FROM_ORDER" -t "BASE64_SIGNED_TX" ``` **Arguments:** - `-r, --request-id` (required): Request ID from `/ultra/v1/order` response - `-t, --signed-tx` (required): Base64-encoded signed transaction - `-k, --api-key`: API key (or use `JUP_API_KEY` env var) **Output:** Execution result JSON including signature and status. ### send-transaction.ts Send signed transactions to Solana RPC. **Use for Metis swaps** (Ultra handles RPC internally). > **Warning**: The default public Solana RPC (`api.mainnet-beta.solana.com`) is rate-limited and unreliable for production use. Use a dedicated RPC provider (Helius, QuickNode, Triton, etc.) for production applications. ```bash # Default RPC (mainnet-beta) pnpm send-transaction -t "BASE64_SIGNED_TX" # Custom RPC pnpm send-transaction -t "BASE64_SIGNED_TX" -r "https://your-rpc.com" # With environment variable export SOLANA_RPC_URL="https://your-rpc.com" pnpm send-transaction -t "BASE64_SIGNED_TX" ``` **Arguments:** - `-t, --signed-tx` (required): Base64-encoded signed transaction - `-r, --rpc-url`: RPC endpoint (default: `https://api.mainnet-beta.solana.com`) - `--skip-preflight`: Skip preflight checks (faster, less safe) - `--max-retries`: Max send retries (default: 3) **Output:** Transaction signature to stdout. --- ## Workflows ### Ultra Swap (Recommended) Ultra is RPC-less, gasless, with automatic slippage optimization. ``` Ultra Swap Progress: - [ ] Step 1: Get order from /ultra/v1/order - [ ] Step 2: Sign the transaction - [ ] Step 3: Execute via /ultra/v1/execute - [ ] Step 4: Verify result ``` **Step 1: Get Order** ```bash ORDER=$(pnpm fetch-api -e /ultra/v1/order -p '{ "inputMint": [REDACTED_SECRET], "outputMint": [REDACTED_SECRET], "amount": "1000000", "taker": "YOUR_WALLET_ADDRESS" }') echo "$ORDER" ``` Response contains `requestId` and `transaction` (base64 unsigned). **Step 2: Sign Transaction** Extract transaction from response and sign: ```bash UNSIGNED_TX=$(echo "$ORDER" | jq -r '.transaction') SIGNED_TX=$(pnpm wallet-sign -t "$UNSIGNED_TX" -w ~/.config/solana/id.json) ``` **Step 3: Execute Order** ```bash REQUEST_ID=$(echo "$ORDER" | jq -r '.requestId') pnpm execute-ultra -r "$REQUEST_ID" -t "$SIGNED_TX" ``` **Step 4: Verify** Check the signature on [Solscan](https://solscan.io). --- ### Metis Swap (Advanced) Use Metis when you need custom transaction composition or fine-grained control. ``` Metis Swap Progress: - [ ] Step 1: Get quote from /swap/v1/quote - [ ] Step 2: Build transaction via /swap/v1/swap - [ ] Step 3: Sign the transaction - [ ] Step 4: Send to RPC - [ ] Step 5: Verify on-chain ``` **Step 1: Get Quote** ```bash QUOTE=$(pnpm fetch-api -e /swap/v1/quote -p '{ "inputMint": [REDACTED_SECRET], "outputMint": [REDACTED_SECRET], "amount": "1000000", "slippageBps": "50" }') ``` **Step 2: Build Transaction** ```bash SWAP_TX=$(pnpm fetch-api -e /swap/v1/swap -m POST -b "{ \"quoteResponse\": $QUOTE, \"userPublicKey\": \"YOUR_WALLET_ADDRESS\", \"dynamicComputeUnitLimit\": true, \"prioritizationFeeLamports\": \"auto\" }") ``` **Step 3: Sign** ```bash UNSIGNED_TX=$(echo "$SWAP_TX" | jq -r '.swapTransaction') SIGNED_TX=$(pnpm wallet-sign -t "$UNSIGNED_TX" --wallet ~/.config/solana/id.json) ``` **Step 4: Send to RPC** ```bash pnpm send-transaction -t "$SIGNED_TX" -r "https://your-rpc.com" ``` **Step 5: Verify** Check signature on Solscan. --- ### Prediction Markets (Beta) Trade on real-world event outcomes. Contracts trade $0-$1 USD, with prices reflecting outcome probability. ``` Prediction Market Flow: - [ ] Step 1: Browse events/markets - [ ] Step 2: Create order (buy YES/NO contracts) - [ ] Step 3: Sign and send transaction - [ ] Step 4: Monitor position - [ ] Step 5: Claim winnings (if correct) ``` **Step 1: Browse Events** ```bash # Search for events pnpm fetch-api -e /prediction/v1/events/search -p '{"query":"election","limit":"10"}' # List all events pnpm fetch-api -e /prediction/v1/events -p '{"category":"politics","includeMarkets":"true"}' # Get specific event with markets pnpm fetch-api -e /prediction/v1/events/{eventId} -p '{"includeMarkets":"true"}' ``` **Step 2: Create Order** ```bash # Buy YES contracts on a market ORDER=$(pnpm fetch-api -e /prediction/v1/orders -m POST -b '{ "ownerPubkey": "YOUR_WALLET_ADDRESS", "marketId": "MARKET_ID", "isYes": true, "isBuy": true, "contracts": 10, "maxBuyPriceUsd": 0.65 }') ``` Response contains `transaction` (base64 unsigned) and order details. **Step 3: Sign and Send** ```bash UNSIGNED_TX=$(echo "$ORDER" | jq -r '.transaction') SIGNED_TX=$(pnpm wallet-sign -t "$UNSIGNED_TX" -w ~/.config/solana/id.json) pnpm send-transaction -t "$SIGNED_TX" -r "YOUR_RPC_URL" ``` **Step 4: Monitor Position** ```bash # List your positions pnpm fetch-api -e /prediction/v1/positions -p '{"ownerPubkey":"YOUR_WALLET_ADDRESS"}' # Get specific position pnpm fetch-api -e /prediction/v1/positions/{positionPubkey} # View order history pnpm fetch-api -e /prediction/v1/history -p '{"ownerPubkey":"YOUR_WALLET_ADDRESS"}' ``` **Step 5: Claim Winnings** After market resolves, claim payout for winning positions: ```bash CLAIM=$(pnpm fetch-api -e /prediction/v1/positions/{positionPubkey}/claim -m POST -b '{ "ownerPubkey": "YOUR_WALLET_ADDRESS" }') UNSIGNED_TX=$(echo "$CLAIM" | jq -r '.transaction') SIGNED_TX=$(pnpm wallet-sign -t "$UNSIGNED_TX" -w ~/.config/solana/id.json) pnpm send-transaction -t "$SIGNED_TX" -r "YOUR_RPC_URL" ``` **Prediction API Endpoints** | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/prediction/v1/events` | List events with filters | | GET | `/prediction/v1/events/search` | Search events by query | | GET | `/prediction/v1/events/{eventId}` | Get event details | | GET | `/prediction/v1/markets/{marketId}` | Get market details | | GET | `/prediction/v1/orderbook/{marketId}` | Get orderbook | | POST | `/prediction/v1/orders` | Create order (returns unsigned tx) | | DELETE | `/prediction/v1/orders` | Close/cancel order | | GET | `/prediction/v1/positions` | List user positions | | DELETE | `/prediction/v1/positions/{positionPubkey}` | Sell position | | POST | `/prediction/v1/positions/{positionPubkey}/claim` | Claim winnings | | GET | `/prediction/v1/leaderboards` | View leaderboards | | GET | `/prediction/v1/profiles/{ownerPubkey}` | User profile stats | **Key Concepts** - **Contracts**: Trade in units, each worth $0-$1 based on probability - **YES/NO**: Binary outcomes - buy YES if you think event happens, NO otherwise - **Settlement**: Winning contracts pay $1, losing contracts pay $0 - **No claim fees**: Winners receive full $1 per contract --- ### Jupiter Lend Deposit tokens to earn yield or borrow against collateral. **Deposit (Earn)** ```bash # Get deposit transaction DEPOSIT=$(pnpm fetch-api -e /lend/v1/earn/deposit -m POST -b '{ "asset": [REDACTED_SECRET], "amount": "1000000", "signer": "YOUR_WALLET_ADDRESS" }') # Sign and send UNSIGNED_TX=$(echo "$DEPOSIT" | jq -r '.transaction') SIGNED_TX=$(pnpm wallet-sign -t "$UNSIGNED_TX" -w ~/.config/solana/id.json) pnpm send-transaction -t "$SIGNED_TX" -r "YOUR_RPC_URL" ``` **Withdraw** ```bash WITHDRAW=$(pnpm fetch-api -e /lend/v1/earn/withdraw -m POST -b '{ "asset": [REDACTED_SECRET], "amount": "1000000", "signer": "YOUR_WALLET_ADDRESS" }') # Sign and send as above ``` --- ### Portfolio API Track DeFi positions, platform info, and staked JUP across Solana. **Get Positions** Fetch all positions for a wallet address across Jupiter products. ```bash # Get all positions pnpm fetch-api -e /portfolio/v1/positions/YOUR_WALLET_ADDRESS # Filter by specific platforms pnpm fetch-api -e /portfolio/v1/positions/YOUR_WALLET_ADDRESS -p '{"platforms":"jupiter-exchange,jupiter-governance"}' ``` Response includes: - `elements`: Array of position types (Multiple, Liquidity, Leverage, BorrowLend, Trade) - `tokenInfo`: Token metadata indexed by network and address - `fetcherReports`: Status of each data fetcher **Get Platforms** List all available platforms tracked by the Portfolio API. ```bash pnpm fetch-api -e /portfolio/v1/platforms ``` Response includes platform details: - `id`: Platform identifier (e.g., `jupiter-exchange`) - `name`: Display name - `image`: Logo URL - `description`: Platform summary - `defiLlamaId`: DefiLlama reference - `isDeprecated`: Whether platform is deprecated - `tags`: Categorization tags - `links`: Social/web links (website, discord, twitter, github, docs) **Get Staked JUP** Check staked JUP amounts and pending unstaking for a wallet. ```bash pnpm fetch-api -e /portfolio/v1/staked-jup/YOUR_WALLET_ADDRESS ``` Response: ```json { "stakedAmount": 15000.5, "unstaking": [ { "amount": 500, "until": 1711000000000 } ] } ``` - `stakedAmount`: Total staked JUP - `unstaking`: Pending unstakes with amount and completion timestamp (ms) --- ### Trigger API (Limit Orders) Create orders that execute automatically when price conditions are met. **Create Limit Order** ```bash ORDER=$(pnpm fetch-api -e /trigger/v1/createOrder -m POST -b '{ "inputMint": [REDACTED_SECRET], "outputMint": [REDACTED_SECRET], "maker": "YOUR_WALLET_ADDRESS", "payer": "YOUR_WALLET_ADDRESS", "params": { "makingAmount": "1000000000", "takingAmount": "150000000", "expiredAt": null } }') # Sign and send UNSIGNED_TX=$(echo "$ORDER" | jq -r '.transaction') SIGNED_TX=$(pnpm wallet-sign -t "$UNSIGNED_TX" -w ~/.config/solana/id.json) pnpm send-transaction -t "$SIGNED_TX" -r "YOUR_RPC_URL" ``` Parameters: - `makingAmount`: Amount of input token to sell (in smallest units) - `takingAmount`: Minimum amount of output token to receive - `expiredAt`: Unix timestamp for expiration (null = no expiry) - `slippageBps`: Optional slippage tolerance (0 = exact price only) **Get Orders** ```bash # Get active orders pnpm fetch-api -e /trigger/v1/getTriggerOrders -p '{"user":"YOUR_WALLET","orderStatus":"active"}' # Get order history pnpm fetch-api -e /trigger/v1/getTriggerOrders -p '{"user":"YOUR_WALLET","orderStatus":"history","page":"1"}' ``` **Cancel Order** ```bash # Cancel single order CANCEL=$(pnpm fetch-api -e /trigger/v1/cancelOrder -m POST -b '{ "maker": "YOUR_WALLET_ADDRESS", "order": "ORDER_ACCOUNT_ADDRESS", "computeUnitPrice": "auto" }') # Sign and send transaction # Cancel all orders (batched in groups of 5) CANCEL_ALL=$(pnpm fetch-api -e /trigger/v1/cancelOrders -m POST -b '{ "maker": "YOUR_WALLET_ADDRESS", "computeUnitPrice": "auto" }') ``` **Trigger API Endpoints** | Method | Endpoint | Description | |--------|----------|-------------| | POST | `/trigger/v1/createOrder` | Create limit order | | GET | `/trigger/v1/getTriggerOrders` | Get orders by wallet | | POST | `/trigger/v1/cancelOrder` | Cancel single order | | POST | `/trigger/v1/cancelOrders` | Cancel multiple orders (batched) | **Fees**: 0.03% for stable pairs, 0.1% for other pairs. --- ### Recurring API (DCA) Automate recurring token purchases at specified intervals. **Create DCA Order** ```bash ORDER=$(pnpm fetch-api -e /recurring/v1/createOrder -m POST -b '{ "user": "YOUR_WALLET_ADDRESS", "inputMint": [REDACTED_SECRET], "outputMint": [REDACTED_SECRET], "params": { "time": { "inAmount": "10000000", "numberOfOrders": 10, "interval": 86400, "minPrice": null, "maxPrice": null, "startAt": null } } }') # Sign and send UNSIGNED_TX=$(echo "$ORDER" | jq -r '.transaction') SIGNED_TX=$(pnpm wallet-sign -t "$UNSIGNED_TX" -w ~/.config/solana/id.json) pnpm send-transaction -t "$SIGNED_TX" -r "YOUR_RPC_URL" ``` Parameters: - `inAmount`: Total amount to spend (raw units) - `numberOfOrders`: How many purchases to make - `interval`: Seconds between purchases (86400 = daily) - `minPrice`/`maxPrice`: Optional price bounds (null = any price) - `startAt`: Unix timestamp to start (null = immediate) **Get Orders** ```bash # Get active DCA orders pnpm fetch-api -e /recurring/v1/getRecurringOrders -p '{"user":"YOUR_WALLET","orderStatus":"active","recurringType":"time"}' # Get order history pnpm fetch-api -e /recurring/v1/getRecurringOrders -p '{"user":"YOUR_WALLET","orderStatus":"history","recurringType":"time","page":"1"}' ``` **Cancel Order** ```bash CANCEL=$(pnpm fetch-api -e /recurring/v1/cancelOrder -m POST -b '{ "user": "YOUR_WALLET_ADDRESS", "order": "ORDER_ACCOUNT_ADDRESS", "recurringType": "time" }') # Sign and send transaction ``` **Recurring API Endpoints** | Method | Endpoint | Description | |--------|----------|-------------| | POST | `/recurring/v1/createOrder` | Create DCA order | | GET | [REDACTED_SECRET] | Get orders by wallet | | POST | `/recurring/v1/cancelOrder` | Cancel order | **Fees**: 0.1% per execution. Token2022 tokens NOT supported. --- ## API Endpoints Reference | Use Case | API | Endpoint | |----------|-----|----------| | Token swaps (default) | Ultra | `/ultra/v1/order`, `/ultra/v1/execute` | | Swaps with control | Metis | `/swap/v1/quote`, `/swap/v1/swap` | | Limit orders | Trigger | `/trigger/v1/createOrder`, `/trigger/v1/cancelOrder` | | Get limit orders | Trigger | `/trigger/v1/getTriggerOrders` | | DCA orders | Recurring | `/recurring/v1/createOrder`, `/recurring/v1/cancelOrder` | | Get DCA orders | Recurring | [REDACTED_SECRET] | | Token search | Ultra | `/ultra/v1/search` | | Token holdings | Ultra | `/ultra/v1/holdings/{address}` | | Token warnings | Ultra | `/ultra/v1/shield` | | Token prices | Price | `/price/v3?ids={mints}` | | Token metadata | Tokens | `/tokens/v2/search?query={query}` | | Portfolio positions | Portfolio | `/portfolio/v1/positions/{address}` | | Portfolio platforms | Portfolio | `/portfolio/v1/platforms` | | Staked JUP | Portfolio | `/portfolio/v1/staked-jup/{address}` | | Prediction markets | Prediction | `/prediction/v1/events`, `/prediction/v1/orders` | | Lending deposit | Lend | `/lend/v1/earn/deposit` | | Lending withdraw | Lend | `/lend/v1/earn/withdraw` | --- ## Caveats & Limitations ### API Key & Rate Limits | Tier | Rate Limit | |------|------------| | Free | 60 requests/minute | | Pro | Up to 30,000 requests/minute | | Ultra | Dynamic scaling with executed swap volume | Ultra rate limits increase as you execute more swaps. Base: 50 requests per 10-second window. ### Fees | API | Fee | |-----|-----| | Ultra | 5-10 basis points per swap | | Metis | No Jupiter fee (you pay gas) | Integrators can add custom fees (50-255 bps). Jupiter takes 20% of integrator fees. ### Gasless Requirements Ultra offers "gasless" swaps where Jupiter pays the transaction fees, but with important caveats: - **User still needs SOL** for account rent (creating token accounts) - **User must sign** the transaction (not truly "zero-touch") - **Minimum trade amount**: ~$10 equivalent - Automatic when taker has <0.01 SOL - JupiterZ RFQ: market makers pay transaction fees ### Transaction Size Limit Solana transactions are limited to **1232 bytes**. If you hit this: - Reduce `maxAccounts` parameter in quote request - Use `dynamicComputeUnitLimit: true` for Metis ### Token Limitations - **Token2022**: NOT supported for Recurring (DCA) orders - Some tokens may have transfer fees or freeze authority ### Ultra vs Metis | Feature | Ultra | Metis | |---------|-------|-------| | RPC required | No (Jupiter handles) | Yes (your RPC) | | Gasless | Yes (conditions apply) | No | | Custom instructions | No | Yes | | Transaction composition | No | Yes | | Slippage | Auto-optimized | Manual | **Use Ultra** for most swaps. **Use Metis** only when you need to add custom instructions or compose transactions. ### Common Errors | Error | Cause | Fix | |-------|-------|-----| | [REDACTED_SECRET] | Blockhash expired | Request fresh order/quote | | `SlippageToleranceExceeded` | Price moved too much | Increase slippage or retry | | `InsufficientFunds` | Not enough SOL/tokens | Check balances | | `RateLimited (429)` | Too many requests | Wait and retry, or upgrade tier | | `InvalidSignature` | Wrong signer or corrupted tx | Verify wallet matches taker address | ### Ultra Execute Error Codes | Code | Meaning | |------|---------| | 0 | Success | | -1 to -5 | Client/validation errors | | -1000 to -1999 | Aggregator routing errors | | -2000 to -2999 | RFQ (market maker) errors | --- ## Environment Variables | Variable | Description | Default | |----------|-------------|---------| | `JUP_API_KEY` | Jupiter API key | (required) | | `SOLANA_RPC_URL` | RPC endpoint for send-transaction | `https://api.mainnet-beta.solana.com` | --- ## Common Token Mints | Token | Mint Address | |-------|--------------| | SOL (wrapped) | [REDACTED_SECRET] | | USDC | [REDACTED_SECRET] | | USDT | [REDACTED_SECRET] | | JUP | [REDACTED_SECRET] | --- ## Resources **Freshness note:** This skill includes Jupiter API guidance, but the API can change. After each new run/session, verify endpoints and params against the latest docs at [dev.jup.ag](https://dev.jup.ag). ### Docs URLs for Claude Sync Check these first on each run/session: - `https://dev.jup.ag/llms.txt` - `https://dev.jup.ag/llms-full.txt` Then verify workflow-specific pages: - `https://dev.jup.ag/get-started/index.md` - `https://dev.jup.ag/portal/setup.md` - `https://dev.jup.ag/portal/rate-limit.md` - `https://dev.jup.ag/portal/responses.md` - `https://dev.jup.ag/docs/ultra/index.md` - `https://dev.jup.ag/docs/ultra/get-started.md` - `https://dev.jup.ag/docs/ultra/get-order.md` - `https://dev.jup.ag/docs/ultra/execute-order.md` - `https://dev.jup.ag/docs/ultra/response.md` - `https://dev.jup.ag/docs/ultra/rate-limit.md` - `https://dev.jup.ag/docs/ultra/search-token.md` - `https://dev.jup.ag/docs/swap/index.md` - `https://dev.jup.ag/docs/swap/get-quote.md` - `https://dev.jup.ag/docs/swap/build-swap-transaction.md` - `https://dev.jup.ag/docs/swap/send-swap-transaction.md` - `https://dev.jup.ag/docs/swap/common-errors.md` - `https://dev.jup.ag/updates/index.md` Any Jupiter docs page can also be fetched as markdown by appending `.md` to the path. - [Jupiter Portal](https://portal.jup.ag) - API key management - [Jupiter Docs](https://dev.jup.ag) - Full documentation - [Status Page](https://status.jup.ag) - API status - [Solscan](https://solscan.io) - Transaction explorer

path: tsconfig.json
content: {
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "rootDir": ".",
    "declaration": true,
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true
  },
  "include": ["scripts/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}


path: package.json
content: {
  "name": "jup-skill",
  "version": "1.0.0",
  "description": "Jupiter API skill for Claude - fetch, sign, and execute Solana transactions",
  "type": "module",
  "main": "dist/index.js",
  "scripts": {
    "build": "tsc",
    "fetch-api": "tsx scripts/fetch-api.ts",
    "execute-ultra": "tsx scripts/execute-ultra.ts",
    "wallet-sign": "tsx scripts/wallet-sign.ts",
    "send-transaction": "tsx scripts/send-transaction.ts"
  },
  "keywords": [
    "jupiter",
    "solana",
    "swap",
    "defi",
    "claude",
    "skill"
  ],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@solana/web3.js": "^1.95.0",
    "bs58": "^6.0.0",
    "commander": "^12.1.0"
  },
  "devDependencies": {
    "@types/node": "^22.0.0",
    "tsx": "^4.19.0",
    "typescript": "^5.6.0"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}


path: pnpm-lock.yaml
content: lockfileVersion: '9.0'

settings:
  autoInstallPeers: true
  excludeLinksFromLockfile: false

importers:

  .:
    dependencies:
      '@solana/web3.js':
        specifier: ^1.95.0
        version: 1.98.4(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)
      bs58:
        specifier: ^6.0.0
        version: 6.0.0
      commander:
        specifier: ^12.1.0
        version: 12.1.0
    devDependencies:
      '@types/node':
        specifier: ^22.0.0
        version: 22.19.10
      tsx:
        specifier: ^4.19.0
        version: 4.21.0
      typescript:
        specifier: ^5.6.0
        version: 5.9.3

packages:

  '@babel/runtime@7.28.6':
    resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
    engines: {node: '>=6.9.0'}

  '@esbuild/aix-ppc64@0.27.3':
    resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
    engines: {node: '>=18'}
    cpu: [ppc64]
    os: [aix]

  '@esbuild/android-arm64@0.27.3':
    resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [android]

  '@esbuild/android-arm@0.27.3':
    resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
    engines: {node: '>=18'}
    cpu: [arm]
    os: [android]

  '@esbuild/android-x64@0.27.3':
    resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [android]

  '@esbuild/darwin-arm64@0.27.3':
    resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [darwin]

  '@esbuild/darwin-x64@0.27.3':
    resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [darwin]

  '@esbuild/freebsd-arm64@0.27.3':
    resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [freebsd]

  '@esbuild/freebsd-x64@0.27.3':
    resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [freebsd]

  '@esbuild/linux-arm64@0.27.3':
    resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [linux]

  '@esbuild/linux-arm@0.27.3':
    resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
    engines: {node: '>=18'}
    cpu: [arm]
    os: [linux]

  '@esbuild/linux-ia32@0.27.3':
    resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
    engines: {node: '>=18'}
    cpu: [ia32]
    os: [linux]

  '@esbuild/linux-loong64@0.27.3':
    resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
    engines: {node: '>=18'}
    cpu: [loong64]
    os: [linux]

  '@esbuild/linux-mips64el@0.27.3':
    resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
    engines: {node: '>=18'}
    cpu: [mips64el]
    os: [linux]

  '@esbuild/linux-ppc64@0.27.3':
    resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
    engines: {node: '>=18'}
    cpu: [ppc64]
    os: [linux]

  '@esbuild/linux-riscv64@0.27.3':
    resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
    engines: {node: '>=18'}
    cpu: [riscv64]
    os: [linux]

  '@esbuild/linux-s390x@0.27.3':
    resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
    engines: {node: '>=18'}
    cpu: [s390x]
    os: [linux]

  '@esbuild/linux-x64@0.27.3':
    resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [linux]

  '@esbuild/netbsd-arm64@0.27.3':
    resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [netbsd]

  '@esbuild/netbsd-x64@0.27.3':
    resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [netbsd]

  '@esbuild/openbsd-arm64@0.27.3':
    resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [openbsd]

  '@esbuild/openbsd-x64@0.27.3':
    resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [openbsd]

  '@esbuild/openharmony-arm64@0.27.3':
    resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [openharmony]

  '@esbuild/sunos-x64@0.27.3':
    resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [sunos]

  '@esbuild/win32-arm64@0.27.3':
    resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [win32]

  '@esbuild/win32-ia32@0.27.3':
    resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==}
    engines: {node: '>=18'}
    cpu: [ia32]
    os: [win32]

  '@esbuild/win32-x64@0.27.3':
    resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [win32]

  '@noble/curves@1.9.7':
    resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==}
    engines: {node: ^14.21.3 || >=16}

  '@noble/hashes@1.8.0':
    resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
    engines: {node: ^14.21.3 || >=16}

  '@solana/buffer-layout@4.0.1':
    resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==}
    engines: {node: '>=5.10'}

  '@solana/codecs-core@2.3.0':
    resolution: {integrity: sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==}
    engines: {node: '>=20.18.0'}
    peerDependencies:
      typescript: '>=5.3.3'

  '@solana/codecs-numbers@2.3.0':
    resolution: {integrity: sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==}
    engines: {node: '>=20.18.0'}
    peerDependencies:
      typescript: '>=5.3.3'

  '@solana/errors@2.3.0':
    resolution: {integrity: sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==}
    engines: {node: '>=20.18.0'}
    hasBin: true
    peerDependencies:
      typescript: '>=5.3.3'

  '@solana/web3.js@1.98.4':
    resolution: {integrity: sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==}

  '@swc/helpers@0.5.18':
    resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==}

  '@types/connect@3.4.38':
    resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}

  '@types/node@12.20.55':
    resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==}

  '@types/node@22.19.10':
    resolution: {integrity: sha512-tF5VOugLS/EuDlTBijk0MqABfP8UxgYazTLo3uIn3b4yJgg26QRbVYJYsDtHrjdDUIRfP70+VfhTTc+CE1yskw==}

  '@types/uuid@8.3.4':
    resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==}

  '@types/ws@7.4.7':
    resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==}

  '@types/ws@8.18.1':
    resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}

  agentkeepalive@4.6.0:
    resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
    engines: {node: '>= 8.0.0'}

  base-x@3.0.11:
    resolution: {integrity: sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==}

  base-x@5.0.1:
    resolution: {integrity: sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==}

  base64-js@1.5.1:
    resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}

  bn.js@5.2.2:
    resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==}

  borsh@0.7.0:
    resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==}

  bs58@4.0.1:
    resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==}

  bs58@6.0.0:
    resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==}

  buffer@6.0.3:
    resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}

  bufferutil@4.1.0:
    resolution: {integrity: sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==}
    engines: {node: '>=6.14.2'}

  chalk@5.6.2:
    resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
    engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}

  commander@12.1.0:
    resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
    engines: {node: '>=18'}

  commander@14.0.3:
    resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
    engines: {node: '>=20'}

  commander@2.20.3:
    resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}

  delay@5.0.0:
    resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==}
    engines: {node: '>=10'}

  es6-promise@4.2.8:
    resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==}

  es6-promisify@5.0.0:
    resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==}

  esbuild@0.27.3:
    resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
    engines: {node: '>=18'}
    hasBin: true

  eventemitter3@5.0.4:
    resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}

  eyes@0.1.8:
    resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==}
    engines: {node: '> 0.1.90'}

  fast-stable-stringify@1.0.0:
    resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==}

  fsevents@2.3.3:
    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
    os: [darwin]

  get-tsconfig@4.13.6:
    resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==}

  humanize-ms@1.2.1:
    resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}

  ieee754@1.2.1:
    resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}

  isomorphic-ws@4.0.1:
    resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==}
    peerDependencies:
      ws: '*'

  jayson@4.3.0:
    resolution: {integrity: sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==}
    engines: {node: '>=8'}
    hasBin: true

  json-stringify-safe@5.0.1:
    resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}

  ms@2.1.3:
    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}

  node-fetch@2.7.0:
    resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
    engines: {node: 4.x || >=6.0.0}
    peerDependencies:
      encoding: ^0.1.0
    peerDependenciesMeta:
      encoding:
        optional: true

  node-gyp-build@4.8.4:
    resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
    hasBin: true

  resolve-pkg-maps@1.0.0:
    resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}

  rpc-websockets@9.3.3:
    resolution: {integrity: sha512-OkCsBBzrwxX4DoSv4Zlf9DgXKRB0MzVfCFg5MC+fNnf9ktr4SMWjsri0VNZQlDbCnGcImT6KNEv4ZoxktQhdpA==}

  safe-buffer@5.2.1:
    resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}

  stream-chain@2.2.5:
    resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==}

  stream-json@1.9.1:
    resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==}

  superstruct@2.0.2:
    resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==}
    engines: {node: '>=14.0.0'}

  text-encoding-utf-8@1.0.2:
    resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==}

  tr46@0.0.3:
    resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}

  tslib@2.8.1:
    resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}

  tsx@4.21.0:
    resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
    engines: {node: '>=18.0.0'}
    hasBin: true

  typescript@5.9.3:
    resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
    engines: {node: '>=14.17'}
    hasBin: true

  undici-types@6.21.0:
    resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}

  utf-8-validate@5.0.10:
    resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==}
    engines: {node: '>=6.14.2'}

  uuid@8.3.2:
    resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
    hasBin: true

  webidl-conversions@3.0.1:
    resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}

  whatwg-url@5.0.0:
    resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}

  ws@7.5.10:
    resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
    engines: {node: '>=8.3.0'}
    peerDependencies:
      bufferutil: ^4.0.1
      utf-8-validate: ^5.0.2
    peerDependenciesMeta:
      bufferutil:
        optional: true
      utf-8-validate:
        optional: true

  ws@8.19.0:
    resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}
    engines: {node: '>=10.0.0'}
    peerDependencies:
      bufferutil: ^4.0.1
      utf-8-validate: '>=5.0.2'
    peerDependenciesMeta:
      bufferutil:
        optional: true
      utf-8-validate:
        optional: true

snapshots:

  '@babel/runtime@7.28.6': {}

  '@esbuild/aix-ppc64@0.27.3':
    optional: true

  '@esbuild/android-arm64@0.27.3':
    optional: true

  '@esbuild/android-arm@0.27.3':
    optional: true

  '@esbuild/android-x64@0.27.3':
    optional: true

  '@esbuild/darwin-arm64@0.27.3':
    optional: true

  '@esbuild/darwin-x64@0.27.3':
    optional: true

  '@esbuild/freebsd-arm64@0.27.3':
    optional: true

  '@esbuild/freebsd-x64@0.27.3':
    optional: true

  '@esbuild/linux-arm64@0.27.3':
    optional: true

  '@esbuild/linux-arm@0.27.3':
    optional: true

  '@esbuild/linux-ia32@0.27.3':
    optional: true

  '@esbuild/linux-loong64@0.27.3':
    optional: true

  '@esbuild/linux-mips64el@0.27.3':
    optional: true

  '@esbuild/linux-ppc64@0.27.3':
    optional: true

  '@esbuild/linux-riscv64@0.27.3':
    optional: true

  '@esbuild/linux-s390x@0.27.3':
    optional: true

  '@esbuild/linux-x64@0.27.3':
    optional: true

  '@esbuild/netbsd-arm64@0.27.3':
    optional: true

  '@esbuild/netbsd-x64@0.27.3':
    optional: true

  '@esbuild/openbsd-arm64@0.27.3':
    optional: true

  '@esbuild/openbsd-x64@0.27.3':
    optional: true

  '@esbuild/openharmony-arm64@0.27.3':
    optional: true

  '@esbuild/sunos-x64@0.27.3':
    optional: true

  '@esbuild/win32-arm64@0.27.3':
    optional: true

  '@esbuild/win32-ia32@0.27.3':
    optional: true

  '@esbuild/win32-x64@0.27.3':
    optional: true

  '@noble/curves@1.9.7':
    dependencies:
      '@noble/hashes': 1.8.0

  '@noble/hashes@1.8.0': {}

  '@solana/buffer-layout@4.0.1':
    dependencies:
      buffer: 6.0.3

  '@solana/codecs-core@2.3.0(typescript@5.9.3)':
    dependencies:
      '@solana/errors': 2.3.0(typescript@5.9.3)
      typescript: 5.9.3

  '@solana/codecs-numbers@2.3.0(typescript@5.9.3)':
    dependencies:
      '@solana/codecs-core': 2.3.0(typescript@5.9.3)
      '@solana/errors': 2.3.0(typescript@5.9.3)
      typescript: 5.9.3

  '@solana/errors@2.3.0(typescript@5.9.3)':
    dependencies:
      chalk: 5.6.2
      commander: 14.0.3
      typescript: 5.9.3

  '@solana/web3.js@1.98.4(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)':
    dependencies:
      '@babel/runtime': 7.28.6
      '@noble/curves': 1.9.7
      '@noble/hashes': 1.8.0
      '@solana/buffer-layout': 4.0.1
      '@solana/codecs-numbers': 2.3.0(typescript@5.9.3)
      agentkeepalive: 4.6.0
      bn.js: 5.2.2
      borsh: 0.7.0
      bs58: 4.0.1
      buffer: 6.0.3
      fast-stable-stringify: 1.0.0
      jayson: 4.3.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)
      node-fetch: 2.7.0
      rpc-websockets: 9.3.3
      superstruct: 2.0.2
    transitivePeerDependencies:
      - bufferutil
      - encoding
      - typescript
      - utf-8-validate

  '@swc/helpers@0.5.18':
    dependencies:
      tslib: 2.8.1

  '@types/connect@3.4.38':
    dependencies:
      '@types/node': 22.19.10

  '@types/node@12.20.55': {}

  '@types/node@22.19.10':
    dependencies:
      undici-types: 6.21.0

  '@types/uuid@8.3.4': {}

  '@types/ws@7.4.7':
    dependencies:
      '@types/node': 22.19.10

  '@types/ws@8.18.1':
    dependencies:
      '@types/node': 22.19.10

  agentkeepalive@4.6.0:
    dependencies:
      humanize-ms: 1.2.1

  base-x@3.0.11:
    dependencies:
      safe-buffer: 5.2.1

  base-x@5.0.1: {}

  base64-js@1.5.1: {}

  bn.js@5.2.2: {}

  borsh@0.7.0:
    dependencies:
      bn.js: 5.2.2
      bs58: 4.0.1
      text-encoding-utf-8: 1.0.2

  bs58@4.0.1:
    dependencies:
      base-x: 3.0.11

  bs58@6.0.0:
    dependencies:
      base-x: 5.0.1

  buffer@6.0.3:
    dependencies:
      base64-js: 1.5.1
      ieee754: 1.2.1

  bufferutil@4.1.0:
    dependencies:
      node-gyp-build: 4.8.4
    optional: true

  chalk@5.6.2: {}

  commander@12.1.0: {}

  commander@14.0.3: {}

  commander@2.20.3: {}

  delay@5.0.0: {}

  es6-promise@4.2.8: {}

  es6-promisify@5.0.0:
    dependencies:
      es6-promise: 4.2.8

  esbuild@0.27.3:
    optionalDependencies:
      '@esbuild/aix-ppc64': 0.27.3
      '@esbuild/android-arm': 0.27.3
      '@esbuild/android-arm64': 0.27.3
      '@esbuild/android-x64': 0.27.3
      '@esbuild/darwin-arm64': 0.27.3
      '@esbuild/darwin-x64': 0.27.3
      '@esbuild/freebsd-arm64': 0.27.3
      '@esbuild/freebsd-x64': 0.27.3
      '@esbuild/linux-arm': 0.27.3
      '@esbuild/linux-arm64': 0.27.3
      '@esbuild/linux-ia32': 0.27.3
      '@esbuild/linux-loong64': 0.27.3
      '@esbuild/linux-mips64el': 0.27.3
      '@esbuild/linux-ppc64': 0.27.3
      '@esbuild/linux-riscv64': 0.27.3
      '@esbuild/linux-s390x': 0.27.3
      '@esbuild/linux-x64': 0.27.3
      '@esbuild/netbsd-arm64': 0.27.3
      '@esbuild/netbsd-x64': 0.27.3
      '@esbuild/openbsd-arm64': 0.27.3
      '@esbuild/openbsd-x64': 0.27.3
      '@esbuild/openharmony-arm64': 0.27.3
      '@esbuild/sunos-x64': 0.27.3
      '@esbuild/win32-arm64': 0.27.3
      '@esbuild/win32-ia32': 0.27.3
      '@esbuild/win32-x64': 0.27.3

  eventemitter3@5.0.4: {}

  eyes@0.1.8: {}

  fast-stable-stringify@1.0.0: {}

  fsevents@2.3.3:
    optional: true

  get-tsconfig@4.13.6:
    dependencies:
      resolve-pkg-maps: 1.0.0

  humanize-ms@1.2.1:
    dependencies:
      ms: 2.1.3

  ieee754@1.2.1: {}

  isomorphic-ws@4.0.1(ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@5.0.10)):
    dependencies:
      ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@5.0.10)

  jayson@4.3.0(bufferutil@4.1.0)(utf-8-validate@5.0.10):
    dependencies:
      '@types/connect': 3.4.38
      '@types/node': 12.20.55
      '@types/ws': 7.4.7
      commander: 2.20.3
      delay: 5.0.0
      es6-promisify: 5.0.0
      eyes: 0.1.8
      isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@5.0.10))
      json-stringify-safe: 5.0.1
      stream-json: 1.9.1
      uuid: 8.3.2
      ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@5.0.10)
    transitivePeerDependencies:
      - bufferutil
      - utf-8-validate

  json-stringify-safe@5.0.1: {}

  ms@2.1.3: {}

  node-fetch@2.7.0:
    dependencies:
      whatwg-url: 5.0.0

  node-gyp-build@4.8.4:
    optional: true

  resolve-pkg-maps@1.0.0: {}

  rpc-websockets@9.3.3:
    dependencies:
      '@swc/helpers': 0.5.18
      '@types/uuid': 8.3.4
      '@types/ws': 8.18.1
      buffer: 6.0.3
      eventemitter3: 5.0.4
      uuid: 8.3.2
      ws: 8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)
    optionalDependencies:
      bufferutil: 4.1.0
      utf-8-validate: 5.0.10

  safe-buffer@5.2.1: {}

  stream-chain@2.2.5: {}

  stream-json@1.9.1:
    dependencies:
      stream-chain: 2.2.5

  superstruct@2.0.2: {}

  text-encoding-utf-8@1.0.2: {}

  tr46@0.0.3: {}

  tslib@2.8.1: {}

  tsx@4.21.0:
    dependencies:
      esbuild: 0.27.3
      get-tsconfig: 4.13.6
    optionalDependencies:
      fsevents: 2.3.3

  typescript@5.9.3: {}

  undici-types@6.21.0: {}

  utf-8-validate@5.0.10:
    dependencies:
      node-gyp-build: 4.8.4
    optional: true

  uuid@8.3.2: {}

  webidl-conversions@3.0.1: {}

  whatwg-url@5.0.0:
    dependencies:
      tr46: 0.0.3
      webidl-conversions: 3.0.1

  ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@5.0.10):
    optionalDependencies:
      bufferutil: 4.1.0
      utf-8-validate: 5.0.10

  ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10):
    optionalDependencies:
      bufferutil: 4.1.0
      utf-8-validate: 5.0.10


path: README.md
content: A Claude Code skill for executing Jupiter API operations on Solana. Includes scripts for fetching quotes, signing transactions, and executing swaps.

## Prerequisites

- Node.js 18+
- pnpm (recommended) or npm
- Jupiter API key from [portal.jup.ag](https://portal.jup.ag)
- Solana wallet (for signing transactions)

## Installation

```bash
# Clone the repo
cd /path/to/jup-skill

# Install dependencies
pnpm install
```

## Setup

### 1. Get API Key

1. Visit [portal.jup.ag](https://portal.jup.ag)
2. Create an account
3. Generate an API key

### 2. Configure Environment

```bash
# Set Jupiter API key
export JUP_API_KEY=your_api_key_here

# Optional: Set custom RPC (for Metis swaps)
export SOLANA_RPC_URL=https://your-rpc.com
```

`JUP_API_KEY` is required for all Jupiter API calls. `SOLANA_RPC_URL` is optional and only used by `send-transaction`.

### 3. Wallet Setup

You'll need a Solana wallet file to sign transactions.

> **SECURITY NOTE**: This tool only accepts wallet files, not private keys via command line arguments. This prevents exposure in shell history and process listings.
>
> Wallet files contain private key material. Do not use a high-value wallet with this workflow. Prefer a dedicated low-balance wallet, use ephemeral keys for testing, and use hardware signing where possible.

```bash
# Generate new wallet
solana-keygen new -o ~/.config/solana/id.json

# Or use existing wallet - the --wallet flag is required
pnpm wallet-sign -t "TX" --wallet ~/.config/solana/id.json

# Tilde expansion is supported
pnpm wallet-sign -t "TX" --wallet ~/my-wallets/trading.json
```

## Usage

Before running any script command below, install dependencies:

```bash
pnpm install
```

### Quick Examples

```bash
# Search for tokens
pnpm fetch-api -e /ultra/v1/search -p '{"query":"SOL"}'

# Get swap order
pnpm fetch-api -e /ultra/v1/order -p '{
  "inputMint": [REDACTED_SECRET],
  "outputMint": [REDACTED_SECRET],
  "amount": "1000000",
  "taker": "YOUR_WALLET_ADDRESS"
}'

# Sign a transaction (--wallet flag is required)
pnpm wallet-sign -t "BASE64_UNSIGNED_TX" --wallet ~/.config/solana/id.json

# Execute Ultra order
pnpm execute-ultra -r "REQUEST_ID" -t "BASE64_SIGNED_TX"

# Send transaction to RPC (for Metis)
# Note: Default public RPC is rate-limited. Use a dedicated RPC for production.
pnpm send-transaction -t "BASE64_SIGNED_TX"
```

### Complete Ultra Swap Flow

```bash
# 1. Get order
ORDER=$(pnpm fetch-api -e /ultra/v1/order -p '{
  "inputMint": [REDACTED_SECRET],
  "outputMint": [REDACTED_SECRET],
  "amount": "1000000",
  "taker": "YOUR_WALLET_ADDRESS"
}')

# 2. Extract and sign (--wallet flag is required)
UNSIGNED_TX=$(echo "$ORDER" | jq -r '.transaction')
SIGNED_TX=$(pnpm wallet-sign -t "$UNSIGNED_TX" --wallet ~/.config/solana/id.json)

# 3. Execute
REQUEST_ID=$(echo "$ORDER" | jq -r '.requestId')
pnpm execute-ultra -r "$REQUEST_ID" -t "$SIGNED_TX"
```

## Scripts

| Script | Purpose |
|--------|---------|
| `fetch-api.ts` | Fetch from any Jupiter API endpoint |
| `wallet-sign.ts` | Sign transactions with local wallet file |
| `execute-ultra.ts` | Execute Ultra orders |
| `send-transaction.ts` | Send signed transactions to RPC |

## Claude Code Skill

This repo includes a `SKILL.md` file that can be used as a Claude Code skill. The skill provides:

- Quick reference for all scripts
- Complete workflows for Ultra and Metis swaps
- API endpoint reference
- Caveats and error handling guidance

### Installing as a Skill

Add to your Claude Code skills directory:

```bash
# Link or copy to skills directory
ln -s /path/to/jup-skill ~/.claude/skills/jup-skill
```

## Troubleshooting

### "API key required" error

Set the `JUP_API_KEY` environment variable or pass `--api-key` flag.

### "Rate limited (429)" error

- Free tier: 60 requests/minute
- Consider upgrading at [portal.jup.ag](https://portal.jup.ag)
- Ultra rate limits scale with swap volume

### "Transaction expired" error

Blockhash expired. Request a fresh order/quote and try again.

### "Insufficient funds" error

Check that your wallet has enough SOL for fees and the input token. Note: Even "gasless" Ultra swaps require SOL for account rent (creating token accounts).

## Resources

Freshness note: this repo documents the Jupiter API flow, but the API can change. After each new run/session, check the latest docs at [dev.jup.ag](https://dev.jup.ag) before executing.

### Docs URLs for Claude Sync

Check these first on each run/session:

- `https://dev.jup.ag/llms.txt`
- `https://dev.jup.ag/llms-full.txt`

Then verify workflow-specific pages:

- `https://dev.jup.ag/get-started/index.md`
- `https://dev.jup.ag/portal/setup.md`
- `https://dev.jup.ag/portal/rate-limit.md`
- `https://dev.jup.ag/portal/responses.md`
- `https://dev.jup.ag/docs/ultra/index.md`
- `https://dev.jup.ag/docs/ultra/get-started.md`
- `https://dev.jup.ag/docs/ultra/get-order.md`
- `https://dev.jup.ag/docs/ultra/execute-order.md`
- `https://dev.jup.ag/docs/ultra/response.md`
- `https://dev.jup.ag/docs/ultra/rate-limit.md`
- `https://dev.jup.ag/docs/ultra/search-token.md`
- `https://dev.jup.ag/docs/swap/index.md`
- `https://dev.jup.ag/docs/swap/get-quote.md`
- `https://dev.jup.ag/docs/swap/build-swap-transaction.md`
- `https://dev.jup.ag/docs/swap/send-swap-transaction.md`
- `https://dev.jup.ag/docs/swap/common-errors.md`
- `https://dev.jup.ag/updates/index.md`

Any Jupiter docs page can also be fetched as markdown by appending `.md` to the path.

- [Jupiter Portal](https://portal.jup.ag) - API key management
- [Jupiter Docs](https://dev.jup.ag) - Full documentation
- [Status Page](https://status.jup.ag) - API status
- [Solscan](https://solscan.io) - Transaction explorer

## License

MIT


path: scripts/execute-ultra.ts
content: #!/usr/bin/env tsx
/**
 * execute-ultra.ts - Execute Ultra orders on Jupiter
 *
 * Usage:
 *   pnpm execute-ultra --request-id "UUID" --signed-tx "BASE64_SIGNED_TX"
 *
 * This script:
 *   1. Takes the requestId from /ultra/v1/order response
 *   2. Takes the signed transaction (sign with wallet-sign.ts first)
 *   3. POSTs to /ultra/v1/execute
 *   4. Returns execution result
 */

import { Command } from "commander";
import { getApiKey, printApiKeyError, isValidBase64, isValidUUID } from "./utils.js";

const EXECUTE_URL = "https://api.jup.ag/ultra/v1/execute";
const REQUEST_TIMEOUT_MS = 30000;

interface ExecuteOptions {
  requestId: string;
  signedTx: string;
  apiKey?: string;
}

interface ExecuteResponse {
  status: "Success" | "Failed";
  signature?: string;
  error?: string;
  code?: string;
  slot?: number;
  inputAmountResult?: string;
  outputAmountResult?: string;
}

async function executeOrder(options: ExecuteOptions): Promise<void> {
  const [REDACTED_SECRET]);

  if (!apiKey) {
    printApiKeyError();
    process.exit(1);
  }

  // Validate inputs
  if (!options.requestId) {
    console.error("Error: --request-id is required");
    console.error("Get the requestId from the /ultra/v1/order response");
    process.exit(1);
  }

  if (!isValidUUID(options.requestId)) {
    console.error("Error: Invalid request ID format. Expected UUID format.");
    process.exit(1);
  }

  if (!options.signedTx) {
    console.error("Error: --signed-tx is required");
    console.error("Sign the transaction first using: pnpm wallet-sign --unsigned-tx ...");
    process.exit(1);
  }

  // Validate base64 format
  if (!isValidBase64(options.signedTx)) {
    console.error("Error: Invalid base64 format for signed transaction");
    process.exit(1);
  }

  const body = {
    requestId: options.requestId,
    signedTransaction: options.signedTx,
  };

  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);

  try {
    const response = await fetch(EXECUTE_URL, {
      method: "POST",
      headers: {
        "x-api-key": apiKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
      signal: controller.signal,
    });

    // Check for rate limiting
    if (response.status === 429) {
      const retryAfter = response.headers.get("Retry-After");
      console.error("Error: Rate limited by Jupiter API");
      if (retryAfter) {
        console.error(`Retry after: ${retryAfter} seconds`);
      }
      console.error("\nUltra rate limits scale with your executed swap volume.");
      console.error("See: https://dev.jup.ag/docs/ultra/rate-limit.md");
      process.exit(1);
    }

    let data: ExecuteResponse;
    try {
      data = (await response.json()) as ExecuteResponse;
    } catch (e) {
      console.error("Error: API returned non-JSON response");
      console.error(`Status: ${response.status}`);
      const text = await response.text().catch(() => "Unable to read response");
      console.error(`Body: ${text.slice(0, 500)}`);
      process.exit(1);
    }

    if (!response.ok) {
      console.error(`Error: API returned status ${response.status}`);
      console.error(JSON.stringify(data, null, 2));
      process.exit(1);
    }

    // Output result
    console.log(JSON.stringify(data, null, 2));

    // Additional info to stderr
    if (data.status === "Success" && data.signature) {
      console.error(`\nTransaction successful!`);
      console.error(`Signature: ${data.signature}`);
      console.error(`Explorer: https://solscan.io/tx/${data.signature}`);
    } else if (data.status === "Failed") {
      console.error(`\nTransaction failed!`);
      if (data.error) {
        console.error(`Error: ${data.error}`);
      }
      if (data.code) {
        console.error(`Code: ${data.code}`);
      }
      process.exit(1);
    }
  } catch (error) {
    if (error instanceof Error) {
      if (error.name === "AbortError") {
        console.error(`Error: Request timed out after ${REQUEST_TIMEOUT_MS / 1000} seconds`);
      } else {
        console.error(`Network error: ${error.message}`);
      }
    } else {
      console.error("Unknown error occurred");
    }
    process.exit(1);
  } finally {
    clearTimeout(timeoutId);
  }
}

// CLI setup
const program = new Command();

program
  .name("execute-ultra")
  .description("Execute Ultra orders on Jupiter")
  .requiredOption(
    "-r, --request-id <id>",
    "Request ID from /ultra/v1/order response"
  )
  .requiredOption(
    "-t, --signed-tx <base64>",
    "Base64-encoded signed transaction"
  )
  .option("-k, --api-key <key>", "Jupiter API key (or use JUP_API_KEY env var)")
  .action(executeOrder);

program.parse();


path: scripts/fetch-api.ts
content: #!/usr/bin/env tsx
/**
 * fetch-api.ts - Fetch data from any Jupiter API endpoint
 *
 * Usage:
 *   pnpm fetch-api --endpoint /ultra/v1/search --params '{"query":"SOL"}'
 *   pnpm fetch-api --endpoint /ultra/v1/order --params '{"inputMint":"So11...","outputMint":"EPjF...","amount":"1000000","taker":"YOUR_WALLET"}'
 *   pnpm fetch-api --endpoint /swap/v1/swap --method POST --body '{"quoteResponse":{...},"userPublicKey":"..."}'
 */

import { Command } from "commander";
import { getApiKey, printApiKeyError } from "./utils.js";

const BASE_URL = "https://api.jup.ag";
const REQUEST_TIMEOUT_MS = 30000;

interface FetchOptions {
  endpoint: string;
  params?: string;
  body?: string;
  method: string;
  apiKey?: string;
}

async function fetchApi(options: FetchOptions): Promise<void> {
  const [REDACTED_SECRET]);

  if (!apiKey) {
    printApiKeyError();
    process.exit(1);
  }

  // Build URL with query params for GET requests
  let url = `${BASE_URL}${options.endpoint}`;

  const headers: Record<string, string> = {
    "x-api-key": apiKey,
    "Content-Type": "application/json",
  };

  const fetchOptions: RequestInit = {
    method: options.method.toUpperCase(),
    headers,
  };

  // Handle params for GET or body for POST
  if (options.method.toUpperCase() === "GET" && options.params) {
    try {
      const params = JSON.parse(options.params);
      const searchParams = new URLSearchParams();
      for (const [key, value] of Object.entries(params)) {
        searchParams.append(key, String(value));
      }
      url += `?${searchParams.toString()}`;
    } catch (e) {
      console.error("Error: Invalid JSON in --params");
      console.error("Expected format: '{\"key\":\"value\"}'");
      process.exit(1);
    }
  } else if (options.method.toUpperCase() === "POST") {
    if (options.body) {
      try {
        // Validate JSON
        JSON.parse(options.body);
        fetchOptions.body = options.body;
      } catch (e) {
        console.error("Error: Invalid JSON in --body");
        process.exit(1);
      }
    } else if (options.params) {
      // Allow --params for POST body as well
      try {
        JSON.parse(options.params);
        fetchOptions.body = options.params;
      } catch (e) {
        console.error("Error: Invalid JSON in --params");
        process.exit(1);
      }
    }
  }

  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);

  try {
    const response = await fetch(url, { ...fetchOptions, signal: controller.signal });

    // Check for rate limiting
    if (response.status === 429) {
      const retryAfter = response.headers.get("Retry-After");
      console.error("Error: Rate limited by Jupiter API");
      if (retryAfter) {
        console.error(`Retry after: ${retryAfter} seconds`);
      }
      console.error("\nConsider upgrading your API tier at https://portal.jup.ag");
      process.exit(1);
    }

    let data;
    try {
      data = await response.json();
    } catch (e) {
      console.error("Error: API returned non-JSON response");
      console.error(`Status: ${response.status}`);
      const text = await response.text().catch(() => "Unable to read response");
      console.error(`Body: ${text.slice(0, 500)}`);
      process.exit(1);
    }

    if (!response.ok) {
      console.error(`Error: API returned status ${response.status}`);
      console.error(JSON.stringify(data, null, 2));
      process.exit(1);
    }

    // Output JSON to stdout for piping to other tools
    console.log(JSON.stringify(data, null, 2));
  } catch (error) {
    if (error instanceof Error) {
      if (error.name === "AbortError") {
        console.error(`Error: Request timed out after ${REQUEST_TIMEOUT_MS / 1000} seconds`);
      } else {
        console.error(`Network error: ${error.message}`);
      }
    } else {
      console.error("Unknown error occurred");
    }
    process.exit(1);
  } finally {
    clearTimeout(timeoutId);
  }
}

// CLI setup
const program = new Command();

program
  .name("fetch-api")
  .description("Fetch data from any Jupiter API endpoint")
  .requiredOption("-e, --endpoint <path>", "API endpoint path (e.g., /ultra/v1/order)")
  .option("-p, --params <json>", "Query parameters as JSON string (for GET) or body (for POST)")
  .option("-b, --body <json>", "Request body as JSON string (for POST)")
  .option("-m, --method <method>", "HTTP method (GET or POST)", "GET")
  .option("-k, --api-key <key>", "Jupiter API key (or use JUP_API_KEY env var)")
  .action(fetchApi);

program.parse();


path: scripts/utils.ts
content: /**
 * utils.ts - Shared utility functions for Jupiter API scripts
 */

/**
 * Gets the Jupiter API key from provided value or environment variable
 */
export function getApiKey(providedKey?: string): string | null {
  if (providedKey) return providedKey;
  if (process.env.JUP_API_KEY) return process.env.JUP_API_KEY;
  return null;
}

/**
 * Prints an error message about missing API key with instructions
 */
export function printApiKeyError(): void {
  console.error(`
Error: Jupiter API key is required.

To get an API key:
  1. Visit https://portal.jup.ag
  2. Create an account and generate an API key
  3. Use it via:
     - --api-key flag: pnpm fetch-api --api-key YOUR_KEY ...
     - Environment variable: export JUP_API_KEY=YOUR_KEY

Rate limits:
  - Free tier: 60 requests/minute
  - Pro tier: Up to 30,000 requests/minute
  - Ultra tier: Dynamic scaling with swap volume
`);
}

/**
 * Validates if a string is valid base64
 */
export function isValidBase64(str: string): boolean {
  if (!str || str.length === 0) return false;

  // Base64 regex: allows A-Z, a-z, 0-9, +, /, and = for padding
  const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;

  // Check regex and length (must be multiple of 4)
  if (!base64Regex.test(str) || str.length % 4 !== 0) {
    return false;
  }

  // Try to decode to verify
  try {
    Buffer.from(str, "base64");
    return true;
  } catch {
    return false;
  }
}

/**
 * Validates if a string is a valid UUID format
 * Accepts any version of UUID (1-5), not just v4
 */
export function isValidUUID(str: string): boolean {
  if (!str) return false;

  // General UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
  return uuidRegex.test(str);
}


path: scripts/wallet-sign.ts
content: #!/usr/bin/env tsx
/**
 * wallet-sign.ts - Sign Solana transactions using a local wallet
 *
 * Usage:
 *   pnpm wallet-sign --unsigned-tx "BASE64_TX" --wallet ~/.config/solana/id.json
 *   pnpm wallet-sign --unsigned-tx "BASE64_TX" --wallet /path/to/wallet.json
 *
 * Supports:
 *   - Solana CLI JSON wallet format (array of 64 bytes)
 *
 * SECURITY NOTE: The --wallet flag is required. This script does not accept
 * private keys via command line arguments to prevent exposure in shell history
 * and process listings.
 */

import { Command } from "commander";
import { Keypair, VersionedTransaction } from "@solana/web3.js";
import { readFileSync, existsSync } from "fs";
import { homedir } from "os";
import { join } from "path";

interface SignOptions {
  unsignedTx: string;
  wallet?: string;
}

/**
 * Expand tilde (~) in file paths to the user's home directory
 */
function expandTilde(filePath: string): string {
  if (filePath.startsWith("~")) {
    return join(homedir(), filePath.slice(1));
  }
  return filePath;
}

function loadKeypairFromJson(filePath: string): Keypair {
  const expandedPath = expandTilde(filePath);

  if (!existsSync(expandedPath)) {
    throw new Error(`Wallet file not found: ${expandedPath}`);
  }

  try {
    const fileContent = readFileSync(expandedPath, "utf-8");
    const secretKey = JSON.parse(fileContent);

    if (!Array.isArray(secretKey) || secretKey.length !== 64) {
      throw new Error(
        "Invalid wallet format. Expected JSON array of 64 bytes (Solana CLI format)"
      );
    }

    return Keypair.fromSecretKey(Uint8Array.from(secretKey));
  } catch (error) {
    if (error instanceof SyntaxError) {
      throw new Error(`Invalid JSON in wallet file: ${expandedPath}`);
    }
    throw error;
  }
}

function getKeypair(options: SignOptions): Keypair {
  if (!options.wallet) {
    throw new Error(`
No wallet provided. The --wallet flag is required.

Usage:
  pnpm wallet-sign --unsigned-tx "BASE64_TX" --wallet ~/.config/solana/id.json
  pnpm wallet-sign --unsigned-tx "BASE64_TX" --wallet /path/to/wallet.json

To create a new wallet:
  solana-keygen new -o ~/.config/solana/id.json

SECURITY NOTE: Private keys cannot be passed via command line arguments
to prevent exposure in shell history and process listings.
`);
  }

  return loadKeypairFromJson(options.wallet);
}

async function signTransaction(options: SignOptions): Promise<void> {
  // Load keypair
  let keypair: Keypair;
  try {
    keypair = getKeypair(options);
  } catch (error) {
    if (error instanceof Error) {
      console.error(`Error: ${error.message}`);
    }
    process.exit(1);
  }

  // Decode the unsigned transaction
  let transaction: VersionedTransaction;
  try {
    const txBuffer = Buffer.from(options.unsignedTx, "base64");
    transaction = VersionedTransaction.deserialize(txBuffer);
  } catch (error) {
    console.error("Error: Failed to deserialize transaction");
    console.error("Ensure the transaction is a valid base64-encoded VersionedTransaction");
    process.exit(1);
  }

  // Sign the transaction
  try {
    transaction.sign([keypair]);
  } catch (error) {
    console.error("Error: Failed to sign transaction");
    if (error instanceof Error) {
      console.error(error.message);
    }
    process.exit(1);
  }

  // Serialize and output the signed transaction
  const signedTxBuffer = transaction.serialize();
  const signedTxBase64 = Buffer.from(signedTxBuffer).toString("base64");

  // Output to stdout for piping
  console.log(signedTxBase64);

  // Log signer info to stderr (so it doesn't interfere with piped output)
  console.error(`\nSigned by: ${keypair.publicKey.toBase58()}`);
}

// CLI setup
const program = new Command();

program
  .name("wallet-sign")
  .description(
    "Sign Solana transactions using a local wallet file.\n\n" +
      "SECURITY: Private keys must be stored in wallet files, not passed via\n" +
      "command line arguments (which appear in shell history and process listings)."
  )
  .requiredOption("-t, --unsigned-tx <base64>", "Base64-encoded unsigned transaction")
  .requiredOption(
    "-w, --wallet <path>",
    "Path to Solana CLI JSON wallet file (array of 64 bytes). Supports ~ for home directory."
  )
  .action(signTransaction);

program.parse();


path: scripts/send-transaction.ts
content: #!/usr/bin/env tsx
/**
 * send-transaction.ts - Send signed transactions to Solana RPC
 *
 * Usage:
 *   pnpm send-transaction --signed-tx "BASE64_SIGNED_TX"
 *   pnpm send-transaction --signed-tx "BASE64_SIGNED_TX" --rpc-url "https://your-rpc.com"
 *
 * This script:
 *   1. Takes a signed transaction (base64)
 *   2. Sends it to the specified RPC endpoint
 *   3. Waits for confirmation
 *   4. Returns the transaction signature
 *
 * Use this for Metis swaps or any transaction that needs manual RPC submission.
 * For Ultra swaps, use execute-ultra.ts instead (Jupiter handles RPC internally).
 */

import { Command } from "commander";
import {
  Connection,
  VersionedTransaction,
  SendTransactionError,
} from "@solana/web3.js";

const DEFAULT_RPC_URL = "https://api.mainnet-beta.solana.com";

interface SendOptions {
  signedTx: string;
  rpcUrl?: string;
  skipPreflight?: boolean;
  maxRetries?: string;
}

function getRpcUrl(providedUrl?: string): string {
  if (providedUrl) return providedUrl;
  if (process.env.SOLANA_RPC_URL) return process.env.SOLANA_RPC_URL;
  return DEFAULT_RPC_URL;
}

async function sendTransaction(options: SendOptions): Promise<void> {
  const rpcUrl = getRpcUrl(options.rpcUrl);

  console.error(`Using RPC: ${rpcUrl}`);

  if (rpcUrl === DEFAULT_RPC_URL) {
    console.error("Warning: Using public Solana RPC which is rate-limited.");
    console.error("For production, set SOLANA_RPC_URL to your own RPC endpoint.");
  }

  // Deserialize the transaction
  let transaction: VersionedTransaction;
  try {
    const txBuffer = Buffer.from(options.signedTx, "base64");
    transaction = VersionedTransaction.deserialize(txBuffer);
  } catch (error) {
    console.error("Error: Failed to deserialize transaction");
    console.error("Ensure the transaction is a valid base64-encoded signed VersionedTransaction");
    process.exit(1);
  }

  // Check if transaction is signed
  if (transaction.signatures.length === 0) {
    console.error("Error: Transaction has no signatures");
    console.error("Sign the transaction first using: pnpm wallet-sign --unsigned-tx ...");
    process.exit(1);
  }
  
  // Create connection
  const connection = new Connection(rpcUrl, "confirmed");

  // Send transaction
  try {
    console.error("Sending transaction...");

    const maxRetries = options.maxRetries ? parseInt(options.maxRetries, 10) : 3;
    if (isNaN(maxRetries) || maxRetries < 0) {
      console.error("Error: --max-retries must be a non-negative integer");
      process.exit(1);
    }

    const signature = await connection.sendTransaction(transaction, {
      skipPreflight: options.skipPreflight ?? false,
      maxRetries,
    });

    console.error("Transaction sent, waiting for confirmation...");

    // Wait for confirmation using non-deprecated API
    const latestBlockhash = await connection.getLatestBlockhash();
    const confirmation = await connection.confirmTransaction({
      signature,
      blockhash: latestBlockhash.blockhash,
      lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
    }, "confirmed");

    if (confirmation.value.err) {
      console.error("Error: Transaction failed on-chain");
      console.error(JSON.stringify(confirmation.value.err, null, 2));
      process.exit(1);
    }

    // Output signature to stdout
    console.log(signature);

    // Additional info to stderr
    console.error(`\nTransaction confirmed!`);
    console.error(`Signature: ${signature}`);
    console.error(`Explorer: https://solscan.io/tx/${signature}`);
  } catch (error) {
    if (error instanceof SendTransactionError) {
      console.error("Error: Failed to send transaction");
      console.error(`Message: ${error.message}`);

      // Try to get more details
      const logs = error.logs;
      if (logs && logs.length > 0) {
        console.error("\nTransaction logs:");
        logs.forEach((log) => console.error(`  ${log}`));
      }
    } else if (error instanceof Error) {
      console.error(`Error: ${error.message}`);

      // Common error hints
      if (error.message.includes("blockhash")) {
        console.error("\nHint: The transaction's blockhash may have expired.");
        console.error("Request a fresh transaction and try again.");
      } else if (error.message.includes("insufficient")) {
        console.error("\nHint: The wallet may have insufficient SOL for fees.");
      } else if (error.message.includes("429") || error.message.includes("rate")) {
        console.error("\nHint: RPC rate limited. Try a different RPC endpoint.");
        console.error("You can specify one with: --rpc-url <url>");
      }
    } else {
      console.error("Unknown error occurred");
    }
    process.exit(1);
  }
}

// CLI setup
const program = new Command();

program
  .name("send-transaction")
  .description("Send signed transactions to Solana RPC")
  .requiredOption("-t, --signed-tx <base64>", "Base64-encoded signed transaction")
  .option(
    "-r, --rpc-url <url>",
    `RPC endpoint URL (default: ${DEFAULT_RPC_URL}, or SOLANA_RPC_URL env var)`
  )
  .option(
    "--skip-preflight",
    "Skip preflight transaction checks (faster but less safe)"
  )
  .option("--max-retries <number>", "Maximum send retries (default: 3)")
  .action(sendTransaction);

program.parse();


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)