diff --git a/.gitignore b/.gitignore index 8692757aefa..6be6cb15e17 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ yarn-error.log* # idea *.iml .idea/ +.playwright-mcp/ diff --git a/packages/public-api/.env.example b/packages/public-api/.env.example new file mode 100644 index 00000000000..d800eaf6015 --- /dev/null +++ b/packages/public-api/.env.example @@ -0,0 +1,21 @@ +# Server Configuration +PORT=3001 +HOST=0.0.0.0 +NODE_ENV=production + +# Asset Data Path (optional - will auto-detect if not set) +# ASSET_DATA_PATH=/app/public/generated/generatedAssetData.json + +# API Configuration (optional - defaults shown) +# VITE_PORTALS_API_KEY= +# VITE_CHAINALYSIS_API_KEY= +# VITE_ZRX_API_KEY= +# VITE_ZERION_API_KEY= + +# Unchained URLs (defaults to ShapeShift public instances) +# UNCHAINED_ETHEREUM_HTTP_URL=https://api.ethereum.shapeshift.com +# UNCHAINED_THORCHAIN_HTTP_URL=https://api.thorchain.shapeshift.com + +# Partner API Keys (add your partner keys here) +# Format: API_KEY_=:: +# Example: API_KEY_PARTNER1=abc123:MyPartner:50 diff --git a/packages/public-api/CLAUDE.md b/packages/public-api/CLAUDE.md new file mode 100644 index 00000000000..36d9178eb89 --- /dev/null +++ b/packages/public-api/CLAUDE.md @@ -0,0 +1,15 @@ +# Public API - LLM Instructions + +## Running the Server Locally (Recommended) + +Use bundled mode to avoid ESM/tsx module resolution issues: + +```bash +yarn build:bundle && yarn start:prod +``` + +This bundles everything with esbuild into `dist/server.cjs` and runs it with plain Node.js. + +## Code Style + +This codebase does **not** use semicolons. Follow the existing style in each file. diff --git a/packages/public-api/Dockerfile b/packages/public-api/Dockerfile new file mode 100644 index 00000000000..2814f6d748d --- /dev/null +++ b/packages/public-api/Dockerfile @@ -0,0 +1,78 @@ +# syntax=docker/dockerfile:1 + +# Build stage +FROM node:22-alpine AS builder + +WORKDIR /app + +# Install build dependencies (including Java for unchained-client code generation) +RUN apk add --no-cache python3 make g++ openjdk17-jre + +# Copy workspace files +COPY package.json yarn.lock .yarnrc.yml ./ +COPY .yarn ./.yarn + +# Copy ALL package.json files for workspace resolution (yarn requires all workspaces to be present) +COPY packages/public-api/package.json ./packages/public-api/ +COPY packages/caip/package.json ./packages/caip/ +COPY packages/types/package.json ./packages/types/ +COPY packages/utils/package.json ./packages/utils/ +COPY packages/swapper/package.json ./packages/swapper/ +COPY packages/chain-adapters/package.json ./packages/chain-adapters/ +COPY packages/unchained-client/package.json ./packages/unchained-client/ +COPY packages/contracts/package.json ./packages/contracts/ +COPY packages/errors/package.json ./packages/errors/ +COPY packages/swap-widget/package.json ./packages/swap-widget/ + +# Install dependencies with Railway cache mount (skip build/postinstall scripts that require git/cypress/etc) +RUN --mount=type=cache,id=s/5fa4b228-de48-4bb3-90d6-807d0c6e7f1c-/root/.yarn/berry/cache,target=/root/.yarn/berry/cache \ + yarn install --immutable --mode skip-build + +# Copy unchained-client config and generator files FIRST (rarely changes, enables layer caching) +COPY packages/unchained-client/openapitools.json ./packages/unchained-client/ +COPY packages/unchained-client/generator/ ./packages/unchained-client/generator/ + +# Generate unchained-client code (cached when openapitools.json unchanged) +# Cache mount for OpenAPI Generator JAR to avoid re-downloading on each build +RUN --mount=type=cache,id=s/5fa4b228-de48-4bb3-90d6-807d0c6e7f1c-/root/.openapitools,target=/root/.openapitools \ + yarn workspace @shapeshiftoss/unchained-client generate + +# Copy remaining source files +COPY packages/ ./packages/ +COPY public/generated/generatedAssetData.json ./public/generated/ +COPY tsconfig.json tsconfig.packages.json ./ + +# Build all packages using parallel tsc with project references +RUN yarn run -T tsc --build tsconfig.packages.json && \ + yarn workspaces foreach -ptiv --include '@shapeshiftoss/{errors,types,contracts,caip,utils,unchained-client,chain-adapters,swapper,public-api}' run postbuild + +# Build the public-api bundle +WORKDIR /app/packages/public-api +RUN yarn node esbuild.config.mjs + +# Build smoke tests bundle +RUN yarn node esbuild.smoke-tests.mjs + +# Production stage +FROM node:22-alpine AS runner + +WORKDIR /app + +# Copy only the bundled server, smoke tests, and asset data (node_modules not needed - esbuild bundles everything) +COPY --from=builder /app/packages/public-api/dist/server.cjs ./server.cjs +COPY --from=builder /app/packages/public-api/dist/smoke-tests.cjs ./smoke-tests.cjs +COPY --from=builder /app/public/generated/generatedAssetData.json ./public/generated/ + +# Set environment +ENV NODE_ENV=production +ENV ASSET_DATA_PATH=/app/public/generated/generatedAssetData.json +ENV PORT=3001 +ENV HOST=0.0.0.0 + +EXPOSE 3001 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1 + +CMD ["node", "server.cjs"] diff --git a/packages/public-api/Dockerfile.dockerignore b/packages/public-api/Dockerfile.dockerignore new file mode 100644 index 00000000000..9614c140cf7 --- /dev/null +++ b/packages/public-api/Dockerfile.dockerignore @@ -0,0 +1,30 @@ +# Dependencies (yarn install handles these fresh) +node_modules/ +packages/*/node_modules/ + +# Git +.git/ + +# Build artifacts +build/ +packages/*/dist/ + +# Development files +.env* +!.env.example +*.log +.DS_Store +coverage/ + +# IDE +.idea/ +.vscode/ + +# Test files not needed for build +cypress/ +**/*.test.ts +**/*.test.tsx +**/*.spec.ts + +# Other large/unnecessary directories +.playwright-mcp/ diff --git a/packages/public-api/esbuild.config.mjs b/packages/public-api/esbuild.config.mjs new file mode 100644 index 00000000000..368664106aa --- /dev/null +++ b/packages/public-api/esbuild.config.mjs @@ -0,0 +1,25 @@ +import * as esbuild from 'esbuild' + +const result = await esbuild.build({ + entryPoints: ['src/index.ts'], + bundle: true, + outfile: 'dist/server.cjs', + platform: 'node', + target: 'node20', + format: 'cjs', + sourcemap: true, + external: [ + // Keep native modules external + 'fsevents', + ], + alias: { + // Alias ethers/lib/utils to ethers which has the utils re-exported + 'ethers/lib/utils': 'ethers', + }, + define: { + 'process.env.NODE_ENV': '"production"', + }, + logLevel: 'info', +}) + +console.log('Build complete:', result) diff --git a/packages/public-api/esbuild.smoke-tests.mjs b/packages/public-api/esbuild.smoke-tests.mjs new file mode 100644 index 00000000000..5426e7227c1 --- /dev/null +++ b/packages/public-api/esbuild.smoke-tests.mjs @@ -0,0 +1,15 @@ +import * as esbuild from 'esbuild' + +const result = await esbuild.build({ + entryPoints: ['tests/run-smoke-tests.ts'], + bundle: true, + outfile: 'dist/smoke-tests.cjs', + platform: 'node', + target: 'node20', + format: 'cjs', + sourcemap: true, + external: ['fsevents'], + logLevel: 'info', +}) + +console.log('Smoke tests build complete:', result) diff --git a/packages/public-api/package.json b/packages/public-api/package.json new file mode 100644 index 00000000000..45fc380ea63 --- /dev/null +++ b/packages/public-api/package.json @@ -0,0 +1,46 @@ +{ + "name": "@shapeshiftoss/public-api", + "version": "0.1.0", + "packageManager": "yarn@3.5.0", + "repository": "https://github.com/shapeshift/web", + "license": "MIT", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "yarn clean && yarn run -T tsc --build", + "build:bundle": "node esbuild.config.mjs", + "build:smoke-tests": "node esbuild.smoke-tests.mjs", + "clean": "rm -rf dist", + "dev": "tsx watch src/index.ts", + "start": "node dist/index.js", + "start:prod": "node dist/server.cjs", + "test:smoke": "tsx tests/run-smoke-tests.ts", + "docker:build": "docker build -t shapeshift-public-api -f Dockerfile ../../", + "docker:run": "docker run -p 3001:3001 --env-file .env shapeshift-public-api" + }, + "dependencies": { + "@asteasolutions/zod-to-openapi": "^7.3.4", + "@scalar/express-api-reference": "^0.8.30", + "@shapeshiftoss/caip": "workspace:^", + "@shapeshiftoss/chain-adapters": "workspace:^", + "@shapeshiftoss/swapper": "workspace:^", + "@shapeshiftoss/types": "workspace:^", + "@shapeshiftoss/utils": "workspace:^", + "@sniptt/monads": "^0.5.10", + "@types/swagger-ui-express": "^4.1.8", + "cors": "^2.8.5", + "express": "^4.21.0", + "uuid": "^9.0.0", + "yaml": "^2.8.2", + "zod": "3.23.8" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^22.10.4", + "@types/uuid": "^9.0.5", + "tsx": "^4.19.2", + "typescript": "^5.7.3" + } +} diff --git a/packages/public-api/railway.toml b/packages/public-api/railway.toml new file mode 100644 index 00000000000..4badc5a3e70 --- /dev/null +++ b/packages/public-api/railway.toml @@ -0,0 +1,21 @@ +[build] +builder = "dockerfile" +dockerfilePath = "packages/public-api/Dockerfile" +watchPatterns = [ + "packages/public-api/**", + "packages/swapper/**", + "packages/caip/**", + "packages/types/**", + "packages/utils/**", + "packages/unchained-client/**", + "packages/chain-adapters/**", + "packages/contracts/**", + "packages/errors/**", + "public/generated/generatedAssetData.json" +] + +[deploy] +healthcheckPath = "/health" +healthcheckTimeout = 30 +restartPolicyType = "on_failure" +restartPolicyMaxRetries = 5 diff --git a/packages/public-api/src/assets.ts b/packages/public-api/src/assets.ts new file mode 100644 index 00000000000..643f8b1711b --- /dev/null +++ b/packages/public-api/src/assets.ts @@ -0,0 +1,77 @@ +import type { AssetId } from '@shapeshiftoss/caip' +import type { Asset, AssetsById } from '@shapeshiftoss/types' +import { getBaseAsset } from '@shapeshiftoss/utils' +import fs from 'fs' +import path from 'path' + +let assetsById: AssetsById = {} +let assetIds: AssetId[] = [] +let assets: Asset[] = [] +let initialized = false + +export const initAssets = (): Promise => { + if (initialized) return Promise.resolve() + + try { + // Try to load from the generated asset data file + // First check env var, then relative to cwd, then relative to monorepo root + const possiblePaths = [ + process.env.ASSET_DATA_PATH, + path.join(process.cwd(), 'public/generated/generatedAssetData.json'), + path.join(process.cwd(), '../../public/generated/generatedAssetData.json'), + path.join(process.cwd(), 'generatedAssetData.json'), + ].filter(Boolean) as string[] + + let assetDataPath: string | undefined + for (const p of possiblePaths) { + if (fs.existsSync(p)) { + assetDataPath = p + break + } + } + + if (!assetDataPath) { + const error = new Error( + `Asset data file not found in any of the expected locations: ${possiblePaths.join(', ')}`, + ) + console.warn(error.message) + return Promise.reject(error) + } + + const assetDataJson = JSON.parse(fs.readFileSync(assetDataPath, 'utf8')) + const localAssetData = assetDataJson.byId + const sortedAssetIds = assetDataJson.ids as AssetId[] + + // Enrich assets with chain-level data + const enrichedAssetsById: AssetsById = {} + for (const assetId of sortedAssetIds) { + const asset = localAssetData[assetId] + if (asset) { + const baseAsset = getBaseAsset(asset.chainId) + enrichedAssetsById[assetId] = { + ...asset, + networkName: baseAsset?.networkName, + explorer: baseAsset?.explorer, + explorerAddressLink: baseAsset?.explorerAddressLink, + explorerTxLink: baseAsset?.explorerTxLink, + } + } + } + + assetsById = enrichedAssetsById + assetIds = sortedAssetIds + assets = sortedAssetIds.map((id: AssetId) => enrichedAssetsById[id]).filter(Boolean) as Asset[] + + console.log(`Loaded ${assetIds.length} assets from ${assetDataPath}`) + initialized = true + return Promise.resolve() + } catch (error) { + console.error('Failed to load assets:', error) + return Promise.reject(error) + } +} + +export const getAssetsById = (): AssetsById => assetsById +export const getAssetIds = (): AssetId[] => assetIds +export const getAllAssets = (): Asset[] => assets +export const getAsset = (assetId: AssetId): Asset | undefined => assetsById[assetId] diff --git a/packages/public-api/src/config.ts b/packages/public-api/src/config.ts new file mode 100644 index 00000000000..421cf3ef967 --- /dev/null +++ b/packages/public-api/src/config.ts @@ -0,0 +1,63 @@ +import type { SwapperConfig } from '@shapeshiftoss/swapper' + +// Server-side config that mirrors the web app's config but from environment variables +export const getServerConfig = (): SwapperConfig => ({ + VITE_UNCHAINED_THORCHAIN_HTTP_URL: + process.env.UNCHAINED_THORCHAIN_HTTP_URL || 'https://api.thorchain.shapeshift.com', + VITE_UNCHAINED_MAYACHAIN_HTTP_URL: + process.env.UNCHAINED_MAYACHAIN_HTTP_URL || 'https://api.mayachain.shapeshift.com', + VITE_UNCHAINED_COSMOS_HTTP_URL: + process.env.UNCHAINED_COSMOS_HTTP_URL || 'https://api.cosmos.shapeshift.com', + VITE_THORCHAIN_NODE_URL: process.env.THORCHAIN_NODE_URL || 'https://thornode.ninerealms.com', + VITE_MAYACHAIN_NODE_URL: process.env.MAYACHAIN_NODE_URL || 'https://tendermint.mayachain.info', + VITE_TRON_NODE_URL: process.env.TRON_NODE_URL || 'https://api.trongrid.io', + VITE_FEATURE_THORCHAINSWAP_LONGTAIL: process.env.FEATURE_THORCHAINSWAP_LONGTAIL === 'true', + VITE_FEATURE_THORCHAINSWAP_L1_TO_LONGTAIL: + process.env.FEATURE_THORCHAINSWAP_L1_TO_LONGTAIL === 'true', + VITE_THORCHAIN_MIDGARD_URL: + process.env.THORCHAIN_MIDGARD_URL || 'https://midgard.ninerealms.com/v2', + VITE_MAYACHAIN_MIDGARD_URL: + process.env.MAYACHAIN_MIDGARD_URL || 'https://midgard.mayachain.info/v2', + VITE_UNCHAINED_BITCOIN_HTTP_URL: + process.env.UNCHAINED_BITCOIN_HTTP_URL || 'https://api.bitcoin.shapeshift.com', + VITE_UNCHAINED_DOGECOIN_HTTP_URL: + process.env.UNCHAINED_DOGECOIN_HTTP_URL || 'https://api.dogecoin.shapeshift.com', + VITE_UNCHAINED_LITECOIN_HTTP_URL: + process.env.UNCHAINED_LITECOIN_HTTP_URL || 'https://api.litecoin.shapeshift.com', + VITE_UNCHAINED_BITCOINCASH_HTTP_URL: + process.env.UNCHAINED_BITCOINCASH_HTTP_URL || 'https://api.bitcoincash.shapeshift.com', + VITE_UNCHAINED_ETHEREUM_HTTP_URL: + process.env.UNCHAINED_ETHEREUM_HTTP_URL || 'https://api.ethereum.shapeshift.com', + VITE_UNCHAINED_AVALANCHE_HTTP_URL: + process.env.UNCHAINED_AVALANCHE_HTTP_URL || 'https://api.avalanche.shapeshift.com', + VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL: + process.env.UNCHAINED_BNBSMARTCHAIN_HTTP_URL || 'https://api.bnbsmartchain.shapeshift.com', + VITE_UNCHAINED_BASE_HTTP_URL: + process.env.UNCHAINED_BASE_HTTP_URL || 'https://api.base.shapeshift.com', + VITE_COWSWAP_BASE_URL: process.env.COWSWAP_BASE_URL || 'https://api.cow.fi', + VITE_PORTALS_BASE_URL: process.env.PORTALS_BASE_URL || 'https://api.portals.fi', + VITE_ZRX_BASE_URL: process.env.ZRX_BASE_URL || 'https://api.proxy.shapeshift.com/api/v1/zrx/', + VITE_CHAINFLIP_API_KEY: process.env.CHAINFLIP_API_KEY || '', + VITE_CHAINFLIP_API_URL: process.env.CHAINFLIP_API_URL || 'https://chainflip-broker.io', + VITE_FEATURE_CHAINFLIP_SWAP_DCA: process.env.FEATURE_CHAINFLIP_SWAP_DCA === 'true', + VITE_JUPITER_API_URL: process.env.JUPITER_API_URL || 'https://quote-api.jup.ag/v6', + VITE_RELAY_API_URL: process.env.RELAY_API_URL || 'https://api.relay.link', + VITE_BEBOP_API_KEY: process.env.BEBOP_API_KEY || '', + VITE_NEAR_INTENTS_API_KEY: process.env.NEAR_INTENTS_API_KEY || '', + VITE_TENDERLY_API_KEY: process.env.TENDERLY_API_KEY || '', + VITE_TENDERLY_ACCOUNT_SLUG: process.env.TENDERLY_ACCOUNT_SLUG || '', + VITE_TENDERLY_PROJECT_SLUG: process.env.TENDERLY_PROJECT_SLUG || '', + VITE_SUI_NODE_URL: process.env.SUI_NODE_URL || 'https://fullnode.mainnet.sui.io', +}) + +// Default affiliate fee in basis points +export const DEFAULT_AFFILIATE_BPS = '55' + +// API server config +export const API_PORT = parseInt(process.env.PORT || '3001', 10) +export const API_HOST = process.env.HOST || '0.0.0.0' + +// Static API keys for testing (in production these would come from a database) +export const STATIC_API_KEYS: Record = { + 'test-api-key-123': { name: 'Test Partner', feeSharePercentage: 50 }, +} diff --git a/packages/public-api/src/docs/openapi.ts b/packages/public-api/src/docs/openapi.ts new file mode 100644 index 00000000000..5f3fc5ffa1c --- /dev/null +++ b/packages/public-api/src/docs/openapi.ts @@ -0,0 +1,281 @@ +import '../setupZod' + +import { OpenApiGeneratorV3, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi' +import { z } from 'zod' + +import { AssetRequestSchema, AssetsListRequestSchema } from '../routes/assets' +import { QuoteRequestSchema } from '../routes/quote' +import { RatesRequestSchema } from '../routes/rates' + +export const registry = new OpenAPIRegistry() + +// Register reusable schemas +// We should probably define the response schemas with Zod too, but for now we'll do best effort with the request schemas +// and basic response structures. + +// Security Schemes +registry.registerComponent('securitySchemes', 'apiKeyAuth', { + type: 'apiKey', + in: 'header', + name: 'X-API-Key', +}) + +// --- Definitions --- + +// Asset +const AssetSchema = registry.register( + 'Asset', + z.object({ + assetId: z.string().openapi({ example: 'eip155:1/slip44:60' }), + chainId: z.string().openapi({ example: 'eip155:1' }), + name: z.string().openapi({ example: 'Ethereum' }), + symbol: z.string().openapi({ example: 'ETH' }), + precision: z.number().openapi({ example: 18 }), + color: z.string().openapi({ example: '#5C6BC0' }), + icon: z.string().openapi({ + example: 'https://assets.coincap.io/assets/icons/eth@2x.png', + }), + explorer: z.string().openapi({ example: 'https://etherscan.io' }), + explorerAddressLink: z.string().openapi({ example: 'https://etherscan.io/address/' }), + explorerTxLink: z.string().openapi({ example: 'https://etherscan.io/tx/' }), + }), +) + +// Quote Response Step +const QuoteStepSchema = registry.register( + 'QuoteStep', + z.object({ + sellAsset: AssetSchema, + buyAsset: AssetSchema, + sellAmountCryptoBaseUnit: z.string().openapi({ example: '1000000000000000000' }), + buyAmountAfterFeesCryptoBaseUnit: z.string().openapi({ example: '995000000' }), + allowanceContract: z + .string() + .openapi({ example: '0xdef1c0ded9bec7f1a1670819833240f027b25eff' }), + estimatedExecutionTimeMs: z.number().optional().openapi({ example: 60000 }), + source: z.string().openapi({ example: '0x' }), + transactionData: z + .object({ + to: z.string(), + data: z.string(), + value: z.string(), + gasLimit: z.string().optional(), + }) + .optional(), + }), +) + +// Quote Response +const QuoteResponseSchema = registry.register( + 'QuoteResponse', + z.object({ + quoteId: z.string().uuid(), + swapperName: z.string().openapi({ example: '0x' }), + rate: z.string().openapi({ example: '0.995' }), + sellAsset: AssetSchema, + buyAsset: AssetSchema, + sellAmountCryptoBaseUnit: z.string(), + buyAmountBeforeFeesCryptoBaseUnit: z.string(), + buyAmountAfterFeesCryptoBaseUnit: z.string(), + affiliateBps: z.string().openapi({ example: '10' }), + slippageTolerancePercentageDecimal: z.string().optional().openapi({ example: '0.01' }), + steps: z.array(QuoteStepSchema), + expiresAt: z.number(), + }), +) + +// Rate Response +const RateResponseSchema = registry.register( + 'RateResponse', + z.object({ + rates: z.array( + z.object({ + swapperName: z.string(), + rate: z.string(), + buyAmountCryptoBaseUnit: z.string(), + sellAmountCryptoBaseUnit: z.string(), + steps: z.number(), + estimatedExecutionTimeMs: z.number().optional(), + priceImpactPercentageDecimal: z.string().optional(), + affiliateBps: z.string(), + networkFeeCryptoBaseUnit: z.string().optional(), + error: z + .object({ + code: z.string(), + message: z.string(), + }) + .optional(), + }), + ), + timestamp: z.number(), + expiresAt: z.number(), + }), +) + +// --- Paths --- + +// GET /v1/assets +registry.registerPath({ + method: 'get', + path: '/v1/assets', + summary: 'List supported assets', + description: 'Get a list of all supported assets, optionally filtered by chain.', + tags: ['Assets'], + request: { + query: AssetsListRequestSchema, + }, + responses: { + 200: { + description: 'List of assets', + content: { + 'application/json': { + schema: z.object({ + assets: z.array(AssetSchema), + timestamp: z.number(), + }), + }, + }, + }, + }, +}) + +// GET /v1/assets/{assetId} +registry.registerPath({ + method: 'get', + path: '/v1/assets/{assetId}', + summary: 'Get asset by ID', + description: 'Get details of a specific asset by its ID (URL encoded).', + tags: ['Assets'], + request: { + params: AssetRequestSchema, + }, + responses: { + 200: { + description: 'Asset details', + content: { + 'application/json': { + schema: AssetSchema, + }, + }, + }, + 404: { + description: 'Asset not found', + }, + }, +}) + +// GET /v1/swap/rates +registry.registerPath({ + method: 'get', + path: '/v1/swap/rates', + summary: 'Get swap rates', + description: + 'Get informative swap rates from all available swappers. This does not create a transaction.', + tags: ['Swaps'], + security: [{ apiKeyAuth: [] }], + request: { + query: RatesRequestSchema, + }, + responses: { + 200: { + description: 'Swap rates', + content: { + 'application/json': { + schema: RateResponseSchema, + }, + }, + }, + 400: { + description: 'Invalid request', + }, + }, +}) + +// POST /v1/swap/quote +registry.registerPath({ + method: 'post', + path: '/v1/swap/quote', + summary: 'Get executable quote', + description: + 'Get an executable quote for a swap, including transaction data. Requires a specific swapper name.', + tags: ['Swaps'], + security: [{ apiKeyAuth: [] }], + request: { + body: { + content: { + 'application/json': { + schema: QuoteRequestSchema, + }, + }, + }, + }, + responses: { + 200: { + description: 'Swap quote', + content: { + 'application/json': { + schema: QuoteResponseSchema, + }, + }, + }, + 400: { + description: 'Invalid request or unavailable swapper', + }, + }, +}) + +export const generateOpenApiDocument = () => { + const generator = new OpenApiGeneratorV3(registry.definitions) + + return generator.generateDocument({ + openapi: '3.0.0', + info: { + version: '1.0.0', + title: 'ShapeShift Public API', + description: `The ShapeShift Public API enables developers to integrate multi-chain swap functionality into their applications. Access rates from multiple DEX aggregators and execute swaps across supported blockchains. + +## Integration Overview + +### 1. Get Supported Assets +First, fetch the list of supported assets to populate your UI: +\`\`\` +GET /v1/assets +\`\`\` + +### 2. Get Swap Rates +When a user wants to swap, fetch rates from all available swappers to find the best deal: +\`\`\` +GET /v1/swap/rates?sellAssetId=eip155:1/slip44:60&buyAssetId=eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&sellAmountCryptoBaseUnit=1000000000000000000 +\`\`\` +This returns rates from THORChain, 0x, CoW Swap, and other supported swappers. + +### 3. Get Executable Quote +Once the user selects a rate, request an executable quote with transaction data: +\`\`\` +POST /v1/swap/quote +{ + "sellAssetId": "eip155:1/slip44:60", + "buyAssetId": "bip122:000000000019d6689c085ae165831e93/slip44:0", + "sellAmountCryptoBaseUnit": "1000000000000000000", + "swapperName": "Relay", + "receiveAddress": "bc1q...", + "sendAddress": "0x..." +} +\`\`\` + +### 4. Execute the Swap +Use the returned \`transactionData\` to build and sign a transaction with the user's wallet, then broadcast it to the network. + +## Authentication +Include your API key in the \`X-API-Key\` header for all swap endpoints. + +## Asset IDs +Assets use CAIP-19 format: \`{chainId}/{assetNamespace}:{assetReference}\` +- Native ETH: \`eip155:1/slip44:60\` +- USDC on Ethereum: \`eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\` +- Native BTC: \`bip122:000000000019d6689c085ae165831e93/slip44:0\` +`, + }, + servers: [{ url: 'https://api.shapeshift.com' }, { url: 'http://localhost:3001' }], + }) +} diff --git a/packages/public-api/src/index.ts b/packages/public-api/src/index.ts new file mode 100644 index 00000000000..efce70190c3 --- /dev/null +++ b/packages/public-api/src/index.ts @@ -0,0 +1,92 @@ +import './setupZod' + +import cors from 'cors' +import express from 'express' + +import { initAssets } from './assets' +import { API_HOST, API_PORT } from './config' +import { apiKeyAuth, optionalApiKeyAuth } from './middleware/auth' +import { getAssetById, getAssetCount, getAssets } from './routes/assets' +import { docsRouter } from './routes/docs' +import { getQuote } from './routes/quote' +import { getRates } from './routes/rates' + +const app = express() + +// Middleware +app.use(cors()) +app.use(express.json()) + +// Root endpoint - API info +app.get('/', (_req, res) => { + res.json({ + name: 'ShapeShift API', + version: '1.0.0', + description: 'Decentralized swap and asset discovery API', + documentation: '/docs', + endpoints: { + health: 'GET /health', + assets: 'GET /v1/assets', + assetCount: 'GET /v1/assets/count', + assetById: 'GET /v1/assets/:assetId', + swapRates: 'GET /v1/swap/rates', + swapQuote: 'POST /v1/swap/quote', + }, + }) +}) + +// Health check (no auth required) +app.get('/health', (_req, res) => { + res.json({ status: 'ok', timestamp: Date.now() }) +}) + +// API v1 routes +const v1Router = express.Router() + +// Swap endpoints (require API key) +v1Router.get('/swap/rates', apiKeyAuth, getRates) +v1Router.post('/swap/quote', apiKeyAuth, getQuote) + +// Asset endpoints (optional auth) +v1Router.get('/assets', optionalApiKeyAuth, getAssets) +v1Router.get('/assets/count', optionalApiKeyAuth, getAssetCount) +v1Router.get('/assets/:assetId(*)', optionalApiKeyAuth, getAssetById) + +app.use('/v1', v1Router) +app.use('/docs', docsRouter) + +// 404 handler +app.use((_req, res) => { + res.status(404).json({ error: 'Not found' }) +}) + +// Error handler +app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => { + console.error('Unhandled error:', err) + res.status(500).json({ error: 'Internal server error' }) +}) + +// Start server +const startServer = async () => { + console.log('Initializing assets...') + await initAssets() + + app.listen(API_PORT, API_HOST, () => { + console.log(`Public API server running at http://${API_HOST}:${API_PORT}`) + console.log(` +Available endpoints: + GET /health - Health check + GET /v1/swap/rates - Get swap rates from all swappers + POST /v1/swap/quote - Get executable quote with tx data + GET /v1/assets - List supported assets + GET /v1/assets/count - Get asset count + GET /v1/assets/:assetId - Get single asset by ID + +Authentication: + Include 'X-API-Key' header with your API key for /v1/swap/* endpoints. + Test API key: test-api-key-123 + `) + }) +} + +startServer().catch(console.error) diff --git a/packages/public-api/src/lib/zod.ts b/packages/public-api/src/lib/zod.ts new file mode 100644 index 00000000000..21543279075 --- /dev/null +++ b/packages/public-api/src/lib/zod.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +export const booleanFromString = z.preprocess( + val => (val === 'true' ? true : val === 'false' ? false : val), + z.boolean(), +) diff --git a/packages/public-api/src/middleware/auth.ts b/packages/public-api/src/middleware/auth.ts new file mode 100644 index 00000000000..c859cc5c66e --- /dev/null +++ b/packages/public-api/src/middleware/auth.ts @@ -0,0 +1,83 @@ +import crypto from 'crypto' +import type { NextFunction, Request, Response } from 'express' + +import { STATIC_API_KEYS } from '../config' +import type { ErrorResponse, PartnerConfig } from '../types' + +// Hash an API key for comparison (in production, keys would be stored hashed) +const hashApiKey = (key: string): string => { + return crypto.createHash('sha256').update(key).digest('hex') +} + +// API key authentication middleware +export const apiKeyAuth = (req: Request, res: Response, next: NextFunction): void => { + const apiKey = req.header('X-API-Key') + + if (!apiKey) { + const errorResponse: ErrorResponse = { + error: 'API key required', + code: 'MISSING_API_KEY', + } + res.status(401).json(errorResponse) + return + } + + // Look up the partner by API key + // In production, this would query a database + const partnerInfo = STATIC_API_KEYS[apiKey] + + if (!partnerInfo) { + const errorResponse: ErrorResponse = { + error: 'Invalid API key', + code: 'INVALID_API_KEY', + } + res.status(401).json(errorResponse) + return + } + + // Attach partner info to request + const partner: PartnerConfig = { + id: hashApiKey(apiKey).substring(0, 16), // Use hash prefix as ID + apiKeyHash: hashApiKey(apiKey), + name: partnerInfo.name, + feeSharePercentage: partnerInfo.feeSharePercentage, + status: 'active', + rateLimit: { + requestsPerMinute: 60, + requestsPerDay: 10000, + }, + createdAt: new Date(), + } + + req.partner = partner + + next() +} + +// Optional auth - allows unauthenticated requests but attaches partner info if present +export const optionalApiKeyAuth = (req: Request, _res: Response, next: NextFunction): void => { + const apiKey = req.header('X-API-Key') + + if (apiKey) { + const partnerInfo = STATIC_API_KEYS[apiKey] + + if (partnerInfo) { + const partner: PartnerConfig = { + id: hashApiKey(apiKey).substring(0, 16), + apiKeyHash: hashApiKey(apiKey), + name: partnerInfo.name, + feeSharePercentage: partnerInfo.feeSharePercentage, + status: 'active', + rateLimit: { + requestsPerMinute: 60, + requestsPerDay: 10000, + }, + createdAt: new Date(), + } + + req.partner = partner + } + } + + next() +} diff --git a/packages/public-api/src/routes/assets.ts b/packages/public-api/src/routes/assets.ts new file mode 100644 index 00000000000..4c69e3515ae --- /dev/null +++ b/packages/public-api/src/routes/assets.ts @@ -0,0 +1,102 @@ +import type { Request, Response } from 'express' +import { z } from 'zod' + +import { getAllAssets, getAsset, getAssetIds } from '../assets' +import type { AssetsResponse, ErrorResponse } from '../types' + +// Request validation schema for single asset +export const AssetRequestSchema = z.object({ + assetId: z.string().min(1).openapi({ example: 'eip155:1/slip44:60' }), +}) + +// Request validation schema for filtered list +export const AssetsListRequestSchema = z.object({ + chainId: z.string().optional().openapi({ example: 'eip155:1' }), + limit: z.coerce.number().min(1).max(1000).optional().default(100).openapi({ example: 100 }), + offset: z.coerce.number().min(0).optional().default(0).openapi({ example: 0 }), +}) + +export const getAssets = (req: Request, res: Response): void => { + try { + const parseResult = AssetsListRequestSchema.safeParse(req.query) + if (!parseResult.success) { + const errorResponse: ErrorResponse = { + error: 'Invalid request parameters', + details: parseResult.error.errors, + } + res.status(400).json(errorResponse) + return + } + + const { chainId, limit, offset } = parseResult.data + + let assets = getAllAssets() + + // Filter by chain if specified + if (chainId) { + assets = assets.filter(asset => asset.chainId === chainId) + } + + // Apply pagination + const paginatedAssets = assets.slice(offset, offset + limit) + + const response: AssetsResponse = { + assets: paginatedAssets, + timestamp: Date.now(), + } + + res.json(response) + } catch (error) { + console.error('Error in getAssets:', error) + res.status(500).json({ error: 'Internal server error' } as ErrorResponse) + } +} + +export const getAssetById = (req: Request, res: Response): void => { + try { + const parseResult = AssetRequestSchema.safeParse(req.params) + if (!parseResult.success) { + const errorResponse: ErrorResponse = { + error: 'Invalid request parameters', + details: parseResult.error.errors, + } + res.status(400).json(errorResponse) + return + } + + const { assetId } = parseResult.data + // URL decode the assetId since it contains special characters + let decodedAssetId: string + try { + decodedAssetId = decodeURIComponent(assetId) + } catch { + const errorResponse: ErrorResponse = { + error: 'Invalid URL encoding for assetId', + details: { assetId }, + } + res.status(400).json(errorResponse) + return + } + const asset = getAsset(decodedAssetId) + + if (!asset) { + res.status(404).json({ error: `Asset not found: ${decodedAssetId}` } as ErrorResponse) + return + } + + res.json(asset) + } catch (error) { + console.error('Error in getAssetById:', error) + res.status(500).json({ error: 'Internal server error' } as ErrorResponse) + } +} + +export const getAssetCount = (_req: Request, res: Response): void => { + try { + const count = getAssetIds().length + res.json({ count, timestamp: Date.now() }) + } catch (error) { + console.error('Error in getAssetCount:', error) + res.status(500).json({ error: 'Internal server error' } as ErrorResponse) + } +} diff --git a/packages/public-api/src/routes/docs.ts b/packages/public-api/src/routes/docs.ts new file mode 100644 index 00000000000..0434728335f --- /dev/null +++ b/packages/public-api/src/routes/docs.ts @@ -0,0 +1,41 @@ +import { apiReference } from '@scalar/express-api-reference' +import express from 'express' + +import { generateOpenApiDocument } from '../docs/openapi' + +const router = express.Router() + +// Generate Spec +const openApiDocument = generateOpenApiDocument() + +// Serve raw JSON spec +router.get('/json', (_req, res) => { + res.json(openApiDocument) +}) + +// Serve Scalar UI +router.use( + '/', + apiReference({ + spec: { + content: openApiDocument, + }, + pageTitle: 'ShapeShift API Reference', + theme: 'purple', + showSidebar: true, + hideDownloadButton: true, + darkMode: true, + defaultOpenAllTags: true, + authentication: { + preferredSecurityScheme: 'apiKeyAuth', + apiKey: { + token: 'test-api-key-123', + }, + }, + customCss: ` + .sidebar { --theme-color-1: #383838; } + `, + } as any), +) + +export const docsRouter = router diff --git a/packages/public-api/src/routes/quote.ts b/packages/public-api/src/routes/quote.ts new file mode 100644 index 00000000000..6b3c35cb587 --- /dev/null +++ b/packages/public-api/src/routes/quote.ts @@ -0,0 +1,281 @@ +import { fromChainId } from '@shapeshiftoss/caip' +import type { + GetTradeQuoteInputWithWallet, + SwapperName, + TradeQuote, + TradeQuoteStep, +} from '@shapeshiftoss/swapper' +import { isNativeEvmAsset } from '@shapeshiftoss/swapper' +import type { Request, Response } from 'express' +import { v4 as uuidv4 } from 'uuid' +import { z } from 'zod' + +import { getAsset, getAssetsById } from '../assets' +import { DEFAULT_AFFILIATE_BPS } from '../config' +import { booleanFromString } from '../lib/zod' +import { createServerSwapperDeps } from '../swapperDeps' +import type { ApiQuoteStep, ApprovalInfo, ErrorResponse, QuoteResponse } from '../types' + +// Lazy load swapper to avoid import issues at module load time +let swapperModule: Awaited> | null = null +const importSwapperModule = () => import('@shapeshiftoss/swapper') +const getSwapperModule = async () => { + if (!swapperModule) { + swapperModule = await importSwapperModule() + } + return swapperModule +} + +// Request validation schema - swapperName is string, validated later +export const QuoteRequestSchema = z.object({ + sellAssetId: z.string().min(1).openapi({ example: 'eip155:1/slip44:60' }), + buyAssetId: z.string().min(1).openapi({ + example: 'bip122:000000000019d6689c085ae165831e93/slip44:0', + }), + sellAmountCryptoBaseUnit: z.string().min(1).openapi({ example: '1000000000000000000' }), + receiveAddress: z + .string() + .min(1) + .openapi({ example: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq' }), + sendAddress: z + .string() + .optional() + .openapi({ example: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' }), + swapperName: z.string().min(1).openapi({ example: 'Relay' }), + slippageTolerancePercentageDecimal: z.string().optional().openapi({ example: '0.01' }), + allowMultiHop: booleanFromString.optional().default(true).openapi({ example: true }), + accountNumber: z.coerce.number().optional().default(0).openapi({ example: 0 }), +}) + +// Helper to extract transaction data from quote step +const extractTransactionData = (step: TradeQuoteStep): ApiQuoteStep['transactionData'] => { + // Check for various swapper-specific transaction metadata + if (step.zrxTransactionMetadata) { + return { + to: step.zrxTransactionMetadata.to, + data: step.zrxTransactionMetadata.data, + value: step.zrxTransactionMetadata.value, + gasLimit: step.zrxTransactionMetadata.gas, + } + } + + if (step.portalsTransactionMetadata) { + return { + to: step.portalsTransactionMetadata.to, + data: step.portalsTransactionMetadata.data, + value: step.portalsTransactionMetadata.value, + gasLimit: step.portalsTransactionMetadata.gasLimit, + } + } + + if (step.bebopTransactionMetadata) { + return { + to: step.bebopTransactionMetadata.to, + data: step.bebopTransactionMetadata.data, + value: step.bebopTransactionMetadata.value, + gasLimit: step.bebopTransactionMetadata.gas, + } + } + + if (step.butterSwapTransactionMetadata) { + return { + to: step.butterSwapTransactionMetadata.to, + data: step.butterSwapTransactionMetadata.data, + value: step.butterSwapTransactionMetadata.value, + gasLimit: step.butterSwapTransactionMetadata.gasLimit, + } + } + + // For THORChain/MAYAChain and other swappers, transaction is built differently + // The consumer will need to use the quote data to build the transaction + return undefined +} + +const buildApprovalInfo = (step: TradeQuoteStep, _sellAssetId: string): ApprovalInfo => { + const { chainNamespace } = fromChainId(step.sellAsset.chainId) + + if (chainNamespace !== 'eip155') { + return { isRequired: false, spender: '' } + } + + if (isNativeEvmAsset(step.sellAsset.assetId)) { + return { isRequired: false, spender: '' } + } + + if (step.allowanceContract) { + return { + isRequired: true, + spender: step.allowanceContract, + } + } + + return { isRequired: false, spender: '' } +} + +// Transform quote step to API format +const transformQuoteStep = (step: TradeQuoteStep): ApiQuoteStep => ({ + sellAsset: step.sellAsset, + buyAsset: step.buyAsset, + sellAmountCryptoBaseUnit: step.sellAmountIncludingProtocolFeesCryptoBaseUnit, + buyAmountAfterFeesCryptoBaseUnit: step.buyAmountAfterFeesCryptoBaseUnit, + allowanceContract: step.allowanceContract, + estimatedExecutionTimeMs: step.estimatedExecutionTimeMs, + source: step.source, + transactionData: extractTransactionData(step), +}) + +export const getQuote = async (req: Request, res: Response): Promise => { + try { + // Parse and validate request + const parseResult = QuoteRequestSchema.safeParse(req.body) + if (!parseResult.success) { + const errorResponse: ErrorResponse = { + error: 'Invalid request parameters', + details: parseResult.error.errors, + } + res.status(400).json(errorResponse) + return + } + + const { + sellAssetId, + buyAssetId, + sellAmountCryptoBaseUnit, + receiveAddress, + sendAddress, + swapperName, + slippageTolerancePercentageDecimal, + allowMultiHop, + accountNumber, + } = parseResult.data + + // Lazy load swapper module + const { getTradeQuotes, swappers, SwapperName, getDefaultSlippageDecimalPercentageForSwapper } = + await getSwapperModule() + + // Validate swapper name + const validSwapperName = Object.values(SwapperName).find(v => v === swapperName) as + | SwapperName + | undefined + if (!validSwapperName) { + res.status(400).json({ error: `Unknown swapper: ${swapperName}` } as ErrorResponse) + return + } + + // Validate swapper exists + const swapper = swappers[validSwapperName] + if (!swapper) { + res.status(400).json({ + error: `Swapper not available: ${swapperName}`, + } as ErrorResponse) + return + } + + // Get assets + const sellAsset = getAsset(sellAssetId) + const buyAsset = getAsset(buyAssetId) + + if (!sellAsset) { + res.status(400).json({ error: `Unknown sell asset: ${sellAssetId}` } as ErrorResponse) + return + } + if (!buyAsset) { + res.status(400).json({ error: `Unknown buy asset: ${buyAssetId}` } as ErrorResponse) + return + } + + // Create swapper dependencies + const deps = createServerSwapperDeps(getAssetsById()) + + // Get default slippage if not provided + let slippage = slippageTolerancePercentageDecimal + if (!slippage) { + try { + slippage = getDefaultSlippageDecimalPercentageForSwapper(validSwapperName) + } catch { + slippage = '0.01' // 1% default fallback + } + } + + // Build quote input + const quoteInput = { + sellAsset, + buyAsset, + sellAmountIncludingProtocolFeesCryptoBaseUnit: sellAmountCryptoBaseUnit, + affiliateBps: DEFAULT_AFFILIATE_BPS, + allowMultiHop, + slippageTolerancePercentageDecimal: slippage, + receiveAddress, + sendAddress, + accountNumber, + quoteOrRate: 'quote' as const, + chainId: sellAsset.chainId, + // EVM-specific fields + supportsEIP1559: true, + } + + // Fetch quote + const result = await getTradeQuotes( + quoteInput as GetTradeQuoteInputWithWallet, + validSwapperName, + deps, + ) + + if (!result) { + res.status(404).json({ + error: 'No quote available from this swapper', + } as ErrorResponse) + return + } + + if (result.isErr()) { + const error = result.unwrapErr() + res.status(400).json({ + error: error.message, + code: error.code, + details: error.details, + } as ErrorResponse) + return + } + + const quotes = result.unwrap() + if (quotes.length === 0) { + res.status(404).json({ error: 'No quote available' } as ErrorResponse) + return + } + + // Use the first/best quote + const quote = quotes[0] as TradeQuote + const firstStep = quote.steps[0] + + // Calculate total buy amount (sum of all steps for multi-hop) + const lastStep = quote.steps[quote.steps.length - 1] + + // Build response + const quoteId = uuidv4() + const now = Date.now() + + const response: QuoteResponse = { + quoteId, + swapperName: validSwapperName, + rate: quote.rate, + sellAsset, + buyAsset, + sellAmountCryptoBaseUnit: firstStep.sellAmountIncludingProtocolFeesCryptoBaseUnit, + buyAmountBeforeFeesCryptoBaseUnit: lastStep.buyAmountBeforeFeesCryptoBaseUnit, + buyAmountAfterFeesCryptoBaseUnit: lastStep.buyAmountAfterFeesCryptoBaseUnit, + affiliateBps: quote.affiliateBps, + slippageTolerancePercentageDecimal: quote.slippageTolerancePercentageDecimal, + networkFeeCryptoBaseUnit: firstStep.feeData.networkFeeCryptoBaseUnit, + steps: quote.steps.map(transformQuoteStep), + approval: buildApprovalInfo(firstStep, sellAssetId), + expiresAt: now + 60_000, // 60 second expiry + quote, // Include full quote for advanced consumers + } + + res.json(response) + } catch (error) { + console.error('Error in getQuote:', error) + res.status(500).json({ error: 'Internal server error' } as ErrorResponse) + } +} diff --git a/packages/public-api/src/routes/rates.ts b/packages/public-api/src/routes/rates.ts new file mode 100644 index 00000000000..d5a4f205c09 --- /dev/null +++ b/packages/public-api/src/routes/rates.ts @@ -0,0 +1,221 @@ +import type { GetTradeRateInput } from '@shapeshiftoss/swapper' +import { TradeQuoteError } from '@shapeshiftoss/swapper' +import type { Request, Response } from 'express' +import { z } from 'zod' + +import { getAsset, getAssetsById } from '../assets' +import { DEFAULT_AFFILIATE_BPS } from '../config' +import { booleanFromString } from '../lib/zod' +import { createServerSwapperDeps } from '../swapperDeps' +import type { ApiRate, ErrorResponse, RatesResponse } from '../types' + +// Request validation schema +export const RatesRequestSchema = z.object({ + sellAssetId: z.string().min(1).openapi({ example: 'eip155:1/slip44:60' }), + buyAssetId: z.string().min(1).openapi({ + example: 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + }), + sellAmountCryptoBaseUnit: z + .string() + .regex(/^\d+$/, 'sellAmountCryptoBaseUnit must be a positive integer') + .openapi({ example: '1000000000000000000' }), + slippageTolerancePercentageDecimal: z + .string() + .regex( + /^(?:\d+)(?:\.\d+)?$/, + 'slippageTolerancePercentageDecimal must be a non-negative decimal number', + ) + .optional() + .openapi({ example: '0.01' }), + allowMultiHop: booleanFromString.optional().default(true).openapi({ example: true }), +}) + +// Swapper names as strings to avoid import issues +const ENABLED_SWAPPER_NAMES = [ + 'THORChain', + 'MAYAChain', + '0x', + 'CoW Swap', + 'Portals', + 'Chainflip', + 'Jupiter', + 'Relay', + 'ButterSwap', + 'Bebop', +] as const + +// Rate timeout per swapper (10 seconds) +const RATE_TIMEOUT_MS = 10_000 + +// Lazy load swapper to avoid import issues at module load time +let swapperModule: Awaited> | null = null +const importSwapperModule = () => import('@shapeshiftoss/swapper') +const getSwapperModule = async () => { + if (!swapperModule) { + swapperModule = await importSwapperModule() + } + return swapperModule +} + +export const getRates = async (req: Request, res: Response): Promise => { + try { + // Parse and validate request + const parseResult = RatesRequestSchema.safeParse(req.query) + if (!parseResult.success) { + const errorResponse: ErrorResponse = { + error: 'Invalid request parameters', + details: parseResult.error.errors, + } + res.status(400).json(errorResponse) + return + } + + const { + sellAssetId, + buyAssetId, + sellAmountCryptoBaseUnit, + slippageTolerancePercentageDecimal, + allowMultiHop, + } = parseResult.data + + // Get assets + const sellAsset = getAsset(sellAssetId) + const buyAsset = getAsset(buyAssetId) + + if (!sellAsset) { + res.status(400).json({ error: `Unknown sell asset: ${sellAssetId}` } as ErrorResponse) + return + } + if (!buyAsset) { + res.status(400).json({ error: `Unknown buy asset: ${buyAssetId}` } as ErrorResponse) + return + } + + // Lazy load swapper module + let swapperModuleResult + try { + swapperModuleResult = await getSwapperModule() + } catch (error) { + console.error('Failed to load swapper module:', error) + res.status(503).json({ + error: 'Swap service temporarily unavailable', + details: 'Failed to initialize swapper module', + } as ErrorResponse) + return + } + const { getTradeRates, swappers, SwapperName } = swapperModuleResult + + // Create swapper dependencies + const deps = createServerSwapperDeps(getAssetsById()) + + // Build rate input + const rateInput = { + sellAsset, + buyAsset, + sellAmountIncludingProtocolFeesCryptoBaseUnit: sellAmountCryptoBaseUnit, + affiliateBps: DEFAULT_AFFILIATE_BPS, + allowMultiHop, + slippageTolerancePercentageDecimal, + receiveAddress: undefined, + sendAddress: undefined, + accountNumber: undefined, + quoteOrRate: 'rate' as const, + chainId: sellAsset.chainId, + } + + // Map string names to SwapperName enum + const enabledSwappers = ENABLED_SWAPPER_NAMES.map(name => { + const swapperName = Object.values(SwapperName).find(v => v === name) + return swapperName + }).filter((name): name is (typeof SwapperName)[keyof typeof SwapperName] => name !== undefined) + + // Fetch rates from all enabled swappers in parallel + const ratePromises = enabledSwappers.map(async (swapperName): Promise => { + try { + const swapper = swappers[swapperName] + if (!swapper) return null + + const result = await getTradeRates( + rateInput as GetTradeRateInput, + swapperName, + deps, + RATE_TIMEOUT_MS, + ) + + if (!result) return null + + if (result.isErr()) { + const error = result.unwrapErr() + return { + swapperName, + rate: '0', + buyAmountCryptoBaseUnit: '0', + sellAmountCryptoBaseUnit, + steps: 0, + estimatedExecutionTimeMs: undefined, + priceImpactPercentageDecimal: undefined, + affiliateBps: DEFAULT_AFFILIATE_BPS, + networkFeeCryptoBaseUnit: undefined, + error: { + code: error.code ?? TradeQuoteError.UnknownError, + message: error.message, + }, + } + } + + const rates = result.unwrap() + if (rates.length === 0) return null + + // Return the first/best rate + const rate = rates[0] + const firstStep = rate.steps[0] + + return { + swapperName, + rate: rate.rate, + buyAmountCryptoBaseUnit: firstStep.buyAmountAfterFeesCryptoBaseUnit, + sellAmountCryptoBaseUnit: firstStep.sellAmountIncludingProtocolFeesCryptoBaseUnit, + steps: rate.steps.length, + estimatedExecutionTimeMs: firstStep.estimatedExecutionTimeMs, + priceImpactPercentageDecimal: rate.priceImpactPercentageDecimal, + affiliateBps: rate.affiliateBps, + networkFeeCryptoBaseUnit: firstStep.feeData.networkFeeCryptoBaseUnit, + } + } catch (error) { + console.error(`Error fetching rate from ${swapperName}:`, error) + return null + } + }) + + const results = await Promise.allSettled(ratePromises) + const rates: ApiRate[] = results + .filter((r): r is PromiseFulfilledResult => r.status === 'fulfilled') + .map(r => r.value) + .filter((r): r is ApiRate => r !== null) + + // Sort by best rate (highest buy amount) + rates.sort((a, b) => { + if (a.error && !b.error) return 1 + if (!a.error && b.error) return -1 + try { + const aBuyAmount = BigInt(a.buyAmountCryptoBaseUnit.split('.')[0] ?? '0') + const bBuyAmount = BigInt(b.buyAmountCryptoBaseUnit.split('.')[0] ?? '0') + return bBuyAmount > aBuyAmount ? 1 : bBuyAmount < aBuyAmount ? -1 : 0 + } catch { + return 0 + } + }) + + const now = Date.now() + const response: RatesResponse = { + rates, + timestamp: now, + expiresAt: now + 30_000, // 30 second expiry + } + + res.json(response) + } catch (error) { + console.error('Error in getRates:', error) + res.status(500).json({ error: 'Internal server error' } as ErrorResponse) + } +} diff --git a/packages/public-api/src/server-standalone.ts b/packages/public-api/src/server-standalone.ts new file mode 100644 index 00000000000..b635106082d --- /dev/null +++ b/packages/public-api/src/server-standalone.ts @@ -0,0 +1,245 @@ +/** + * Standalone test server for the Public Swap API + * This version uses mocked responses to demonstrate the API structure + * without requiring full swapper package integration. + * + * For production, use index.ts which integrates with the real swapper package. + */ + +import cors from 'cors' +import crypto from 'crypto' +import express from 'express' +import { v4 as uuidv4 } from 'uuid' + +const API_PORT = parseInt(process.env.PORT || '3001', 10) +const API_HOST = process.env.HOST || '0.0.0.0' + +// Static API keys for testing +const STATIC_API_KEYS: Record = { + 'test-api-key-123': { name: 'Test Partner', feeSharePercentage: 50 }, +} + +const app = express() + +// Middleware +app.use(cors()) +app.use(express.json()) + +// API key auth middleware +const apiKeyAuth = (req: express.Request, res: express.Response, next: express.NextFunction) => { + const apiKey = req.header('X-API-Key') + + if (!apiKey) { + return res.status(401).json({ error: 'API key required', code: 'MISSING_API_KEY' }) + } + + const partnerInfo = STATIC_API_KEYS[apiKey] + if (!partnerInfo) { + return res.status(401).json({ error: 'Invalid API key', code: 'INVALID_API_KEY' }) + } + + ;(req as any).partner = { + id: crypto.createHash('sha256').update(apiKey).digest('hex').substring(0, 16), + name: partnerInfo.name, + feeSharePercentage: partnerInfo.feeSharePercentage, + } + + next() +} + +// Health check +app.get('/health', (_req, res) => { + res.json({ status: 'ok', timestamp: Date.now() }) +}) + +// Mock rates endpoint +app.get('/v1/swap/rates', apiKeyAuth, (req, res) => { + const { sellAssetId, buyAssetId, sellAmountCryptoBaseUnit } = req.query + + if (!sellAssetId || !buyAssetId || !sellAmountCryptoBaseUnit) { + return res.status(400).json({ + error: 'Missing required parameters: sellAssetId, buyAssetId, sellAmountCryptoBaseUnit', + }) + } + + // Mock response demonstrating API structure + const mockRates = [ + { + swapperName: 'THORChain', + rate: '3500.123456', + buyAmountCryptoBaseUnit: '3500123456', + sellAmountCryptoBaseUnit: sellAmountCryptoBaseUnit as string, + steps: 1, + estimatedExecutionTimeMs: 600000, + priceImpactPercentageDecimal: '0.001', + affiliateBps: '55', + networkFeeCryptoBaseUnit: '5000000000000000', + }, + { + swapperName: '0x', + rate: '3498.789012', + buyAmountCryptoBaseUnit: '3498789012', + sellAmountCryptoBaseUnit: sellAmountCryptoBaseUnit as string, + steps: 1, + estimatedExecutionTimeMs: 30000, + priceImpactPercentageDecimal: '0.002', + affiliateBps: '55', + networkFeeCryptoBaseUnit: '3000000000000000', + }, + { + swapperName: 'CoW Swap', + rate: '3499.567890', + buyAmountCryptoBaseUnit: '3499567890', + sellAmountCryptoBaseUnit: sellAmountCryptoBaseUnit as string, + steps: 1, + estimatedExecutionTimeMs: 120000, + priceImpactPercentageDecimal: '0.0015', + affiliateBps: '55', + networkFeeCryptoBaseUnit: '2000000000000000', + }, + ] + + const now = Date.now() + res.json({ + rates: mockRates, + timestamp: now, + expiresAt: now + 30000, + }) +}) + +// Mock quote endpoint +app.post('/v1/swap/quote', apiKeyAuth, (req, res) => { + const { sellAssetId, buyAssetId, sellAmountCryptoBaseUnit, receiveAddress, swapperName } = + req.body + + if (!sellAssetId || !buyAssetId || !sellAmountCryptoBaseUnit || !receiveAddress || !swapperName) { + return res.status(400).json({ + error: 'Missing required parameters', + }) + } + + const quoteId = uuidv4() + const now = Date.now() + + // Mock response with transaction data + res.json({ + quoteId, + swapperName, + rate: '3500.123456', + sellAsset: { + assetId: sellAssetId, + chainId: 'eip155:1', + symbol: 'ETH', + name: 'Ethereum', + precision: 18, + }, + buyAsset: { + assetId: buyAssetId, + chainId: 'eip155:1', + symbol: 'USDC', + name: 'USD Coin', + precision: 6, + }, + sellAmountCryptoBaseUnit, + buyAmountBeforeFeesCryptoBaseUnit: '3505000000', + buyAmountAfterFeesCryptoBaseUnit: '3500000000', + affiliateBps: '55', + slippageTolerancePercentageDecimal: '0.01', + networkFeeCryptoBaseUnit: '5000000000000000', + steps: [ + { + sellAsset: { assetId: sellAssetId, symbol: 'ETH', precision: 18 }, + buyAsset: { assetId: buyAssetId, symbol: 'USDC', precision: 6 }, + sellAmountCryptoBaseUnit, + buyAmountAfterFeesCryptoBaseUnit: '3500000000', + allowanceContract: '0x0000000000000000000000000000000000000000', + estimatedExecutionTimeMs: 600000, + source: swapperName, + transactionData: { + to: '0x1111111254EEB25477B68fb85Ed929f73A960582', + data: '0x12aa3caf000000000000000000000000...', // Truncated for example + value: sellAmountCryptoBaseUnit, + gasLimit: '250000', + }, + }, + ], + approval: { + isRequired: false, + spender: '0x0000000000000000000000000000000000000000', + }, + expiresAt: now + 60000, + }) +}) + +// Mock assets endpoint +app.get('/v1/assets', (_req, res) => { + res.json({ + assets: [ + { + assetId: 'eip155:1/slip44:60', + chainId: 'eip155:1', + symbol: 'ETH', + name: 'Ethereum', + precision: 18, + }, + { + assetId: 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + chainId: 'eip155:1', + symbol: 'USDC', + name: 'USD Coin', + precision: 6, + }, + { + assetId: 'bip122:000000000019d6689c085ae165831e93/slip44:0', + chainId: 'bip122:000000000019d6689c085ae165831e93', + symbol: 'BTC', + name: 'Bitcoin', + precision: 8, + }, + ], + timestamp: Date.now(), + }) +}) + +// 404 handler +app.use((_req, res) => { + res.status(404).json({ error: 'Not found' }) +}) + +// Start server +app.listen(API_PORT, API_HOST, () => { + console.log(` +╔══════════════════════════════════════════════════════════════════╗ +║ ShapeShift Public Swap API ║ +║ (Mock/Test Server) ║ +╚══════════════════════════════════════════════════════════════════╝ + +Server running at http://${API_HOST}:${API_PORT} + +Endpoints: + GET /health - Health check (no auth) + GET /v1/swap/rates - Get swap rates from all swappers + POST /v1/swap/quote - Get executable quote with tx data + GET /v1/assets - List supported assets + +Authentication: + Include 'X-API-Key' header for /v1/swap/* endpoints + Test key: test-api-key-123 + +Example requests: + + # Health check + curl http://localhost:${API_PORT}/health + + # Get rates + curl -H "X-API-Key: test-api-key-123" \\ + "http://localhost:${API_PORT}/v1/swap/rates?sellAssetId=eip155:1/slip44:60&buyAssetId=eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&sellAmountCryptoBaseUnit=1000000000000000000" + + # Get quote + curl -X POST -H "X-API-Key: test-api-key-123" -H "Content-Type: application/json" \\ + -d '{"sellAssetId":"eip155:1/slip44:60","buyAssetId":"eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","sellAmountCryptoBaseUnit":"1000000000000000000","receiveAddress":"0x742d35Cc6634C0532925a3b844Bc9e7595f4EdC3","swapperName":"THORChain"}' \\ + http://localhost:${API_PORT}/v1/swap/quote + +Note: This is a mock server for testing. Real integration uses index.ts. +`) +}) diff --git a/packages/public-api/src/setupZod.ts b/packages/public-api/src/setupZod.ts new file mode 100644 index 00000000000..ecef04a8792 --- /dev/null +++ b/packages/public-api/src/setupZod.ts @@ -0,0 +1,4 @@ +import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi' +import { z } from 'zod' + +extendZodWithOpenApi(z) diff --git a/packages/public-api/src/swapperDeps.ts b/packages/public-api/src/swapperDeps.ts new file mode 100644 index 00000000000..3545efe0f6a --- /dev/null +++ b/packages/public-api/src/swapperDeps.ts @@ -0,0 +1,280 @@ +import type { ChainId } from '@shapeshiftoss/caip' +import type { + ChainAdapter, + CosmosSdkChainAdapter, + EvmChainAdapter, + near, + solana, + starknet, + sui, + ton, + tron, + UtxoChainAdapter, +} from '@shapeshiftoss/chain-adapters' +import type { SwapperDeps } from '@shapeshiftoss/swapper' +import type { AssetsByIdPartial } from '@shapeshiftoss/types' +import { KnownChainIds } from '@shapeshiftoss/types' + +import { getServerConfig } from './config' + +type GasFeeData = { + gasPrice: string + maxFeePerGas?: string + maxPriorityFeePerGas?: string +} + +type GasFeeDataEstimate = { + fast: GasFeeData + average: GasFeeData + slow: GasFeeData +} + +type UtxoNetworkFees = { + fast: { satsPerKiloByte: number } + average: { satsPerKiloByte: number } + slow: { satsPerKiloByte: number } +} + +const getEvmUnchainedUrls = (): Record => { + const config = getServerConfig() + return { + [KnownChainIds.EthereumMainnet]: config.VITE_UNCHAINED_ETHEREUM_HTTP_URL, + [KnownChainIds.ArbitrumMainnet]: + process.env.UNCHAINED_ARBITRUM_HTTP_URL || 'https://api.arbitrum.shapeshift.com', + [KnownChainIds.OptimismMainnet]: + process.env.UNCHAINED_OPTIMISM_HTTP_URL || 'https://api.optimism.shapeshift.com', + [KnownChainIds.PolygonMainnet]: + process.env.UNCHAINED_POLYGON_HTTP_URL || 'https://api.polygon.shapeshift.com', + [KnownChainIds.GnosisMainnet]: + process.env.UNCHAINED_GNOSIS_HTTP_URL || 'https://api.gnosis.shapeshift.com', + [KnownChainIds.AvalancheMainnet]: config.VITE_UNCHAINED_AVALANCHE_HTTP_URL, + [KnownChainIds.BnbSmartChainMainnet]: config.VITE_UNCHAINED_BNBSMARTCHAIN_HTTP_URL, + [KnownChainIds.BaseMainnet]: config.VITE_UNCHAINED_BASE_HTTP_URL, + [KnownChainIds.ArbitrumNovaMainnet]: + process.env.UNCHAINED_ARBITRUM_NOVA_HTTP_URL || 'https://api.arbitrum-nova.shapeshift.com', + } +} + +const getUtxoUnchainedUrls = (): Record => { + const config = getServerConfig() + return { + [KnownChainIds.BitcoinMainnet]: config.VITE_UNCHAINED_BITCOIN_HTTP_URL, + [KnownChainIds.DogecoinMainnet]: config.VITE_UNCHAINED_DOGECOIN_HTTP_URL, + [KnownChainIds.LitecoinMainnet]: config.VITE_UNCHAINED_LITECOIN_HTTP_URL, + [KnownChainIds.BitcoinCashMainnet]: config.VITE_UNCHAINED_BITCOINCASH_HTTP_URL, + } +} + +const GAS_FEES_TIMEOUT_MS = 10_000 + +const fetchGasFees = async (unchainedUrl: string): Promise => { + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), GAS_FEES_TIMEOUT_MS) + + try { + const response = await fetch(`${unchainedUrl}/api/v1/gas/fees`, { + signal: controller.signal, + }) + if (!response.ok) { + throw new Error(`Failed to fetch gas fees: ${response.statusText}`) + } + const data = (await response.json()) as GasFeeDataEstimate + return { + fast: { + gasPrice: data.fast.gasPrice, + maxFeePerGas: data.fast.maxFeePerGas, + maxPriorityFeePerGas: data.fast.maxPriorityFeePerGas, + }, + average: { + gasPrice: data.average.gasPrice, + maxFeePerGas: data.average.maxFeePerGas, + maxPriorityFeePerGas: data.average.maxPriorityFeePerGas, + }, + slow: { + gasPrice: data.slow.gasPrice, + maxFeePerGas: data.slow.maxFeePerGas, + maxPriorityFeePerGas: data.slow.maxPriorityFeePerGas, + }, + } + } finally { + clearTimeout(timeout) + } +} + +const createMinimalEvmAdapter = (chainId: ChainId) => { + const evmUnchainedUrls = getEvmUnchainedUrls() + const unchainedUrl = evmUnchainedUrls[chainId] + if (!unchainedUrl) { + throw new Error(`No Unchained URL configured for chain ${chainId}`) + } + + return { + getChainId: () => chainId, + getGasFeeData: () => fetchGasFees(unchainedUrl), + getFeeAssetId: () => { + switch (chainId) { + case KnownChainIds.EthereumMainnet: + return 'eip155:1/slip44:60' + case KnownChainIds.ArbitrumMainnet: + return 'eip155:42161/slip44:60' + case KnownChainIds.OptimismMainnet: + return 'eip155:10/slip44:60' + case KnownChainIds.PolygonMainnet: + return 'eip155:137/slip44:966' + case KnownChainIds.GnosisMainnet: + return 'eip155:100/slip44:700' + case KnownChainIds.AvalancheMainnet: + return 'eip155:43114/slip44:9005' + case KnownChainIds.BnbSmartChainMainnet: + return 'eip155:56/slip44:714' + case KnownChainIds.BaseMainnet: + return 'eip155:8453/slip44:60' + case KnownChainIds.ArbitrumNovaMainnet: + return 'eip155:42170/slip44:60' + default: + return `${chainId}/slip44:60` + } + }, + getDisplayName: () => chainId, + } +} + +const createMinimalUtxoAdapter = (chainId: ChainId) => { + const utxoUnchainedUrls = getUtxoUnchainedUrls() + const unchainedUrl = utxoUnchainedUrls[chainId] + if (!unchainedUrl) { + throw new Error(`No Unchained URL configured for UTXO chain ${chainId}`) + } + + const getFeeAssetId = () => { + switch (chainId) { + case KnownChainIds.BitcoinMainnet: + return 'bip122:000000000019d6689c085ae165831e93/slip44:0' + case KnownChainIds.DogecoinMainnet: + return 'bip122:00000000001a91e3dace36e2be3bf030/slip44:3' + case KnownChainIds.LitecoinMainnet: + return 'bip122:12a765e31ffd4059bada1e25190f6e98/slip44:2' + case KnownChainIds.BitcoinCashMainnet: + return 'bip122:000000000000000000651ef99cb9fcbe/slip44:145' + default: + throw new Error(`Unknown UTXO chain ${chainId}`) + } + } + + const getDisplayName = () => { + switch (chainId) { + case KnownChainIds.BitcoinMainnet: + return 'Bitcoin' + case KnownChainIds.DogecoinMainnet: + return 'Dogecoin' + case KnownChainIds.LitecoinMainnet: + return 'Litecoin' + case KnownChainIds.BitcoinCashMainnet: + return 'Bitcoin Cash' + default: + return chainId + } + } + + return { + getChainId: () => chainId, + getFeeAssetId, + getDisplayName, + getFeeData: async (_input: { + to: string + value: string + chainSpecific: { pubkey: string; opReturnData?: string } + }) => { + const response = await fetch(`${unchainedUrl}/api/v1/fees`) + if (!response.ok) { + throw new Error(`Failed to fetch UTXO fees: ${response.statusText}`) + } + const networkFees = (await response.json()) as UtxoNetworkFees + + const fastPerByte = String(Math.round(networkFees.fast.satsPerKiloByte / 1000)) + const averagePerByte = String(Math.round(networkFees.average.satsPerKiloByte / 1000)) + const slowPerByte = String(Math.round(networkFees.slow.satsPerKiloByte / 1000)) + + const ESTIMATED_TX_SIZE = 250 + const fastFee = String(parseInt(fastPerByte) * ESTIMATED_TX_SIZE) + const averageFee = String(parseInt(averagePerByte) * ESTIMATED_TX_SIZE) + const slowFee = String(parseInt(slowPerByte) * ESTIMATED_TX_SIZE) + + return { + fast: { + txFee: fastFee, + chainSpecific: { satoshiPerByte: fastPerByte }, + }, + average: { + txFee: averageFee, + chainSpecific: { satoshiPerByte: averagePerByte }, + }, + slow: { + txFee: slowFee, + chainSpecific: { satoshiPerByte: slowPerByte }, + }, + } + }, + } +} + +const createStubAdapter = (type: string) => { + return () => { + throw new Error( + `Chain adapter ${type} not implemented in public API. ` + + `This swapper requires chain adapter functionality that is not yet available.`, + ) + } +} + +export const createServerSwapperDeps = (assetsById: AssetsByIdPartial): SwapperDeps => ({ + assetsById, + config: getServerConfig(), + mixPanel: undefined, + + assertGetChainAdapter: createStubAdapter('generic') as unknown as ( + chainId: ChainId, + ) => ChainAdapter, + + assertGetEvmChainAdapter: ((chainId: ChainId) => { + const evmUnchainedUrls = getEvmUnchainedUrls() + const unchainedUrl = evmUnchainedUrls[chainId] + if (unchainedUrl) { + return createMinimalEvmAdapter(chainId) + } + throw new Error(`Chain adapter EVM for ${chainId} not implemented in public API.`) + }) as unknown as (chainId: ChainId) => EvmChainAdapter, + + assertGetUtxoChainAdapter: ((chainId: ChainId) => { + const utxoUnchainedUrls = getUtxoUnchainedUrls() + const unchainedUrl = utxoUnchainedUrls[chainId] + if (unchainedUrl) { + return createMinimalUtxoAdapter(chainId) + } + throw new Error(`Chain adapter UTXO for ${chainId} not implemented in public API.`) + }) as unknown as (chainId: ChainId) => UtxoChainAdapter, + + assertGetCosmosSdkChainAdapter: createStubAdapter('CosmosSdk') as unknown as ( + chainId: ChainId, + ) => CosmosSdkChainAdapter, + assertGetSolanaChainAdapter: createStubAdapter('Solana') as unknown as ( + chainId: ChainId, + ) => solana.ChainAdapter, + assertGetTronChainAdapter: createStubAdapter('Tron') as unknown as ( + chainId: ChainId, + ) => tron.ChainAdapter, + assertGetSuiChainAdapter: createStubAdapter('Sui') as unknown as ( + chainId: ChainId, + ) => sui.ChainAdapter, + assertGetNearChainAdapter: createStubAdapter('Near') as unknown as ( + chainId: ChainId, + ) => near.ChainAdapter, + assertGetStarknetChainAdapter: createStubAdapter('Starknet') as unknown as ( + chainId: ChainId, + ) => starknet.ChainAdapter, + assertGetTonChainAdapter: createStubAdapter('Ton') as unknown as ( + chainId: ChainId, + ) => ton.ChainAdapter, + + fetchIsSmartContractAddressQuery: () => Promise.resolve(false), +}) diff --git a/packages/public-api/src/types.ts b/packages/public-api/src/types.ts new file mode 100644 index 00000000000..edcdbde5a61 --- /dev/null +++ b/packages/public-api/src/types.ts @@ -0,0 +1,138 @@ +import type { AssetId, ChainId } from '@shapeshiftoss/caip' +import type { SwapperName, TradeQuote, TradeQuoteError, TradeRate } from '@shapeshiftoss/swapper' +import type { Asset } from '@shapeshiftoss/types' + +// Partner configuration +export type PartnerConfig = { + id: string + apiKeyHash: string + name: string + feeSharePercentage: number + status: 'active' | 'suspended' | 'pending' + rateLimit: { + requestsPerMinute: number + requestsPerDay: number + } + createdAt: Date +} + +// API Request Types +export type RatesRequest = { + sellAssetId: AssetId + buyAssetId: AssetId + sellAmountCryptoBaseUnit: string + slippageTolerancePercentageDecimal?: string + allowMultiHop?: boolean +} + +export type QuoteRequest = { + sellAssetId: AssetId + buyAssetId: AssetId + sellAmountCryptoBaseUnit: string + receiveAddress: string + sendAddress?: string + swapperName: SwapperName + slippageTolerancePercentageDecimal?: string + allowMultiHop?: boolean + accountNumber?: number +} + +export type StatusRequest = { + txHash: string + chainId: ChainId + swapperName: SwapperName +} + +// API Response Types +export type ApiRate = { + swapperName: SwapperName + rate: string + buyAmountCryptoBaseUnit: string + sellAmountCryptoBaseUnit: string + steps: number + estimatedExecutionTimeMs: number | undefined + priceImpactPercentageDecimal: string | undefined + affiliateBps: string + networkFeeCryptoBaseUnit: string | undefined + error?: { + code: TradeQuoteError + message: string + } +} + +export type RatesResponse = { + rates: ApiRate[] + timestamp: number + expiresAt: number +} + +export type ApiQuoteStep = { + sellAsset: Asset + buyAsset: Asset + sellAmountCryptoBaseUnit: string + buyAmountAfterFeesCryptoBaseUnit: string + allowanceContract: string + estimatedExecutionTimeMs: number | undefined + source: string + transactionData?: { + to: string + data: string + value: string + gasLimit?: string + } +} + +export type ApprovalInfo = { + isRequired: boolean + spender: string + approvalTx?: { + to: string + data: string + value: string + } +} + +export type QuoteResponse = { + quoteId: string + swapperName: SwapperName + rate: string + sellAsset: Asset + buyAsset: Asset + sellAmountCryptoBaseUnit: string + buyAmountBeforeFeesCryptoBaseUnit: string + buyAmountAfterFeesCryptoBaseUnit: string + affiliateBps: string + slippageTolerancePercentageDecimal: string | undefined + networkFeeCryptoBaseUnit: string | undefined + steps: ApiQuoteStep[] + approval: ApprovalInfo + expiresAt: number + quote: TradeQuote | TradeRate +} + +export type StatusResponse = { + status: 'pending' | 'confirmed' | 'failed' | 'unknown' + sellTxHash: string + buyTxHash?: string + message?: string +} + +export type AssetsResponse = { + assets: Asset[] + timestamp: number +} + +export type ErrorResponse = { + error: string + code?: string + details?: unknown +} + +// Extend Express Request to include partner info +declare global { + namespace Express { + interface Request { + partner?: PartnerConfig + } + } +} diff --git a/packages/public-api/tests/run-smoke-tests.ts b/packages/public-api/tests/run-smoke-tests.ts new file mode 100644 index 00000000000..7510bad22ad --- /dev/null +++ b/packages/public-api/tests/run-smoke-tests.ts @@ -0,0 +1,76 @@ +import { runSmokeTests } from './smoke-tests' +import { sleep } from './test-utils' + +const main = async (): Promise => { + console.log('='.repeat(60)) + console.log('ShapeShift Public API - Smoke Tests') + console.log('='.repeat(60)) + + const apiUrl = process.env.API_URL || 'http://localhost:3001' + console.log(`Testing API at: ${apiUrl}`) + console.log('') + + // Wait for API to be ready (Railway may still be spinning up) + const maxRetries = 10 + const retryDelay = 3000 + + for (let i = 0; i < maxRetries; i++) { + try { + const res = await fetch(`${apiUrl}/health`) + if (res.ok) { + console.log('API is ready!') + break + } + } catch { + // Ignore - API not ready yet + } + + if (i < maxRetries - 1) { + console.log(`Waiting for API to be ready... (${i + 1}/${maxRetries})`) + await sleep(retryDelay) + } else { + console.error('API did not become ready in time') + // Still exit 0 to not block deployment + process.exit(0) + } + } + + console.log('') + const results = await runSmokeTests() + + // Print results + console.log('\nTest Results:') + console.log('-'.repeat(60)) + + for (const result of results.results) { + const status = result.passed ? 'PASS' : result.critical ? 'FAIL (CRITICAL)' : 'FAIL (warning)' + const icon = result.passed ? '[OK]' : '[!!]' + console.log(`${icon} ${result.name}: ${status} (${result.duration}ms)`) + if (result.error) { + console.log(` Error: ${result.error}`) + } + } + + console.log('-'.repeat(60)) + console.log( + `Total: ${results.totalTests} | Passed: ${results.passed} | Failed: ${results.failed}`, + ) + + if (results.criticalFailures > 0) { + console.log(`\n[WARNING] ${results.criticalFailures} CRITICAL test(s) failed!`) + console.log('The API may not be functioning correctly.') + } + + // Output JSON for programmatic consumption + console.log('\n--- JSON Results ---') + console.log(JSON.stringify(results, null, 2)) + + // Always exit 0 to not block deployment + // Critical failures are logged but don't fail the deploy + process.exit(0) +} + +main().catch(err => { + console.error('Smoke test runner error:', err) + process.exit(0) // Still exit 0 to not block +}) diff --git a/packages/public-api/tests/smoke-tests.ts b/packages/public-api/tests/smoke-tests.ts new file mode 100644 index 00000000000..caa752fd85b --- /dev/null +++ b/packages/public-api/tests/smoke-tests.ts @@ -0,0 +1,203 @@ +import { ASSET_IDS, TEST_API_KEY, TEST_PAIRS } from './test-config' +import type { TestResult, TestSuiteResult } from './test-utils' +import { fetchWithTimeout, runTest } from './test-utils' + +const getApiUrl = (): string => process.env.API_URL || 'http://localhost:3001' + +export const runSmokeTests = async (): Promise => { + const API_URL = getApiUrl() + const results: TestResult[] = [] + + // 1. Health Check (Critical) + results.push( + await runTest('Health check', true, async () => { + const res = await fetchWithTimeout(`${API_URL}/health`, {}) + if (!res.ok) throw new Error(`Health check failed: ${res.status}`) + const data = (await res.json()) as { status?: string } + if (data.status !== 'ok') throw new Error('Health status not ok') + }), + ) + + // 2. Asset Count (Critical) + results.push( + await runTest('Asset count', true, async () => { + const res = await fetchWithTimeout(`${API_URL}/v1/assets/count`, {}) + if (!res.ok) throw new Error(`Asset count failed: ${res.status}`) + const data = (await res.json()) as { count?: number } + if (typeof data.count !== 'number' || data.count === 0) { + throw new Error(`Invalid asset count: ${data.count}`) + } + }), + ) + + // 3. Asset List (Critical) + results.push( + await runTest('Asset list', true, async () => { + const res = await fetchWithTimeout(`${API_URL}/v1/assets?limit=10`, {}) + if (!res.ok) throw new Error(`Asset list failed: ${res.status}`) + const data = (await res.json()) as { assets?: unknown[] } + if (!Array.isArray(data.assets) || data.assets.length === 0) { + throw new Error('No assets returned') + } + }), + ) + + // 4. Single Asset Lookup (Critical) + results.push( + await runTest('Single asset lookup (ETH)', true, async () => { + const assetId = encodeURIComponent(ASSET_IDS.ETH) + const res = await fetchWithTimeout(`${API_URL}/v1/assets/${assetId}`, {}) + if (!res.ok) throw new Error(`Asset lookup failed: ${res.status}`) + const asset = (await res.json()) as { chainId?: string; symbol?: string } + if (!asset.chainId || !asset.symbol) { + throw new Error('Invalid asset structure') + } + }), + ) + + // 5. Rates Auth Check (Critical) + results.push( + await runTest('Rates requires auth', true, async () => { + const params = new URLSearchParams({ + sellAssetId: ASSET_IDS.ETH, + buyAssetId: ASSET_IDS.USDC_ETH, + sellAmountCryptoBaseUnit: '100000000000000000', + }) + const res = await fetchWithTimeout(`${API_URL}/v1/swap/rates?${params}`, {}) + if (res.status !== 401) throw new Error(`Expected 401, got ${res.status}`) + }), + ) + + // 6. Quote Auth Check (Critical) + results.push( + await runTest('Quote requires auth', true, async () => { + const res = await fetchWithTimeout(`${API_URL}/v1/swap/quote`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sellAssetId: ASSET_IDS.ETH, + buyAssetId: ASSET_IDS.USDC_ETH, + sellAmountCryptoBaseUnit: '100000000000000000', + receiveAddress: '0x0000000000000000000000000000000000000000', + swapperName: '0x', + }), + }) + if (res.status !== 401) throw new Error(`Expected 401, got ${res.status}`) + }), + ) + + // 7. EVM Same-Chain Rates (Informative) + const evmPair = TEST_PAIRS.evmSameChain[0] + results.push( + await runTest(`Rates: ${evmPair.name}`, false, async () => { + const params = new URLSearchParams({ + sellAssetId: evmPair.sellAssetId, + buyAssetId: evmPair.buyAssetId, + sellAmountCryptoBaseUnit: evmPair.sellAmountCryptoBaseUnit, + }) + const res = await fetchWithTimeout( + `${API_URL}/v1/swap/rates?${params}`, + { + headers: { 'X-API-Key': TEST_API_KEY }, + }, + 30000, + ) // 30s timeout for rates + + if (!res.ok) throw new Error(`Rates request failed: ${res.status}`) + const data = (await res.json()) as { + rates?: { + swapperName: string + error?: unknown + buyAmountCryptoBaseUnit?: string + }[] + } + if (!Array.isArray(data.rates)) throw new Error('Invalid rates response structure') + + const validRates = data.rates.filter( + r => !r.error && r.buyAmountCryptoBaseUnit && r.buyAmountCryptoBaseUnit !== '0', + ) + if (validRates.length === 0) { + const swappersWithErrors = data.rates + .filter(r => r.error) + .map(r => r.swapperName) + .join(', ') + console.warn( + `Warning: No valid rates returned for ${evmPair.name}. Swappers with errors: ${ + swappersWithErrors || 'none' + }`, + ) + } else { + console.log( + ` Found ${validRates.length} valid rate(s) from: ${validRates + .map(r => r.swapperName) + .join(', ')}`, + ) + } + }), + ) + + // 8. Cross-Chain Rates (Informative) + const crossChainPair = TEST_PAIRS.crossChain[0] + results.push( + await runTest(`Rates: ${crossChainPair.name}`, false, async () => { + const params = new URLSearchParams({ + sellAssetId: crossChainPair.sellAssetId, + buyAssetId: crossChainPair.buyAssetId, + sellAmountCryptoBaseUnit: crossChainPair.sellAmountCryptoBaseUnit, + }) + const res = await fetchWithTimeout( + `${API_URL}/v1/swap/rates?${params}`, + { + headers: { 'X-API-Key': TEST_API_KEY }, + }, + 30000, + ) + + if (!res.ok) throw new Error(`Rates request failed: ${res.status}`) + const data = (await res.json()) as { + rates?: { + swapperName: string + error?: unknown + buyAmountCryptoBaseUnit?: string + }[] + } + if (!Array.isArray(data.rates)) throw new Error('Invalid rates response structure') + + const validRates = data.rates.filter( + r => !r.error && r.buyAmountCryptoBaseUnit && r.buyAmountCryptoBaseUnit !== '0', + ) + if (validRates.length === 0) { + const swappersWithErrors = data.rates + .filter(r => r.error) + .map(r => r.swapperName) + .join(', ') + console.warn( + `Warning: No valid rates returned for ${crossChainPair.name}. Swappers with errors: ${ + swappersWithErrors || 'none' + }`, + ) + } else { + console.log( + ` Found ${validRates.length} valid rate(s) from: ${validRates + .map(r => r.swapperName) + .join(', ')}`, + ) + } + }), + ) + + // Calculate summary + const passed = results.filter(r => r.passed).length + const failed = results.filter(r => !r.passed).length + const criticalFailures = results.filter(r => !r.passed && r.critical).length + + return { + timestamp: Date.now(), + apiUrl: API_URL, + totalTests: results.length, + passed, + failed, + criticalFailures, + results, + } +} diff --git a/packages/public-api/tests/test-config.ts b/packages/public-api/tests/test-config.ts new file mode 100644 index 00000000000..7ce270ba947 --- /dev/null +++ b/packages/public-api/tests/test-config.ts @@ -0,0 +1,62 @@ +export const TEST_API_KEY = 'test-api-key-123' + +export const ASSET_IDS = { + // EVM Native Assets + ETH: 'eip155:1/slip44:60', + ARB_ETH: 'eip155:42161/slip44:60', + BASE_ETH: 'eip155:8453/slip44:60', + + // EVM ERC20 Tokens + USDC_ETH: 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + USDC_ARB: 'eip155:42161/erc20:0xaf88d065e77c8cc2239327c5edb3a432268e5831', + FOX_ETH: 'eip155:1/erc20:0xc770eefad204b5180df6a14ee197d99d808ee52d', + + // UTXO + BTC: 'bip122:000000000019d6689c085ae165831e93/slip44:0', + DOGE: 'bip122:00000000001a91e3dace36e2be3bf030/slip44:3', + + // Cosmos + ATOM: 'cosmos:cosmoshub-4/slip44:118', + RUNE: 'cosmos:thorchain-1/slip44:931', +} as const + +export type TestPair = { + name: string + sellAssetId: string + buyAssetId: string + sellAmountCryptoBaseUnit: string + expectedSwappers: string[] +} + +export const TEST_PAIRS: Record = { + evmSameChain: [ + { + name: 'ETH to USDC (Ethereum)', + sellAssetId: ASSET_IDS.ETH, + buyAssetId: ASSET_IDS.USDC_ETH, + sellAmountCryptoBaseUnit: '100000000000000000', // 0.1 ETH + expectedSwappers: ['0x', 'CoW Swap', 'Portals', 'Bebop', 'ButterSwap'], + }, + ], + crossChain: [ + { + name: 'ETH to BTC (THORChain/Chainflip)', + sellAssetId: ASSET_IDS.ETH, + buyAssetId: ASSET_IDS.BTC, + sellAmountCryptoBaseUnit: '100000000000000000', // 0.1 ETH + expectedSwappers: ['THORChain', 'Chainflip', 'Relay'], + }, + ], +} + +export const CRITICAL_SWAPPERS = ['THORChain', '0x'] +export const NON_CRITICAL_SWAPPERS = [ + 'CoW Swap', + 'Portals', + 'Bebop', + 'ButterSwap', + 'Jupiter', + 'Chainflip', + 'MAYAChain', + 'Relay', +] diff --git a/packages/public-api/tests/test-utils.ts b/packages/public-api/tests/test-utils.ts new file mode 100644 index 00000000000..cb207104c85 --- /dev/null +++ b/packages/public-api/tests/test-utils.ts @@ -0,0 +1,55 @@ +export type TestResult = { + name: string + passed: boolean + critical: boolean + duration: number + error?: string + details?: unknown +} + +export type TestSuiteResult = { + timestamp: number + apiUrl: string + totalTests: number + passed: number + failed: number + criticalFailures: number + results: TestResult[] +} + +export const fetchWithTimeout = async ( + url: string, + options: RequestInit = {}, + timeoutMs = 15000, +): Promise => { + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), timeoutMs) + + try { + return await fetch(url, { ...options, signal: controller.signal }) + } finally { + clearTimeout(timeout) + } +} + +export const runTest = async ( + name: string, + critical: boolean, + testFn: () => Promise, +): Promise => { + const start = Date.now() + try { + await testFn() + return { name, passed: true, critical, duration: Date.now() - start } + } catch (error) { + return { + name, + passed: false, + critical, + duration: Date.now() - start, + error: error instanceof Error ? error.message : String(error), + } + } +} + +export const sleep = (ms: number): Promise => new Promise(r => setTimeout(r, ms)) diff --git a/packages/public-api/tsconfig.json b/packages/public-api/tsconfig.json new file mode 100644 index 00000000000..34e59d9b831 --- /dev/null +++ b/packages/public-api/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ES2022", + "esModuleInterop": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "lib": ["ES2022"], + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/swap-widget/railway.toml b/packages/swap-widget/railway.toml new file mode 100644 index 00000000000..a2c100bbd71 --- /dev/null +++ b/packages/swap-widget/railway.toml @@ -0,0 +1,10 @@ +[build] +builder = "dockerfile" +dockerfilePath = "packages/swap-widget/Dockerfile" +watchPatterns = [ + "packages/swap-widget/**" +] + +[deploy] +restartPolicyType = "on_failure" +restartPolicyMaxRetries = 10 diff --git a/packages/swapper/package.json b/packages/swapper/package.json index 8a3c8ca608b..244386731ea 100644 --- a/packages/swapper/package.json +++ b/packages/swapper/package.json @@ -36,6 +36,7 @@ "@cowprotocol/app-data": "^2.3.0", "@defuse-protocol/one-click-sdk-typescript": "^0.1.1-0.2", "@jup-ag/api": "^6.0.30", + "@mysten/sui": "^1.45.2", "@shapeshiftoss/bitcoinjs-lib": "7.0.0-shapeshift.0", "@shapeshiftoss/caip": "workspace:^", "@shapeshiftoss/chain-adapters": "workspace:^", diff --git a/packages/swapper/src/swappers/CetusSwapper/endpoints.ts b/packages/swapper/src/swappers/CetusSwapper/endpoints.ts index 74abaa62c16..3a5c2875d0d 100644 --- a/packages/swapper/src/swappers/CetusSwapper/endpoints.ts +++ b/packages/swapper/src/swappers/CetusSwapper/endpoints.ts @@ -1,5 +1,5 @@ import { getProvidersExcluding } from '@cetusprotocol/aggregator-sdk' -import { Transaction } from '@cetusprotocol/aggregator-sdk/node_modules/@mysten/sui/transactions' +import { Transaction } from '@mysten/sui/transactions' import { TxStatus } from '@shapeshiftoss/unchained-client' import { bnOrZero } from '@shapeshiftoss/utils' import type { Result } from '@sniptt/monads' diff --git a/packages/swapper/src/swappers/CetusSwapper/swapperApi/getTradeQuote.ts b/packages/swapper/src/swappers/CetusSwapper/swapperApi/getTradeQuote.ts index 6841ae00fc5..44957f76228 100644 --- a/packages/swapper/src/swappers/CetusSwapper/swapperApi/getTradeQuote.ts +++ b/packages/swapper/src/swappers/CetusSwapper/swapperApi/getTradeQuote.ts @@ -1,4 +1,4 @@ -import { Transaction } from '@cetusprotocol/aggregator-sdk/node_modules/@mysten/sui/transactions' +import { Transaction } from '@mysten/sui/transactions' import { bnOrZero } from '@shapeshiftoss/utils' import type { Result } from '@sniptt/monads' import { Err } from '@sniptt/monads' diff --git a/packages/swapper/src/swappers/CetusSwapper/swapperApi/getTradeRate.ts b/packages/swapper/src/swappers/CetusSwapper/swapperApi/getTradeRate.ts index 82aa09f4978..7cc7e0025fa 100644 --- a/packages/swapper/src/swappers/CetusSwapper/swapperApi/getTradeRate.ts +++ b/packages/swapper/src/swappers/CetusSwapper/swapperApi/getTradeRate.ts @@ -1,4 +1,4 @@ -import { Transaction } from '@cetusprotocol/aggregator-sdk/node_modules/@mysten/sui/transactions' +import { Transaction } from '@mysten/sui/transactions' import { bnOrZero } from '@shapeshiftoss/utils' import type { Result } from '@sniptt/monads' import { Err } from '@sniptt/monads' diff --git a/packages/swapper/src/swappers/ChainflipSwapper/utils/getQuoteOrRate.ts b/packages/swapper/src/swappers/ChainflipSwapper/utils/getQuoteOrRate.ts index 94e40bc4118..1d1f015d68c 100644 --- a/packages/swapper/src/swappers/ChainflipSwapper/utils/getQuoteOrRate.ts +++ b/packages/swapper/src/swappers/ChainflipSwapper/utils/getQuoteOrRate.ts @@ -127,16 +127,16 @@ export const getQuoteOrRate = async ( if (maybeQuoteResponse.isErr()) { const error = maybeQuoteResponse.unwrapErr() - const cause = error.cause as AxiosError + const cause = error.cause as AxiosError | undefined if ( - cause.message.includes('code 400') && - cause.response?.data.detail.includes('Amount outside asset bounds') + cause?.message?.includes('code 400') && + cause?.response?.data?.detail?.includes('Amount outside asset bounds') ) { return Err( createTradeAmountTooSmallErr({ assetId: sellAsset.assetId, - minAmountCryptoBaseUnit: cause.response?.data.errors.minimalAmountNative[0], + minAmountCryptoBaseUnit: cause.response?.data?.errors?.minimalAmountNative?.[0], }), ) } diff --git a/packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeRate/getPortalsTradeRate.tsx b/packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeRate/getPortalsTradeRate.tsx index 34ed9d53d21..7982e020512 100644 --- a/packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeRate/getPortalsTradeRate.tsx +++ b/packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeRate/getPortalsTradeRate.tsx @@ -41,7 +41,6 @@ export async function getPortalsTradeRate( receiveAddress, supportsEIP1559, } = input - const adapter = assertGetEvmChainAdapter(chainId) const sellAssetChainId = sellAsset.chainId const buyAssetChainId = buyAsset.chainId @@ -66,11 +65,20 @@ export async function getPortalsTradeRate( ) } + if (!isSupportedChainId(chainId)) { + return Err( + makeSwapErrorRight({ + message: `unsupported chainId`, + code: TradeQuoteError.UnsupportedChain, + details: { chainId }, + }), + ) + } + + const adapter = assertGetEvmChainAdapter(chainId) const isCrossChain = sellAssetChainId !== buyAssetChainId try { - if (!isSupportedChainId(chainId)) throw new Error(`Unsupported chainId ${sellAsset.chainId}`) - const sellPortalsNetwork = chainIdToPortalsNetwork[sellAssetChainId as KnownChainIds] const buyPortalsNetwork = chainIdToPortalsNetwork[buyAssetChainId as KnownChainIds] diff --git a/packages/unchained-client/generator/post_process.sh b/packages/unchained-client/generator/post_process.sh index e292c9080ad..67e53a0a7d3 100755 --- a/packages/unchained-client/generator/post_process.sh +++ b/packages/unchained-client/generator/post_process.sh @@ -1,3 +1,5 @@ +#!/bin/sh + # post process script to add // @ts-nocheck to the start of each generated file # there is a PR active to bake this into the generator that we can upgrade to when released: # https://github.com/OpenAPITools/openapi-generator/pull/11674 @@ -10,4 +12,4 @@ if [ "$(uname)" != "Darwin" ]; then else sed -i '' "$SED_COMMAND_1" $1 sed -i '' "$SED_COMMAND_2" $1 -fi \ No newline at end of file +fi diff --git a/packages/unchained-client/package.json b/packages/unchained-client/package.json index eaf3d095e72..f4b80194502 100644 --- a/packages/unchained-client/package.json +++ b/packages/unchained-client/package.json @@ -22,7 +22,9 @@ }, "scripts": { "build": "yarn clean && yarn generate && yarn run -T tsc --build && yarn postbuild", + "build:docker": "yarn clean:dist && yarn run -T tsc --build && yarn postbuild", "clean": "rm -rf dist src/generated", + "clean:dist": "rm -rf dist", "dev": "yarn run -T tsc --build --watch", "generate": "TS_POST_PROCESS_FILE=./generator/post_process.sh JAVA_OPTS='-Dlog.level=error' openapi-generator-cli generate", "postbuild": "yarn postbuild:esm && yarn postbuild:cjs", diff --git a/packages/utils/package.json b/packages/utils/package.json index c36fc0d6928..b3a4a40c15a 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -35,9 +35,9 @@ "@sniptt/monads": "^0.5.10", "bignumber.js": "^9.3.1", "dayjs": "^1.11.3", - "lodash": "^4.17.21" + "lodash-es": "^4.17.21" }, "devDependencies": { - "@types/lodash": "^4.14.178" + "@types/lodash-es": "^4.17.12" } } diff --git a/packages/utils/src/assetData/decodeAssetData.ts b/packages/utils/src/assetData/decodeAssetData.ts index 03eaf121236..ec102f33d16 100644 --- a/packages/utils/src/assetData/decodeAssetData.ts +++ b/packages/utils/src/assetData/decodeAssetData.ts @@ -1,7 +1,7 @@ import type { AssetId } from '@shapeshiftoss/caip' import { fromAssetId } from '@shapeshiftoss/caip' import type { Asset } from '@shapeshiftoss/types' -import { pick } from 'lodash' +import { pick } from 'lodash-es' import { assertUnreachable } from '../assertUnreachable' import { FIELDS } from './constants' diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 96b5efd1212..909d563dc58 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,6 +1,6 @@ import type { AssetId } from '@shapeshiftoss/caip' import { ASSET_NAMESPACE, fromAssetId } from '@shapeshiftoss/caip' -import { isNull, isUndefined } from 'lodash' +import { isNull, isUndefined } from 'lodash-es' export * from './assertUnreachable' export * from './assetData' diff --git a/railway.json b/railway.json deleted file mode 100644 index 46cd2ac60bd..00000000000 --- a/railway.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "https://railway.app/railway.schema.json", - "build": { - "builder": "DOCKERFILE", - "dockerfilePath": "packages/swap-widget/Dockerfile" - }, - "deploy": { - "restartPolicyType": "ON_FAILURE", - "restartPolicyMaxRetries": 10 - } -} diff --git a/yarn.lock b/yarn.lock index 3093cb69180..51861f05853 100644 --- a/yarn.lock +++ b/yarn.lock @@ -95,6 +95,17 @@ __metadata: languageName: node linkType: hard +"@asteasolutions/zod-to-openapi@npm:^7.3.4": + version: 7.3.4 + resolution: "@asteasolutions/zod-to-openapi@npm:7.3.4" + dependencies: + openapi3-ts: ^4.1.2 + peerDependencies: + zod: ^3.20.2 + checksum: 897568b35a5a317c91cf9419d49d9ce8ab1eb8e97253b0d11149032f62a5f692b27abefe7a081717225ac5b49b14df275ae691219a09605a8c347949375a1554 + languageName: node + linkType: hard + "@avnu/avnu-sdk@npm:^4.0.1": version: 4.0.1 resolution: "@avnu/avnu-sdk@npm:4.0.1" @@ -197,6 +208,17 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/code-frame@npm:7.28.6" + dependencies: + "@babel/helper-validator-identifier": ^7.28.5 + js-tokens: ^4.0.0 + picocolors: ^1.1.1 + checksum: 6e98e47fd324b41c1919ff6d0fbf6fa5e991e5beff6b55803d9adaff9e11f4bc432803e52165f7b0d49af0f718209c3138a9b2fd51ff624b19d47704f11f8287 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.13.11, @babel/compat-data@npm:^7.17.7": version: 7.17.7 resolution: "@babel/compat-data@npm:7.17.7" @@ -232,6 +254,13 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/compat-data@npm:7.28.6" + checksum: 599b316aa0e3981aa9165ac34609ef5f29ebf5cecc04784e8b4932dd355aaa3599eaa222ff46a2fcfff52f083b8fd212650a52d8af57c4c217c81a100fefba09 + languageName: node + linkType: hard + "@babel/core@npm:^7.16.0, @babel/core@npm:^7.26.0": version: 7.26.9 resolution: "@babel/core@npm:7.26.9" @@ -324,7 +353,30 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.28.0, @babel/core@npm:^7.28.4": +"@babel/core@npm:^7.28.0": + version: 7.28.6 + resolution: "@babel/core@npm:7.28.6" + dependencies: + "@babel/code-frame": ^7.28.6 + "@babel/generator": ^7.28.6 + "@babel/helper-compilation-targets": ^7.28.6 + "@babel/helper-module-transforms": ^7.28.6 + "@babel/helpers": ^7.28.6 + "@babel/parser": ^7.28.6 + "@babel/template": ^7.28.6 + "@babel/traverse": ^7.28.6 + "@babel/types": ^7.28.6 + "@jridgewell/remapping": ^2.3.5 + convert-source-map: ^2.0.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.3 + semver: ^6.3.1 + checksum: 09d3712c52b2dba76dc0394127f6aacdbb575d79f8b6dc41230c1a13d8047d259ba06d88d56d62d95bb06c94c025c1e4bdd896929b5d4644ce0b96a84fd91553 + languageName: node + linkType: hard + +"@babel/core@npm:^7.28.4": version: 7.28.5 resolution: "@babel/core@npm:7.28.5" dependencies: @@ -447,6 +499,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/generator@npm:7.28.6" + dependencies: + "@babel/parser": ^7.28.6 + "@babel/types": ^7.28.6 + "@jridgewell/gen-mapping": ^0.3.12 + "@jridgewell/trace-mapping": ^0.3.28 + jsesc: ^3.0.2 + checksum: 74f62f140e301c8c21652f7db3bc275008708272c0395f178ba6953297af50c4ea484874a44b3f292d242ce8a977fd3f31d9d3a3501c3aaca9cd46e3b1cded01 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.16.7, @babel/helper-annotate-as-pure@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-annotate-as-pure@npm:7.18.6" @@ -564,6 +629,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-compilation-targets@npm:7.28.6" + dependencies: + "@babel/compat-data": ^7.28.6 + "@babel/helper-validator-option": ^7.27.1 + browserslist: ^4.24.0 + lru-cache: ^5.1.1 + semver: ^6.3.1 + checksum: 8151e36b74eb1c5e414fe945c189436421f7bfa011884de5be3dd7fd77f12f1f733ff7c982581dfa0a49d8af724450243c2409427114b4a6cfeb8333259d001c + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.18.6": version: 7.18.9 resolution: "@babel/helper-create-class-features-plugin@npm:7.18.9" @@ -882,6 +960,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-imports@npm:7.28.6" + dependencies: + "@babel/traverse": ^7.28.6 + "@babel/types": ^7.28.6 + checksum: 437513aa029898b588a38f7991d7656c539b22f595207d85d0c407240c9e3f2aff8b9d0d7115fdedc91e7fdce4465100549a052024e2fba6a810bcbb7584296b + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.22.15": version: 7.22.15 resolution: "@babel/helper-module-transforms@npm:7.22.15" @@ -953,6 +1041,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-transforms@npm:7.28.6" + dependencies: + "@babel/helper-module-imports": ^7.28.6 + "@babel/helper-validator-identifier": ^7.28.5 + "@babel/traverse": ^7.28.6 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 522f7d1d08b5e2ccd4ec912aca879bd1506af78d1fb30f46e3e6b4bb69c6ae6ab4e379a879723844230d27dc6d04a55b03f5215cd3141b7a2b40bb4a02f71a9f + languageName: node + linkType: hard + "@babel/helper-optimise-call-expression@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-optimise-call-expression@npm:7.18.6" @@ -1354,6 +1455,16 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helpers@npm:7.28.6" + dependencies: + "@babel/template": ^7.28.6 + "@babel/types": ^7.28.6 + checksum: 4f3d555ec20dde40a2fcb244c86bfd9ec007b57ec9b30a9d04334c1ea2c1670bb82c151024124e1ab27ccf0b1f5ad30167633457a7c9ffbf4064fad2643f12fc + languageName: node + linkType: hard + "@babel/highlight@npm:^7.18.6": version: 7.18.6 resolution: "@babel/highlight@npm:7.18.6" @@ -1477,6 +1588,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/parser@npm:7.28.6" + dependencies: + "@babel/types": ^7.28.6 + bin: + parser: ./bin/babel-parser.js + checksum: 2a35319792ceef9bc918f0ff854449bef0120707798fe147ef988b0606de226e2fbc3a562ba687148bfe5336c6c67358fb27e71a94e425b28482dcaf0b172fd6 + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.9": version: 7.25.9 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.9" @@ -3908,6 +4030,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/template@npm:7.28.6" + dependencies: + "@babel/code-frame": ^7.28.6 + "@babel/parser": ^7.28.6 + "@babel/types": ^7.28.6 + checksum: 8ab6383053e226025d9491a6e795293f2140482d14f60c1244bece6bf53610ed1e251d5e164de66adab765629881c7d9416e1e540c716541d2fd0f8f36a013d7 + languageName: node + linkType: hard + "@babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.18.9": version: 7.19.1 resolution: "@babel/traverse@npm:7.19.1" @@ -4010,6 +4143,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/traverse@npm:7.28.6" + dependencies: + "@babel/code-frame": ^7.28.6 + "@babel/generator": ^7.28.6 + "@babel/helper-globals": ^7.28.0 + "@babel/parser": ^7.28.6 + "@babel/template": ^7.28.6 + "@babel/types": ^7.28.6 + debug: ^4.3.1 + checksum: 07bc23b720d111a20382fcdba776b800a7c1f94e35f8e4f417869f6769ba67c2b9573c8240924ca3b0ee5a88fa7ed048efb289e8b324f5cb4971e771174a0d32 + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.10, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.19.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.4.4": version: 7.19.0 resolution: "@babel/types@npm:7.19.0" @@ -4095,6 +4243,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/types@npm:7.28.6" + dependencies: + "@babel/helper-string-parser": ^7.27.1 + "@babel/helper-validator-identifier": ^7.28.5 + checksum: f76556cda59be337cc10dc68b2a9a947c10de018998bab41076e7b7e4489b28dd53299f98f22eec0774264c989515e6fdc56de91c73e3aa396367bb953200a6a + languageName: node + linkType: hard + "@bitcoinerlab/secp256k1@npm:^1.1.1": version: 1.1.1 resolution: "@bitcoinerlab/secp256k1@npm:1.1.1" @@ -5374,6 +5532,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/aix-ppc64@npm:0.27.2" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/android-arm64@npm:0.21.5" @@ -5402,6 +5567,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm64@npm:0.27.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/android-arm@npm:0.21.5" @@ -5430,6 +5602,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm@npm:0.27.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/android-x64@npm:0.21.5" @@ -5458,6 +5637,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-x64@npm:0.27.2" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/darwin-arm64@npm:0.21.5" @@ -5486,6 +5672,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-arm64@npm:0.27.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/darwin-x64@npm:0.21.5" @@ -5514,6 +5707,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-x64@npm:0.27.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/freebsd-arm64@npm:0.21.5" @@ -5542,6 +5742,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-arm64@npm:0.27.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/freebsd-x64@npm:0.21.5" @@ -5570,6 +5777,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-x64@npm:0.27.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-arm64@npm:0.21.5" @@ -5598,6 +5812,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm64@npm:0.27.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-arm@npm:0.21.5" @@ -5626,6 +5847,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm@npm:0.27.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-ia32@npm:0.21.5" @@ -5654,6 +5882,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ia32@npm:0.27.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-loong64@npm:0.21.5" @@ -5682,6 +5917,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-loong64@npm:0.27.2" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-mips64el@npm:0.21.5" @@ -5710,6 +5952,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-mips64el@npm:0.27.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-ppc64@npm:0.21.5" @@ -5738,6 +5987,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ppc64@npm:0.27.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-riscv64@npm:0.21.5" @@ -5766,6 +6022,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-riscv64@npm:0.27.2" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-s390x@npm:0.21.5" @@ -5794,6 +6057,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-s390x@npm:0.27.2" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-x64@npm:0.21.5" @@ -5822,6 +6092,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-x64@npm:0.27.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-arm64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/netbsd-arm64@npm:0.24.2" @@ -5843,6 +6120,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-arm64@npm:0.27.2" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/netbsd-x64@npm:0.21.5" @@ -5871,6 +6155,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-x64@npm:0.27.2" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/openbsd-arm64@npm:0.24.2" @@ -5892,6 +6183,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-arm64@npm:0.27.2" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/openbsd-x64@npm:0.21.5" @@ -5920,6 +6218,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-x64@npm:0.27.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openharmony-arm64@npm:0.27.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/sunos-x64@npm:0.21.5" @@ -5948,6 +6260,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/sunos-x64@npm:0.27.2" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/win32-arm64@npm:0.21.5" @@ -5976,6 +6295,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-arm64@npm:0.27.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/win32-ia32@npm:0.21.5" @@ -6004,6 +6330,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-ia32@npm:0.27.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/win32-x64@npm:0.21.5" @@ -6032,6 +6365,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-x64@npm:0.27.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -9090,7 +9430,7 @@ __metadata: languageName: node linkType: hard -"@mysten/sui@npm:^1.0.5, @mysten/sui@npm:^1.3.0": +"@mysten/sui@npm:^1.0.5, @mysten/sui@npm:^1.3.0, @mysten/sui@npm:^1.45.2": version: 1.45.2 resolution: "@mysten/sui@npm:1.45.2" dependencies: @@ -11406,6 +11746,43 @@ __metadata: languageName: node linkType: hard +"@scalar/core@npm:0.3.28": + version: 0.3.28 + resolution: "@scalar/core@npm:0.3.28" + dependencies: + "@scalar/types": 0.5.4 + checksum: e585d454d09c8b12ddc196eaa20a23c55ac78de3590455d2fc289cf87d036d028af2de547cf92a9e3f41fa9497be8b568de42be8789f334576905ea6b7baf882 + languageName: node + linkType: hard + +"@scalar/express-api-reference@npm:^0.8.30": + version: 0.8.30 + resolution: "@scalar/express-api-reference@npm:0.8.30" + dependencies: + "@scalar/core": 0.3.28 + checksum: ae0fdded4fb85878bfdffa264511c4f49a1444d407353f8a53a7aba016df405d4acabbeac070f7b750c13d3a4096b4755d001af1a8f7949256f0a49bf7f9568a + languageName: node + linkType: hard + +"@scalar/helpers@npm:0.2.4": + version: 0.2.4 + resolution: "@scalar/helpers@npm:0.2.4" + checksum: 3b1ac375ce74536ab9d650257a3a2d6514a9be8e1c8158a12bf46b06ed4299b57fb90631155ded753460c9fbbcdba1a65ad0b2d743d5dc937f49bebcadcf58dc + languageName: node + linkType: hard + +"@scalar/types@npm:0.5.4": + version: 0.5.4 + resolution: "@scalar/types@npm:0.5.4" + dependencies: + "@scalar/helpers": 0.2.4 + nanoid: 5.1.5 + type-fest: 5.0.0 + zod: ^4.1.11 + checksum: 91263d099ac4460bb7f71dff54b561f2f7195157f467dafb015f78cbde01666767e1b3430ae40e1c1f1a4d5025aa0bc8d6734e1cd6bb45636303b8996a78a241 + languageName: node + linkType: hard + "@scure/base@npm:1.2.6, @scure/base@npm:^1.2.4, @scure/base@npm:^1.2.6, @scure/base@npm:~1.2.1": version: 1.2.6 resolution: "@scure/base@npm:1.2.6" @@ -12347,6 +12724,33 @@ __metadata: languageName: node linkType: hard +"@shapeshiftoss/public-api@workspace:packages/public-api": + version: 0.0.0-use.local + resolution: "@shapeshiftoss/public-api@workspace:packages/public-api" + dependencies: + "@asteasolutions/zod-to-openapi": ^7.3.4 + "@scalar/express-api-reference": ^0.8.30 + "@shapeshiftoss/caip": "workspace:^" + "@shapeshiftoss/chain-adapters": "workspace:^" + "@shapeshiftoss/swapper": "workspace:^" + "@shapeshiftoss/types": "workspace:^" + "@shapeshiftoss/utils": "workspace:^" + "@sniptt/monads": ^0.5.10 + "@types/cors": ^2.8.17 + "@types/express": ^4.17.21 + "@types/node": ^22.10.4 + "@types/swagger-ui-express": ^4.1.8 + "@types/uuid": ^9.0.5 + cors: ^2.8.5 + express: ^4.21.0 + tsx: ^4.19.2 + typescript: ^5.7.3 + uuid: ^9.0.0 + yaml: ^2.8.2 + zod: 3.23.8 + languageName: unknown + linkType: soft + "@shapeshiftoss/swap-widget@workspace:packages/swap-widget": version: 0.0.0-use.local resolution: "@shapeshiftoss/swap-widget@workspace:packages/swap-widget" @@ -12384,6 +12788,7 @@ __metadata: "@cowprotocol/app-data": ^2.3.0 "@defuse-protocol/one-click-sdk-typescript": ^0.1.1-0.2 "@jup-ag/api": ^6.0.30 + "@mysten/sui": ^1.45.2 "@shapeshiftoss/bitcoinjs-lib": 7.0.0-shapeshift.0 "@shapeshiftoss/caip": "workspace:^" "@shapeshiftoss/chain-adapters": "workspace:^" @@ -12497,10 +12902,10 @@ __metadata: "@shapeshiftoss/caip": "workspace:^" "@shapeshiftoss/types": "workspace:^" "@sniptt/monads": ^0.5.10 - "@types/lodash": ^4.14.178 + "@types/lodash-es": ^4.17.12 bignumber.js: ^9.3.1 dayjs: ^1.11.3 - lodash: ^4.17.21 + lodash-es: ^4.17.21 languageName: unknown linkType: soft @@ -14591,6 +14996,16 @@ __metadata: languageName: node linkType: hard +"@types/body-parser@npm:*": + version: 1.19.6 + resolution: "@types/body-parser@npm:1.19.6" + dependencies: + "@types/connect": "*" + "@types/node": "*" + checksum: 33041e88eae00af2cfa0827e951e5f1751eafab2a8b6fce06cd89ef368a988907996436b1325180edaeddd1c0c7d0d0d4c20a6c9ff294a91e0039a9db9e9b658 + languageName: node + linkType: hard + "@types/bs58check@npm:^2.1.0": version: 2.1.0 resolution: "@types/bs58check@npm:2.1.0" @@ -14626,6 +15041,15 @@ __metadata: languageName: node linkType: hard +"@types/connect@npm:*": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "*" + checksum: 7eb1bc5342a9604facd57598a6c62621e244822442976c443efb84ff745246b10d06e8b309b6e80130026a396f19bf6793b7cecd7380169f369dac3bfc46fb99 + languageName: node + linkType: hard + "@types/connect@npm:^3.4.33": version: 3.4.35 resolution: "@types/connect@npm:3.4.35" @@ -14642,6 +15066,15 @@ __metadata: languageName: node linkType: hard +"@types/cors@npm:^2.8.17": + version: 2.8.19 + resolution: "@types/cors@npm:2.8.19" + dependencies: + "@types/node": "*" + checksum: 9545cc532c9218754443f48a0c98c1a9ba4af1fe54a3425c95de75ff3158147bb39e666cb7c6bf98cc56a9c6dc7b4ce5b2cbdae6b55d5942e50c81b76ed6b825 + languageName: node + linkType: hard + "@types/css-font-loading-module@npm:0.0.7": version: 0.0.7 resolution: "@types/css-font-loading-module@npm:0.0.7" @@ -14822,6 +15255,53 @@ __metadata: languageName: node linkType: hard +"@types/express-serve-static-core@npm:^4.17.33": + version: 4.19.7 + resolution: "@types/express-serve-static-core@npm:4.19.7" + dependencies: + "@types/node": "*" + "@types/qs": "*" + "@types/range-parser": "*" + "@types/send": "*" + checksum: 6d0f1126293a5b35d3697a5fc7e787d6c1bb8f5368dd0691fe0c8041a812a668ebb3b168124de30e600963d8acdf48ec7373daf7608d9dc1d6288f6c373d19a1 + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:^5.0.0": + version: 5.1.0 + resolution: "@types/express-serve-static-core@npm:5.1.0" + dependencies: + "@types/node": "*" + "@types/qs": "*" + "@types/range-parser": "*" + "@types/send": "*" + checksum: a2a780a9954e4553b69474ea76ab5c26534dfb274e1a49524f6394cbb590c2bbf73ce9dc67ab920c25d91583c8a99d8e486696b6e9810bef7a964fcbaad88a7b + languageName: node + linkType: hard + +"@types/express@npm:*": + version: 5.0.6 + resolution: "@types/express@npm:5.0.6" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^5.0.0 + "@types/serve-static": ^2 + checksum: da2cc3de1b1a4d7f20ed3fb6f0a8ee08e99feb3c2eb5a8d643db77017d8d0e70fee9e95da38a73f51bcdf5eda3bb6435073c0271dc04fb16fda92e55daf911fa + languageName: node + linkType: hard + +"@types/express@npm:^4.17.21": + version: 4.17.25 + resolution: "@types/express@npm:4.17.25" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^4.17.33 + "@types/qs": "*" + "@types/serve-static": ^1 + checksum: 285d16008489d37b2be03e2e050bcf201d5d6ed9278ca13619d9029efd2055b192b2445f769116f716cfcf53d9d799a03f4e76199af9cea0ea3dee3d88595931 + languageName: node + linkType: hard + "@types/fast-text-encoding@npm:^1.0.1": version: 1.0.1 resolution: "@types/fast-text-encoding@npm:1.0.1" @@ -14866,6 +15346,13 @@ __metadata: languageName: node linkType: hard +"@types/http-errors@npm:*": + version: 2.0.5 + resolution: "@types/http-errors@npm:2.0.5" + checksum: a88da669366bc483e8f3b3eb3d34ada5f8d13eeeef851b1204d77e2ba6fc42aba4566d877cca5c095204a3f4349b87fe397e3e21288837bdd945dd514120755b + languageName: node + linkType: hard + "@types/http-proxy@npm:^1.17.8": version: 1.17.8 resolution: "@types/http-proxy@npm:1.17.8" @@ -14916,6 +15403,15 @@ __metadata: languageName: node linkType: hard +"@types/lodash-es@npm:^4.17.12": + version: 4.17.12 + resolution: "@types/lodash-es@npm:4.17.12" + dependencies: + "@types/lodash": "*" + checksum: 990a99e2243bebe9505cb5ad19fbc172beb4a8e00f9075c99fc06c46c2801ffdb40bc2867271cf580d5f48994fc9fb076ec92cd60a20e621603bf22114e5b077 + languageName: node + linkType: hard + "@types/lodash.clonedeep@npm:^4.5.7": version: 4.5.7 resolution: "@types/lodash.clonedeep@npm:4.5.7" @@ -14973,6 +15469,13 @@ __metadata: languageName: node linkType: hard +"@types/mime@npm:^1": + version: 1.3.5 + resolution: "@types/mime@npm:1.3.5" + checksum: e29a5f9c4776f5229d84e525b7cd7dd960b51c30a0fb9a028c0821790b82fca9f672dab56561e2acd9e8eed51d431bde52eafdfef30f643586c4162f1aecfc78 + languageName: node + linkType: hard + "@types/minimist@npm:^1.2.0": version: 1.2.2 resolution: "@types/minimist@npm:1.2.2" @@ -15084,6 +15587,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^22.10.4": + version: 22.19.3 + resolution: "@types/node@npm:22.19.3" + dependencies: + undici-types: ~6.21.0 + checksum: 2fffd870ac2a5a531a160034075c858e7c95c521b0612e9b7ae35e7a6bae1880c7a190a77a5c335fbb2e5f4a315c1e2f2b529f9be4bdbb2166664a7206e28951 + languageName: node + linkType: hard + "@types/node@npm:^22.13.14": version: 22.17.2 resolution: "@types/node@npm:22.17.2" @@ -15123,6 +15635,13 @@ __metadata: languageName: node linkType: hard +"@types/qs@npm:*": + version: 6.14.0 + resolution: "@types/qs@npm:6.14.0" + checksum: 1909205514d22b3cbc7c2314e2bd8056d5f05dfb21cf4377f0730ee5e338ea19957c41735d5e4806c746176563f50005bbab602d8358432e25d900bdf4970826 + languageName: node + linkType: hard + "@types/qs@npm:^6.9.18": version: 6.9.18 resolution: "@types/qs@npm:6.9.18" @@ -15130,6 +15649,13 @@ __metadata: languageName: node linkType: hard +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 95640233b689dfbd85b8c6ee268812a732cf36d5affead89e806fe30da9a430767af8ef2cd661024fd97e19d61f3dec75af2df5e80ec3bea000019ab7028629a + languageName: node + linkType: hard + "@types/react-dom@npm:^19.0.0": version: 19.1.2 resolution: "@types/react-dom@npm:19.1.2" @@ -15282,6 +15808,46 @@ __metadata: languageName: node linkType: hard +"@types/send@npm:*": + version: 1.2.1 + resolution: "@types/send@npm:1.2.1" + dependencies: + "@types/node": "*" + checksum: 3b8388edeec77ae62f7bbc384c98ca06140614e4ef34fc04b35824f19937f472f8ff3785e83570e0d40e6d7c934c015d4831c82a74a1ade0d9676720835702c5 + languageName: node + linkType: hard + +"@types/send@npm:<1": + version: 0.17.6 + resolution: "@types/send@npm:0.17.6" + dependencies: + "@types/mime": ^1 + "@types/node": "*" + checksum: 5bd287f1357380963eb4b12daef5c8982f52a3269308ff3414304074d4ad7f05fe466f2cb476f54798096877ad3c5343692978776bd674b25261ecbeab87640f + languageName: node + linkType: hard + +"@types/serve-static@npm:*, @types/serve-static@npm:^2": + version: 2.2.0 + resolution: "@types/serve-static@npm:2.2.0" + dependencies: + "@types/http-errors": "*" + "@types/node": "*" + checksum: 0ad152ae2851cbe6c9381d0eca5fff8e6ff56afc0e03099efa88712c00318f52d8b01000be391375dcb2dbd912a54dacfc67ff36bae636a61570d74feba559b7 + languageName: node + linkType: hard + +"@types/serve-static@npm:^1": + version: 1.15.10 + resolution: "@types/serve-static@npm:1.15.10" + dependencies: + "@types/http-errors": "*" + "@types/node": "*" + "@types/send": <1 + checksum: f216eef2aaf2c8eff09f431c420c5c2989eaf0dfc15d106db9fb64c14577a4059af24fb0ae2eba7984d6360950c8cbc1fb52f65608106477729d251481bc96fe + languageName: node + linkType: hard + "@types/set-cookie-parser@npm:^2.4.0": version: 2.4.2 resolution: "@types/set-cookie-parser@npm:2.4.2" @@ -15307,6 +15873,16 @@ __metadata: languageName: node linkType: hard +"@types/swagger-ui-express@npm:^4.1.8": + version: 4.1.8 + resolution: "@types/swagger-ui-express@npm:4.1.8" + dependencies: + "@types/express": "*" + "@types/serve-static": "*" + checksum: bf4d84fb21c11e820238685cdc102f8d5e82d4382df9450a358bf80f70f5f0699c87645a291c52beee1eba5c0ee594e03714b236f1a0737272941a8708218f8f + languageName: node + linkType: hard + "@types/through@npm:*": version: 0.0.30 resolution: "@types/through@npm:0.0.30" @@ -17468,6 +18044,16 @@ __metadata: languageName: node linkType: hard +"accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: ~2.1.34 + negotiator: 0.6.3 + checksum: 50c43d32e7b50285ebe84b613ee4a3aa426715a7d131b65b786e2ead0fd76b6b60091b9916d3478a75f11f162628a2139991b6c03ab3f1d9ab7c86075dc8eab4 + languageName: node + linkType: hard + "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -17934,6 +18520,13 @@ __metadata: languageName: node linkType: hard +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: a9925bf3512d9dce202112965de90c222cd59a4fbfce68a0951d25d965cf44642931f40aac72309c41f12df19afa010ecadceb07cfff9ccc1621e99d89ab5f3b + languageName: node + linkType: hard + "array-ify@npm:^1.0.0": version: 1.0.0 resolution: "array-ify@npm:1.0.0" @@ -19259,6 +19852,26 @@ __metadata: languageName: node linkType: hard +"body-parser@npm:~1.20.3": + version: 1.20.4 + resolution: "body-parser@npm:1.20.4" + dependencies: + bytes: ~3.1.2 + content-type: ~1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: ~1.2.0 + http-errors: ~2.0.1 + iconv-lite: ~0.4.24 + on-finished: ~2.4.1 + qs: ~6.14.0 + raw-body: ~2.5.3 + type-is: ~1.6.18 + unpipe: ~1.0.0 + checksum: eaa212cff1737d2fbb49fc7aa1d71d9b456adea2dc3de388ff3c6d67b28028d6b1fa7e6cd77e3670b4cbd402ab011f80f6e5bb811480b53a28d11f33678c6298 + languageName: node + linkType: hard + "borsh@npm:1.0.0": version: 1.0.0 resolution: "borsh@npm:1.0.0" @@ -19735,6 +20348,13 @@ __metadata: languageName: node linkType: hard +"bytes@npm:~3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: e4bcd3948d289c5127591fbedf10c0b639ccbf00243504e4e127374a15c3bc8eed0d28d4aaab08ff6f1cf2abc0cce6ba3085ed32f4f90e82a5683ce0014e1b6e + languageName: node + linkType: hard + "cac@npm:^6.7.14": version: 6.7.14 resolution: "cac@npm:6.7.14" @@ -20589,6 +21209,22 @@ __metadata: languageName: node linkType: hard +"content-disposition@npm:~0.5.4": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: 5.2.1 + checksum: afb9d545e296a5171d7574fcad634b2fdf698875f4006a9dd04a3e1333880c5c0c98d47b560d01216fb6505a54a2ba6a843ee3a02ec86d7e911e8315255f56c3 + languageName: node + linkType: hard + +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 566271e0a251642254cde0f845f9dd4f9856e52d988f4eb0d0dcffbb7a1f8ec98de7a5215fc628f3bce30fe2fb6fd2bc064b562d721658c59b544e2d34ea2766 + languageName: node + linkType: hard + "conventional-changelog-angular@npm:^5.0.11": version: 5.0.13 resolution: "conventional-changelog-angular@npm:5.0.13" @@ -20649,10 +21285,17 @@ __metadata: languageName: node linkType: hard -"cookie-es@npm:^1.2.2": - version: 1.2.2 - resolution: "cookie-es@npm:1.2.2" - checksum: 099050c30c967c89aa72d1d7984e87b3395f3e709cf148d297f436828ebfcc39033f5374d2efdc46d9b5e3eee50b1d59635432c252e57329fea7f09afeb4d055 +"cookie-es@npm:^1.0.0": + version: 1.0.0 + resolution: "cookie-es@npm:1.0.0" + checksum: e8721cf4d38f3e44049c9118874b323f4f674b1c5cef84a2b888f5bf86ad720ad17b51b43150cad7535a375c24e2921da603801ad28aa6125c3d36c031b41468 + languageName: node + linkType: hard + +"cookie-signature@npm:~1.0.6": + version: 1.0.7 + resolution: "cookie-signature@npm:1.0.7" + checksum: 1a62808cd30d15fb43b70e19829b64d04b0802d8ef00275b57d152de4ae6a3208ca05c197b6668d104c4d9de389e53ccc2d3bc6bcaaffd9602461417d8c40710 languageName: node linkType: hard @@ -20663,6 +21306,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:~0.7.1": + version: 0.7.2 + resolution: "cookie@npm:0.7.2" + checksum: 9bf8555e33530affd571ea37b615ccad9b9a34febbf2c950c86787088eb00a8973690833b0f8ebd6b69b753c62669ea60cec89178c1fb007bf0749abed74f93e + languageName: node + linkType: hard + "cookiejar@npm:^2.1.1": version: 2.1.4 resolution: "cookiejar@npm:2.1.4" @@ -20922,15 +21572,6 @@ __metadata: languageName: node linkType: hard -"crossws@npm:^0.3.5": - version: 0.3.5 - resolution: "crossws@npm:0.3.5" - dependencies: - uncrypto: ^0.1.3 - checksum: ca597f9b07d82c71fdeed033d6691c4ba2523c596089218ba2782fce26365c86d555e4ea9d6726d277a34087eac861b11bd4df30646dcfc1a1c867a174f2bf98 - languageName: node - linkType: hard - "crypto-browserify@npm:3.12.0, crypto-browserify@npm:^3.12.0": version: 3.12.0 resolution: "crypto-browserify@npm:3.12.0" @@ -21418,6 +22059,15 @@ __metadata: languageName: node linkType: hard +"debug@npm:2.6.9, debug@npm:^2.2.0": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: 2.0.0 + checksum: d2f51589ca66df60bf36e1fa6e4386b318c3f1e06772280eea5b1ae9fd3d05e9c2b7fd8a7d862457d00853c75b00451aa2d7459b924629ee385287a650f58fe6 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.1, debug@npm:~4.3.2": version: 4.3.4 resolution: "debug@npm:4.3.4" @@ -21430,15 +22080,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:^2.2.0": - version: 2.6.9 - resolution: "debug@npm:2.6.9" - dependencies: - ms: 2.0.0 - checksum: d2f51589ca66df60bf36e1fa6e4386b318c3f1e06772280eea5b1ae9fd3d05e9c2b7fd8a7d862457d00853c75b00451aa2d7459b924629ee385287a650f58fe6 - languageName: node - linkType: hard - "debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -21642,20 +22283,13 @@ __metadata: languageName: node linkType: hard -"defu@npm:^6.1.2": +"defu@npm:^6.1.2, defu@npm:^6.1.3": version: 6.1.3 resolution: "defu@npm:6.1.3" checksum: c857a0cf854632e8528dad36454fd1c812bff8f5f091d5a6892e75d7f6b76d8319afbbfb8c504daab84ac86e40037ff37c544d50f89ed5b5419ba1989a226777 languageName: node linkType: hard -"defu@npm:^6.1.4": - version: 6.1.4 - resolution: "defu@npm:6.1.4" - checksum: 40e3af6338f195ac1564f53d1887fa2d0429ac7e8c081204bc4d29191180059d3952b5f4e08fe5df8d59eb873aa26e9c88b56d4fac699673d4a372c93620b229 - languageName: node - linkType: hard - "delaunator@npm:5": version: 5.0.0 resolution: "delaunator@npm:5.0.0" @@ -21693,7 +22327,7 @@ __metadata: languageName: node linkType: hard -"depd@npm:2.0.0, depd@npm:^2.0.0": +"depd@npm:2.0.0, depd@npm:^2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: abbe19c768c97ee2eed6282d8ce3031126662252c58d711f646921c9623f9052e3e1906443066beec1095832f534e57c523b7333f8e7e0d93051ab6baef5ab3a @@ -21740,10 +22374,10 @@ __metadata: languageName: node linkType: hard -"destr@npm:^2.0.5": - version: 2.0.5 - resolution: "destr@npm:2.0.5" - checksum: e6d5b9e922f528527cd98035249b4d34077828debd2be448a33e268ac1f803bd9a53e7cf0f5184ef68a67573b7f0a6033a89913f61eadaf0e180de49b148606e +"destroy@npm:1.2.0, destroy@npm:~1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 languageName: node linkType: hard @@ -22079,6 +22713,13 @@ __metadata: languageName: node linkType: hard +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 1b4cac778d64ce3b582a7e26b218afe07e207a0f9bfe13cc7395a6d307849cfe361e65033c3251e00c27dd060cab43014c2d6b2647676135e18b77d2d05b3f4f + languageName: node + linkType: hard + "eip-712@npm:^1.0.0": version: 1.0.0 resolution: "eip-712@npm:1.0.0" @@ -22271,6 +22912,13 @@ __metadata: languageName: node linkType: hard +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe + languageName: node + linkType: hard + "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -23088,91 +23736,180 @@ __metadata: optional: true "@esbuild/openbsd-x64": optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 2c4e91948b939e711e9342e692fc3c8b0a95acbc1fc9c7628db6092c4aef7c32aa643b2782111625871756084536cebc4831b3f1d5c3b6bd4e4774e21bc4bbea - languageName: node - linkType: hard - -"esbuild@npm:~0.25.0": - version: 0.25.1 - resolution: "esbuild@npm:0.25.1" - dependencies: - "@esbuild/aix-ppc64": 0.25.1 - "@esbuild/android-arm": 0.25.1 - "@esbuild/android-arm64": 0.25.1 - "@esbuild/android-x64": 0.25.1 - "@esbuild/darwin-arm64": 0.25.1 - "@esbuild/darwin-x64": 0.25.1 - "@esbuild/freebsd-arm64": 0.25.1 - "@esbuild/freebsd-x64": 0.25.1 - "@esbuild/linux-arm": 0.25.1 - "@esbuild/linux-arm64": 0.25.1 - "@esbuild/linux-ia32": 0.25.1 - "@esbuild/linux-loong64": 0.25.1 - "@esbuild/linux-mips64el": 0.25.1 - "@esbuild/linux-ppc64": 0.25.1 - "@esbuild/linux-riscv64": 0.25.1 - "@esbuild/linux-s390x": 0.25.1 - "@esbuild/linux-x64": 0.25.1 - "@esbuild/netbsd-arm64": 0.25.1 - "@esbuild/netbsd-x64": 0.25.1 - "@esbuild/openbsd-arm64": 0.25.1 - "@esbuild/openbsd-x64": 0.25.1 - "@esbuild/sunos-x64": 0.25.1 - "@esbuild/win32-arm64": 0.25.1 - "@esbuild/win32-ia32": 0.25.1 - "@esbuild/win32-x64": 0.25.1 - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 2c4e91948b939e711e9342e692fc3c8b0a95acbc1fc9c7628db6092c4aef7c32aa643b2782111625871756084536cebc4831b3f1d5c3b6bd4e4774e21bc4bbea + languageName: node + linkType: hard + +"esbuild@npm:~0.25.0": + version: 0.25.1 + resolution: "esbuild@npm:0.25.1" + dependencies: + "@esbuild/aix-ppc64": 0.25.1 + "@esbuild/android-arm": 0.25.1 + "@esbuild/android-arm64": 0.25.1 + "@esbuild/android-x64": 0.25.1 + "@esbuild/darwin-arm64": 0.25.1 + "@esbuild/darwin-x64": 0.25.1 + "@esbuild/freebsd-arm64": 0.25.1 + "@esbuild/freebsd-x64": 0.25.1 + "@esbuild/linux-arm": 0.25.1 + "@esbuild/linux-arm64": 0.25.1 + "@esbuild/linux-ia32": 0.25.1 + "@esbuild/linux-loong64": 0.25.1 + "@esbuild/linux-mips64el": 0.25.1 + "@esbuild/linux-ppc64": 0.25.1 + "@esbuild/linux-riscv64": 0.25.1 + "@esbuild/linux-s390x": 0.25.1 + "@esbuild/linux-x64": 0.25.1 + "@esbuild/netbsd-arm64": 0.25.1 + "@esbuild/netbsd-x64": 0.25.1 + "@esbuild/openbsd-arm64": 0.25.1 + "@esbuild/openbsd-x64": 0.25.1 + "@esbuild/sunos-x64": 0.25.1 + "@esbuild/win32-arm64": 0.25.1 + "@esbuild/win32-ia32": 0.25.1 + "@esbuild/win32-x64": 0.25.1 + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: c84e209259273fca0f8ba7cd00974dfff53eb3fcce5ff0f987d8231a5b49f22c16fa954f0bf06f07b00bd368270d8274feb5a09d7d5dfae0891a47dda24455a2 + languageName: node + linkType: hard + +"esbuild@npm:~0.27.0": + version: 0.27.2 + resolution: "esbuild@npm:0.27.2" + dependencies: + "@esbuild/aix-ppc64": 0.27.2 + "@esbuild/android-arm": 0.27.2 + "@esbuild/android-arm64": 0.27.2 + "@esbuild/android-x64": 0.27.2 + "@esbuild/darwin-arm64": 0.27.2 + "@esbuild/darwin-x64": 0.27.2 + "@esbuild/freebsd-arm64": 0.27.2 + "@esbuild/freebsd-x64": 0.27.2 + "@esbuild/linux-arm": 0.27.2 + "@esbuild/linux-arm64": 0.27.2 + "@esbuild/linux-ia32": 0.27.2 + "@esbuild/linux-loong64": 0.27.2 + "@esbuild/linux-mips64el": 0.27.2 + "@esbuild/linux-ppc64": 0.27.2 + "@esbuild/linux-riscv64": 0.27.2 + "@esbuild/linux-s390x": 0.27.2 + "@esbuild/linux-x64": 0.27.2 + "@esbuild/netbsd-arm64": 0.27.2 + "@esbuild/netbsd-x64": 0.27.2 + "@esbuild/openbsd-arm64": 0.27.2 + "@esbuild/openbsd-x64": 0.27.2 + "@esbuild/openharmony-arm64": 0.27.2 + "@esbuild/sunos-x64": 0.27.2 + "@esbuild/win32-arm64": 0.27.2 + "@esbuild/win32-ia32": 0.27.2 + "@esbuild/win32-x64": 0.27.2 + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": optional: true "@esbuild/sunos-x64": optional: true @@ -23184,7 +23921,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: c84e209259273fca0f8ba7cd00974dfff53eb3fcce5ff0f987d8231a5b49f22c16fa954f0bf06f07b00bd368270d8274feb5a09d7d5dfae0891a47dda24455a2 + checksum: 62ec92f8f40ad19922ae7d8dbf0427e41744120a77cc95abdf099dfb484d65fbe3c70cc55b8eccb7f6cb0d14e871ff1f2f76376d476915c2a6d2b800269261b2 languageName: node linkType: hard @@ -23202,6 +23939,13 @@ __metadata: languageName: node linkType: hard +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24 + languageName: node + linkType: hard + "escape-string-regexp@npm:2.0.0": version: 2.0.0 resolution: "escape-string-regexp@npm:2.0.0" @@ -23629,6 +24373,13 @@ __metadata: languageName: node linkType: hard +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff + languageName: node + linkType: hard + "eth-block-tracker@npm:6.1.0": version: 6.1.0 resolution: "eth-block-tracker@npm:6.1.0" @@ -24269,6 +25020,45 @@ __metadata: languageName: node linkType: hard +"express@npm:^4.21.0": + version: 4.22.1 + resolution: "express@npm:4.22.1" + dependencies: + accepts: ~1.3.8 + array-flatten: 1.1.1 + body-parser: ~1.20.3 + content-disposition: ~0.5.4 + content-type: ~1.0.4 + cookie: ~0.7.1 + cookie-signature: ~1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: ~2.0.0 + escape-html: ~1.0.3 + etag: ~1.8.1 + finalhandler: ~1.3.1 + fresh: ~0.5.2 + http-errors: ~2.0.0 + merge-descriptors: 1.0.3 + methods: ~1.1.2 + on-finished: ~2.4.1 + parseurl: ~1.3.3 + path-to-regexp: ~0.1.12 + proxy-addr: ~2.0.7 + qs: ~6.14.0 + range-parser: ~1.2.1 + safe-buffer: 5.2.1 + send: ~0.19.0 + serve-static: ~1.16.2 + setprototypeof: 1.2.0 + statuses: ~2.0.1 + type-is: ~1.6.18 + utils-merge: 1.0.1 + vary: ~1.1.2 + checksum: 38fd76585f6a2394e02d499f852fc70c94c9b1527bd5812eb5ee45c23b7f1297baaf13c55162253b14c1e36939b8401429d6594095e63d01ca77447dac72894e + languageName: node + linkType: hard + "ext@npm:^1.1.2": version: 1.7.0 resolution: "ext@npm:1.7.0" @@ -24584,6 +25374,21 @@ __metadata: languageName: node linkType: hard +"finalhandler@npm:~1.3.1": + version: 1.3.2 + resolution: "finalhandler@npm:1.3.2" + dependencies: + debug: 2.6.9 + encodeurl: ~2.0.0 + escape-html: ~1.0.3 + on-finished: ~2.4.1 + parseurl: ~1.3.3 + statuses: ~2.0.2 + unpipe: ~1.0.0 + checksum: 4bce6b3e1f6998497a8ef8418bc307ef09daee05acc5a69a36da665565cbeb86218de1932e42dbf2eebf18f580053d2061eddbdeff9e312de45d46fbf4dd36ec + languageName: node + linkType: hard + "find-cache-dir@npm:^2.0.0": version: 2.1.0 resolution: "find-cache-dir@npm:2.1.0" @@ -24768,6 +25573,13 @@ __metadata: languageName: node linkType: hard +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: fd27e2394d8887ebd16a66ffc889dc983fbbd797d5d3f01087c020283c0f019a7d05ee85669383d8e0d216b116d720fc0cef2f6e9b7eb9f4c90c6e0bc7fd28e6 + languageName: node + linkType: hard + "framer-motion@npm:^12.6.5": version: 12.7.4 resolution: "framer-motion@npm:12.7.4" @@ -24799,6 +25611,13 @@ __metadata: languageName: node linkType: hard +"fresh@npm:~0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: 13ea8b08f91e669a64e3ba3a20eb79d7ca5379a81f1ff7f4310d54e2320645503cc0c78daedc93dfb6191287295f6479544a649c64d8e41a1c0fb0c221552346 + languageName: node + linkType: hard + "friendly-challenge@npm:0.9.2": version: 0.9.2 resolution: "friendly-challenge@npm:0.9.2" @@ -25490,19 +26309,18 @@ __metadata: linkType: hard "h3@npm:^1.8.1, h3@npm:^1.8.2": - version: 1.15.5 - resolution: "h3@npm:1.15.5" - dependencies: - cookie-es: ^1.2.2 - crossws: ^0.3.5 - defu: ^6.1.4 - destr: ^2.0.5 - iron-webcrypto: ^1.2.1 - node-mock-http: ^1.0.4 - radix3: ^1.1.2 - ufo: ^1.6.3 + version: 1.9.0 + resolution: "h3@npm:1.9.0" + dependencies: + cookie-es: ^1.0.0 + defu: ^6.1.3 + destr: ^2.0.2 + iron-webcrypto: ^1.0.0 + radix3: ^1.1.0 + ufo: ^1.3.2 uncrypto: ^0.1.3 - checksum: 455f58681de2b789b39af45067db8dde35234c18d86ec5068687300aae3c0225f85edcd110906493e087407e2caaa1f292352769c484284e72558d7677c29899 + unenv: ^1.7.4 + checksum: 7305163b6fa4ecd7d14fce85e6372fae3580728957c8c021fa6b18176ce5e0cbfcc07c523bafcf7b34e895d2dab3ba731a11af43282faee4f2c8a331ec144f51 languageName: node linkType: hard @@ -25920,6 +26738,19 @@ __metadata: languageName: node linkType: hard +"http-errors@npm:~2.0.0, http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" + dependencies: + depd: ~2.0.0 + inherits: ~2.0.4 + setprototypeof: ~1.2.0 + statuses: ~2.0.2 + toidentifier: ~1.0.1 + checksum: 155d1a100a06e4964597013109590b97540a177b69c3600bbc93efc746465a99a2b718f43cdf76b3791af994bbe3a5711002046bf668cdc007ea44cea6df7ccd + languageName: node + linkType: hard + "http-proxy-agent@npm:^5.0.0": version: 5.0.0 resolution: "http-proxy-agent@npm:5.0.0" @@ -26050,7 +26881,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:^0.4.24": +"iconv-lite@npm:^0.4.24, iconv-lite@npm:~0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" dependencies: @@ -26361,6 +27192,13 @@ __metadata: languageName: node linkType: hard +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: f88d3825981486f5a1942414c8d77dd6674dd71c065adcfa46f578d677edcb99fda25af42675cb59db492fdf427b34a5abfcde3982da11a8fd83a500b41cfe77 + languageName: node + linkType: hard + "ipfs-only-hash@npm:^4.0.0": version: 4.0.0 resolution: "ipfs-only-hash@npm:4.0.0" @@ -26420,10 +27258,10 @@ __metadata: languageName: node linkType: hard -"iron-webcrypto@npm:^1.2.1": - version: 1.2.1 - resolution: "iron-webcrypto@npm:1.2.1" - checksum: b158d1893c8d037c11a7dcfd1998b519f31f979643c2c505c6eb1170fd63553498a58b05947d5dea116975df8f12ede5ca235cb68e4c1f404fa6695e4508c60c +"iron-webcrypto@npm:^1.0.0": + version: 1.0.0 + resolution: "iron-webcrypto@npm:1.0.0" + checksum: bbd96cbbfec7d072296bc7464763b96555bdadb12aca50f1f1c7e4fcdc6acb102bc3488333e924f94404dd50eda24f84b67ad28323b9138ec7255a023e8dc19e languageName: node linkType: hard @@ -28143,6 +28981,13 @@ __metadata: languageName: node linkType: hard +"lodash-es@npm:^4.17.21": + version: 4.17.22 + resolution: "lodash-es@npm:4.17.22" + checksum: 1da119f40e54822fb5d69eeb9d2c7ec46ac541cc73e6250748838abd7dd51ecf613592e07a5476f56bcbbeb27838697a4347d68cd98f131cad8c4665f9bb2a16 + languageName: node + linkType: hard + "lodash.clonedeep@npm:^4.5.0": version: 4.5.0 resolution: "lodash.clonedeep@npm:4.5.0" @@ -28485,6 +29330,13 @@ __metadata: languageName: node linkType: hard +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: af1b38516c28ec95d6b0826f6c8f276c58aec391f76be42aa07646b4e39d317723e869700933ca6995b056db4b09a78c92d5440dc23657e6764be5d28874bba1 + languageName: node + linkType: hard + "memdown@npm:^1.0.0": version: 1.4.1 resolution: "memdown@npm:1.4.1" @@ -28561,6 +29413,13 @@ __metadata: languageName: node linkType: hard +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 52117adbe0313d5defa771c9993fe081e2d2df9b840597e966aadafde04ae8d0e3da46bac7ca4efc37d4d2b839436582659cd49c6a43eacb3fe3050896a105d1 + languageName: node + linkType: hard + "merge-options@npm:^3.0.4": version: 3.0.4 resolution: "merge-options@npm:3.0.4" @@ -28627,6 +29486,13 @@ __metadata: languageName: node linkType: hard +"methods@npm:~1.1.2": + version: 1.1.2 + resolution: "methods@npm:1.1.2" + checksum: 0917ff4041fa8e2f2fda5425a955fe16ca411591fbd123c0d722fcf02b73971ed6f764d85f0a6f547ce49ee0221ce2c19a5fa692157931cecb422984f1dcd13a + languageName: node + linkType: hard + "micro-ftch@npm:^0.3.1": version: 0.3.1 resolution: "micro-ftch@npm:0.3.1" @@ -28683,7 +29549,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.0.1, mime-types@npm:^2.1.12, mime-types@npm:^2.1.34, mime-types@npm:~2.1.19": +"mime-types@npm:^2.0.1, mime-types@npm:^2.1.12, mime-types@npm:^2.1.34, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -28692,6 +29558,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: fef25e39263e6d207580bdc629f8872a3f9772c923c7f8c7e793175cee22777bbe8bba95e5d509a40aaa292d8974514ce634ae35769faa45f22d17edda5e8557 + languageName: node + linkType: hard + "mime@npm:^3.0.0": version: 3.0.0 resolution: "mime@npm:3.0.0" @@ -28875,10 +29750,10 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0": - version: 5.0.0 - resolution: "minipass@npm:5.0.0" - checksum: 425dab288738853fded43da3314a0b5c035844d6f3097a8e3b5b29b328da8f3c1af6fc70618b32c29ff906284cf6406b6841376f21caaadd0793c1d5a6a620ea +"minipass@npm:^4.0.0": + version: 4.2.5 + resolution: "minipass@npm:4.2.5" + checksum: 4f9c19af23a5d4a9e7156feefc9110634b178a8cff8f8271af16ec5ebf7e221725a97429952c856f5b17b30c2065ebd24c81722d90c93d2122611d75b952b48f languageName: node linkType: hard @@ -29069,7 +29944,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -29232,6 +30107,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:5.1.5": + version: 5.1.5 + resolution: "nanoid@npm:5.1.5" + bin: + nanoid: bin/nanoid.js + checksum: 6de2d006b51c983be385ef7ee285f7f2a57bd96f8c0ca881c4111461644bd81fafc2544f8e07cb834ca0f3e0f3f676c1fe78052183f008b0809efe6e273119f5 + languageName: node + linkType: hard + "nanoid@npm:^3.1.31, nanoid@npm:^3.3.6": version: 3.3.6 resolution: "nanoid@npm:3.3.6" @@ -29367,7 +30251,7 @@ __metadata: languageName: node linkType: hard -"negotiator@npm:^0.6.3": +"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" checksum: b8ffeb1e262eff7968fc90a2b6767b04cfd9842582a9d0ece0af7049537266e7b2506dfb1d107a32f06dd849ab2aea834d5830f7f4d0e5cb7d36e1ae55d021d9 @@ -29582,13 +30466,6 @@ __metadata: languageName: node linkType: hard -"node-mock-http@npm:^1.0.4": - version: 1.0.4 - resolution: "node-mock-http@npm:1.0.4" - checksum: 965f7915d3bbe9cbf5555dca12800cb0ca78a0b8db9c8f33a0ea6d9447fd200a925232b5ddf0d60c7112785ea282eb9baa079bb53d485b2b554355e8559f98ed - languageName: node - linkType: hard - "node-polyglot@npm:^2.4.0": version: 2.5.0 resolution: "node-polyglot@npm:2.5.0" @@ -29989,6 +30866,15 @@ __metadata: languageName: node linkType: hard +"on-finished@npm:~2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: 1.1.1 + checksum: d20929a25e7f0bb62f937a425b5edeb4e4cde0540d77ba146ec9357f00b0d497cdb3b9b05b9c8e46222407d1548d08166bff69cc56dfa55ba0e4469228920ff0 + languageName: node + linkType: hard + "once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -30049,6 +30935,15 @@ __metadata: languageName: node linkType: hard +"openapi3-ts@npm:^4.1.2": + version: 4.5.0 + resolution: "openapi3-ts@npm:4.5.0" + dependencies: + yaml: ^2.8.0 + checksum: 3a8609c8b00aed06dbc2fb8d203f86405ea3e45d7988432d2d0e4db7aa81b85ef3136d0acfaa7fcd47dadbf749d0e9f14d1b2f6ec79c9b2d174b02489f788aac + languageName: node + linkType: hard + "optionator@npm:^0.9.3": version: 0.9.3 resolution: "optionator@npm:0.9.3" @@ -30440,6 +31335,13 @@ __metadata: languageName: node linkType: hard +"parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 + languageName: node + linkType: hard + "pascal-case@npm:^3.1.2": version: 3.1.2 resolution: "pascal-case@npm:3.1.2" @@ -30544,6 +31446,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:~0.1.12": + version: 0.1.12 + resolution: "path-to-regexp@npm:0.1.12" + checksum: ab237858bee7b25ecd885189f175ab5b5161e7b712b360d44f5c4516b8d271da3e4bf7bf0a7b9153ecb04c7d90ce8ff5158614e1208819cf62bac2b08452722e + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -31050,6 +31959,16 @@ __metadata: languageName: node linkType: hard +"proxy-addr@npm:~2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + checksum: 29c6990ce9364648255454842f06f8c46fcd124d3e6d7c5066df44662de63cdc0bad032e9bf5a3d653ff72141cc7b6019873d685708ac8210c30458ad99f2b74 + languageName: node + linkType: hard + "proxy-compare@npm:2.5.1": version: 2.5.1 resolution: "proxy-compare@npm:2.5.1" @@ -31274,7 +32193,7 @@ pvutils@latest: languageName: node linkType: hard -"qs@npm:^6.14.0, qs@npm:^6.14.1": +"qs@npm:^6.14.0, qs@npm:^6.14.1, qs@npm:~6.14.0": version: 6.14.1 resolution: "qs@npm:6.14.1" dependencies: @@ -31373,10 +32292,10 @@ pvutils@latest: languageName: node linkType: hard -"radix3@npm:^1.1.2": - version: 1.1.2 - resolution: "radix3@npm:1.1.2" - checksum: c4d49a3f603b5b7b7704dd907383c8884d12064d6d475f7ca8b05ecc7604d3bd73524b55e0fbcca0f7c9da3a2e9b473a6b4fbc0b639c29c2b0e85020ebda67d3 +"radix3@npm:^1.1.0": + version: 1.1.0 + resolution: "radix3@npm:1.1.0" + checksum: e5e6ed8fcf68be4d124bca4f7da7ba0fc7c5b6f9e98bc3f4424459c45d50f1f92506c5f7f8421b5cfee5823c524a4a2cef416053e88845813ce9fc9c7086729a languageName: node linkType: hard @@ -31399,6 +32318,25 @@ pvutils@latest: languageName: node linkType: hard +"range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 0a268d4fea508661cf5743dfe3d5f47ce214fd6b7dec1de0da4d669dd4ef3d2144468ebe4179049eff253d9d27e719c88dae55be64f954e80135a0cada804ec9 + languageName: node + linkType: hard + +"raw-body@npm:~2.5.3": + version: 2.5.3 + resolution: "raw-body@npm:2.5.3" + dependencies: + bytes: ~3.1.2 + http-errors: ~2.0.1 + iconv-lite: ~0.4.24 + unpipe: ~1.0.0 + checksum: 16aa51e504318ebeef7f84a4d884c0f273cb0b7f3f14ea88788f92f5f488870617c97d4f886e84f119f21a2d6cdda3c4554821f8b18ed6be0d731ecb5a063d2a + languageName: node + linkType: hard + "re-reselect@npm:^4.0.0": version: 4.0.0 resolution: "re-reselect@npm:4.0.0" @@ -33241,7 +34179,7 @@ pvutils@latest: languageName: node linkType: hard -"safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 @@ -33525,6 +34463,39 @@ pvutils@latest: languageName: node linkType: hard +"send@npm:~0.19.0, send@npm:~0.19.1": + version: 0.19.2 + resolution: "send@npm:0.19.2" + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: ~2.0.0 + escape-html: ~1.0.3 + etag: ~1.8.1 + fresh: ~0.5.2 + http-errors: ~2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: ~2.4.1 + range-parser: ~1.2.1 + statuses: ~2.0.2 + checksum: f9e11b718b48dbea72daa6a80e36e5a00fb6d01b1a6cfda8b3135c9ca9db84257738283da23371f437148ccd8f400e6171cd2a3642fb43fda462da407d9d30c0 + languageName: node + linkType: hard + +"serve-static@npm:~1.16.2": + version: 1.16.3 + resolution: "serve-static@npm:1.16.3" + dependencies: + encodeurl: ~2.0.0 + escape-html: ~1.0.3 + parseurl: ~1.3.3 + send: ~0.19.1 + checksum: ec7599540215e6676b223ea768bf7c256819180bf14f89d0b5d249a61bbb8f10b05b2a53048a153cb2cc7f3b367f1227d2fb715fe4b09d07299a9233eda1a453 + languageName: node + linkType: hard + "ses@npm:^0.18.7": version: 0.18.7 resolution: "ses@npm:0.18.7" @@ -33606,7 +34577,7 @@ pvutils@latest: languageName: node linkType: hard -"setprototypeof@npm:1.2.0": +"setprototypeof@npm:1.2.0, setprototypeof@npm:~1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" checksum: be18cbbf70e7d8097c97f713a2e76edf84e87299b40d085c6bf8b65314e994cc15e2e317727342fa6996e38e1f52c59720b53fe621e2eb593a6847bf0356db89 @@ -34178,6 +35149,13 @@ pvutils@latest: languageName: node linkType: hard +"statuses@npm:~2.0.1, statuses@npm:~2.0.2": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 6927feb50c2a75b2a4caab2c565491f7a93ad3d8dbad7b1398d52359e9243a20e2ebe35e33726dee945125ef7a515e9097d8a1b910ba2bbd818265a2f6c39879 + languageName: node + linkType: hard + "std-env@npm:^3.4.3": version: 3.6.0 resolution: "std-env@npm:3.6.0" @@ -34683,16 +35661,16 @@ pvutils@latest: linkType: hard "tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.2.1 - resolution: "tar@npm:6.2.1" + version: 6.1.13 + resolution: "tar@npm:6.1.13" dependencies: chownr: ^2.0.0 fs-minipass: ^2.0.0 - minipass: ^5.0.0 + minipass: ^4.0.0 minizlib: ^2.1.1 mkdirp: ^1.0.3 yallist: ^4.0.0 - checksum: f1322768c9741a25356c11373bce918483f40fa9a25c69c59410c8a1247632487edef5fe76c5f12ac51a6356d2f1829e96d2bc34098668a2fc34d76050ac2b6c + checksum: 8a278bed123aa9f53549b256a36b719e317c8b96fe86a63406f3c62887f78267cea9b22dc6f7007009738509800d4a4dccc444abd71d762287c90f35b002eb1c languageName: node linkType: hard @@ -35022,7 +36000,7 @@ pvutils@latest: languageName: node linkType: hard -"toidentifier@npm:1.0.1": +"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45 @@ -35336,6 +36314,22 @@ pvutils@latest: languageName: node linkType: hard +"tsx@npm:^4.19.2": + version: 4.21.0 + resolution: "tsx@npm:4.21.0" + dependencies: + esbuild: ~0.27.0 + fsevents: ~2.3.3 + get-tsconfig: ^4.7.5 + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 50c98e4b6e66d1c30f72925c8e5e7be1a02377574de7cd367d7e7a6d4af43ca8ff659f91c654e7628b25a5498015e32f090529b92c679b0342811e1cf682e8cf + languageName: node + linkType: hard + "tty-browserify@npm:0.0.1": version: 0.0.1 resolution: "tty-browserify@npm:0.0.1" @@ -35396,6 +36390,15 @@ pvutils@latest: languageName: node linkType: hard +"type-fest@npm:5.0.0": + version: 5.0.0 + resolution: "type-fest@npm:5.0.0" + dependencies: + tagged-tag: ^1.0.0 + checksum: 2ae9bc0a5401b2f2061deb8f12c3ce175f8f6ad979c16b9f540fad40b447e035f149ff158e50dbcc0ca7e4d0ac6ba06659f9bf95e366f3dcda0079e76be08af9 + languageName: node + linkType: hard + "type-fest@npm:5.0.1": version: 5.0.1 resolution: "type-fest@npm:5.0.1" @@ -35454,6 +36457,16 @@ pvutils@latest: languageName: node linkType: hard +"type-is@npm:~1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: 0.3.0 + mime-types: ~2.1.24 + checksum: 2c8e47675d55f8b4e404bcf529abdf5036c537a04c2b20177bcf78c9e3c1da69da3942b1346e6edb09e823228c0ee656ef0e033765ec39a70d496ef601a0c657 + languageName: node + linkType: hard + "type@npm:^1.0.1": version: 1.2.0 resolution: "type@npm:1.2.0" @@ -35667,6 +36680,16 @@ pvutils@latest: languageName: node linkType: hard +"typescript@npm:^5.7.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 0d0ffb84f2cd072c3e164c79a2e5a1a1f4f168e84cb2882ff8967b92afe1def6c2a91f6838fb58b168428f9458c57a2ba06a6737711fdd87a256bbe83e9a217f + languageName: node + linkType: hard + "typescript@patch:typescript@<4.8.0#~builtin, typescript@patch:typescript@^4.4.3#~builtin": version: 4.7.4 resolution: "typescript@patch:typescript@npm%3A4.7.4#~builtin::version=4.7.4&hash=65a307" @@ -35697,6 +36720,16 @@ pvutils@latest: languageName: node linkType: hard +"typescript@patch:typescript@^5.7.3#~builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#~builtin::version=5.9.3&hash=85af82" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 8bb8d86819ac86a498eada254cad7fb69c5f74778506c700c2a712daeaff21d3a6f51fd0d534fe16903cb010d1b74f89437a3d02d4d0ff5ca2ba9a4660de8497 + languageName: node + linkType: hard + "ua-is-frozen@npm:^0.1.2": version: 0.1.2 resolution: "ua-is-frozen@npm:0.1.2" @@ -35733,20 +36766,13 @@ pvutils@latest: languageName: node linkType: hard -"ufo@npm:^1.3.0, ufo@npm:^1.3.1": +"ufo@npm:^1.3.0, ufo@npm:^1.3.1, ufo@npm:^1.3.2": version: 1.3.2 resolution: "ufo@npm:1.3.2" checksum: f1180bb715ff4dd46152fd4dec41c731e84d7b9eaf1432548a0210b2f7e0cd29de125ac88e582c6a079d8ae5bc9ab04ef2bdbafe125086480b10c1006b81bfce languageName: node linkType: hard -"ufo@npm:^1.6.3": - version: 1.6.3 - resolution: "ufo@npm:1.6.3" - checksum: a23eff86bbbef0b9cc69c19c653c703b656c2328938576d3a60e05e246ef5a78d88b17c710afa146311c5b855950ccfee60ba8f6c8845e8d1ed6b5a9086ddad1 - languageName: node - linkType: hard - "uglify-js@npm:^3.1.4": version: 3.17.4 resolution: "uglify-js@npm:3.17.4" @@ -35856,6 +36882,19 @@ pvutils@latest: languageName: node linkType: hard +"unenv@npm:^1.7.4": + version: 1.8.0 + resolution: "unenv@npm:1.8.0" + dependencies: + consola: ^3.2.3 + defu: ^6.1.3 + mime: ^3.0.0 + node-fetch-native: ^1.4.1 + pathe: ^1.1.1 + checksum: 8118260ac7be8d13f99ebb5ca6a40203cf64cc592142753c1a1f75d40b07398c3208430d9a0299573cef3e4f2eba3bf6735901ff4217583df8344b2491314fb0 + languageName: node + linkType: hard + "unfetch@npm:^4.2.0": version: 4.2.0 resolution: "unfetch@npm:4.2.0" @@ -35947,6 +36986,13 @@ pvutils@latest: languageName: node linkType: hard +"unpipe@npm:~1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 + languageName: node + linkType: hard + "unplugin@npm:2.1.0": version: 2.1.0 resolution: "unplugin@npm:2.1.0" @@ -36288,6 +37334,13 @@ pvutils@latest: languageName: node linkType: hard +"utils-merge@npm:1.0.1": + version: 1.0.1 + resolution: "utils-merge@npm:1.0.1" + checksum: c81095493225ecfc28add49c106ca4f09cdf56bc66731aa8dabc2edbbccb1e1bfe2de6a115e5c6a380d3ea166d1636410b62ef216bb07b3feb1cfde1d95d5080 + languageName: node + linkType: hard + "uuid@npm:8.3.2, uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" @@ -36489,7 +37542,7 @@ pvutils@latest: languageName: node linkType: hard -"vary@npm:^1": +"vary@npm:^1, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: ae0123222c6df65b437669d63dfa8c36cee20a504101b2fcd97b8bf76f91259c17f9f2b4d70a1e3c6bbcee7f51b28392833adb6b2770b23b01abec84e369660b @@ -37783,6 +38836,15 @@ pvutils@latest: languageName: node linkType: hard +"yaml@npm:^2.8.0, yaml@npm:^2.8.2": + version: 2.8.2 + resolution: "yaml@npm:2.8.2" + bin: + yaml: bin.mjs + checksum: 5ffd9f23bc7a450129cbd49dcf91418988f154ede10c83fd28ab293661ac2783c05da19a28d76a22cbd77828eae25d4bd7453f9a9fe2d287d085d72db46fd105 + languageName: node + linkType: hard + "yamljs@npm:^0.3.0": version: 0.3.0 resolution: "yamljs@npm:0.3.0" @@ -37955,6 +39017,13 @@ pvutils@latest: languageName: node linkType: hard +"zod@npm:3.23.8": + version: 3.23.8 + resolution: "zod@npm:3.23.8" + checksum: 15949ff82118f59c893dacd9d3c766d02b6fa2e71cf474d5aa888570c469dbf5446ac5ad562bb035bf7ac9650da94f290655c194f4a6de3e766f43febd432c5c + languageName: node + linkType: hard + "zod@npm:^3.21.4, zod@npm:^3.23.8": version: 3.25.76 resolution: "zod@npm:3.25.76" @@ -37962,6 +39031,13 @@ pvutils@latest: languageName: node linkType: hard +"zod@npm:^4.1.11": + version: 4.3.5 + resolution: "zod@npm:4.3.5" + checksum: 68691183a91c67c4102db20139f3b5af288c59b4b11eb2239d712aae99dc6c1cecaeebcb0c012b44489771be05fecba21e79f65af4b3163b220239ef0af3ec49 + languageName: node + linkType: hard + "zod@npm:^4.2.1": version: 4.2.1 resolution: "zod@npm:4.2.1"