diff --git a/examples/swapper-demo/README.md b/examples/swapper-demo/README.md new file mode 100644 index 00000000..8cd871b0 --- /dev/null +++ b/examples/swapper-demo/README.md @@ -0,0 +1,157 @@ +# SwapperSDK Demo App + +A React application demonstrating the SwapperSDK capabilities with cross-chain swaps and bridge functionality using the Phantom React SDK. + +## Features + +- **Phantom React SDK Integration**: Connect using injected wallet mode with React hooks +- **Multi-network Support**: View addresses for Solana, Ethereum, Bitcoin +- **Quote Results Display**: Shows successful quote retrievals from SwapperSDK +- **Preconfigured Swaps**: + - SOL ↔ USDC swaps on Solana + - Customizable amounts +- **Cross-chain Bridges**: + - USDC bridging between Ethereum ↔ Solana + - Real-time bridge quote fetching +- **Responsive UI**: Sidebar layout with live quote display + +## Prerequisites + +- Node.js (v16 or higher) +- Yarn workspace setup +- Phantom Browser Extension installed + +## Installation + +From the root of the wallet-sdk monorepo: + +```bash +# Install dependencies +yarn install + +# Navigate to the demo +cd packages/swapper-sdk/examples/swapper-demo + +# Start development server +yarn dev +``` + +The app will be available at `http://localhost:5173` + +## Usage + +### 1. Connect Wallet +- Click "Connect Phantom" to connect your wallet +- The sidebar will show all your wallet addresses across different networks + +### 2. Perform Swaps +- **USDC to SOL**: Enter USDC amount and click swap button +- **SOL to USDC**: Enter SOL amount and click swap button +- View real-time quotes in the main content area + +### 3. Cross-chain Bridges +- Requires both Ethereum and Solana addresses +- **ETH → SOL**: Bridge USDC from Ethereum to Solana +- **SOL → ETH**: Bridge USDC from Solana to Ethereum + +### 4. Live Quote Streaming +- Automatically streams USDC/SOL quotes when connected +- Shows provider, amounts, price impact, and slippage +- Updates in real-time with latest market data + +## Architecture + +``` +src/ +├── components/ +│ ├── WalletSidebar.tsx # Left sidebar with wallet info +│ ├── SwapperControls.tsx # Swap & bridge controls +│ └── QuoteStreaming.tsx # Quote results display +├── App.tsx # Main layout & React SDK config +├── App.css # Component styling +└── main.tsx # React entry point +``` + +## Key Components + +### React SDK Integration +- Uses @phantom/react-sdk PhantomProvider and hooks +- Configured for injected wallet mode only +- Handles wallet connection state automatically + +### SwapperControls +- Preconfigured swap buttons (SOL ↔ USDC) +- Bridge functionality (ETH ↔ SOL USDC) +- Integration with SwapperSDK for quotes + +### QuoteStreaming +- Real-time quote streaming using SwapperSDK +- Displays live market data +- Shows quote history with timestamps + +## Configuration + +The app uses production API endpoints: +- **SwapperSDK**: `https://api.phantom.app` +- **Debug Mode**: Enabled for development + +To modify: +```typescript +const swapperSDK = new SwapperSDK({ + apiUrl: 'https://api.phantom.app', + options: { debug: true } +}) +``` + +## Development + +### Building +```bash +yarn build +``` + +### Linting +```bash +yarn lint +``` + +### Preview Production Build +```bash +yarn preview +``` + +## Token Support + +The demo includes preconfigured tokens from `@phantom/swapper-sdk`: +- **Solana**: SOL, USDC, USDT, WSOL +- **Ethereum**: ETH, USDC, USDT, WETH +- **Base**: ETH, USDC, WETH +- **Polygon**: MATIC, USDC, USDT, WETH +- **Arbitrum**: ETH, USDC, USDT, WETH + +## Troubleshooting + +### Wallet Connection Issues +- Ensure Phantom extension is installed and unlocked +- Check browser console for connection errors +- Try refreshing the page + +### Quote Streaming Problems +- Verify wallet is connected with Solana address +- Check network connectivity +- Look for API errors in browser console + +### Bridge Requirements +- Both Ethereum and Solana addresses needed +- Sufficient token balances required +- Network fees apply for cross-chain transactions + +## Next Steps + +This demo provides a foundation for: +- Custom swap interfaces +- Multi-chain DeFi applications +- Real-time price monitoring +- Cross-chain bridge aggregation + +Extend the codebase by adding more token pairs, custom slippage controls, transaction history, or additional network support. \ No newline at end of file diff --git a/examples/swapper-demo/index.html b/examples/swapper-demo/index.html new file mode 100644 index 00000000..b2c76754 --- /dev/null +++ b/examples/swapper-demo/index.html @@ -0,0 +1,13 @@ + + + + + + + SwapperSDK Demo + + +
+ + + \ No newline at end of file diff --git a/examples/swapper-demo/package.json b/examples/swapper-demo/package.json new file mode 100644 index 00000000..0414ace9 --- /dev/null +++ b/examples/swapper-demo/package.json @@ -0,0 +1,35 @@ +{ + "name": "swapper-demo", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "predev": "yarn workspace @phantom/swapper-sdk build", + "dev": "vite", + "prebuild": "yarn workspace @phantom/swapper-sdk build", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@phantom/react-sdk": "workspace:^", + "@phantom/swapper-sdk": "workspace:^", + "@solana/web3.js": "^1.98.4", + "bs58": "^6.0.0", + "buffer": "^6.0.3", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/examples/swapper-demo/src/App.css b/examples/swapper-demo/src/App.css new file mode 100644 index 00000000..d15c4e41 --- /dev/null +++ b/examples/swapper-demo/src/App.css @@ -0,0 +1,511 @@ +.app { + display: flex; + height: 100vh; +} + +.sidebar { + width: 350px; + background-color: #1a1a1a; + border-right: 1px solid #333; + padding: 1.5rem; + overflow-y: auto; +} + +.main-content { + flex: 1; + padding: 2rem; + overflow-y: auto; +} + +.header { + margin-bottom: 2rem; +} + +.header h1 { + margin: 0; + color: #646cff; + font-size: 2.5rem; +} + +.header p { + margin: 0.5rem 0 0 0; + color: #888; + font-size: 1.1rem; +} + +.connect-section { + text-align: center; + padding: 2rem 0; +} + +.connect-button { + background: linear-gradient(135deg, #646cff 0%, #747bff 100%); + border: none; + padding: 1rem 2rem; + font-size: 1.1rem; + border-radius: 12px; + cursor: pointer; + color: white; + font-weight: 600; + transition: all 0.2s ease; +} + +.connect-button:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(116, 123, 255, 0.3); +} + +.wallet-info h3 { + margin: 0 0 1rem 0; + color: #646cff; +} + +.addresses-list { + margin-bottom: 2rem; +} + +.address-item { + background: #2a2a2a; + padding: 1rem; + border-radius: 8px; + margin-bottom: 0.5rem; + border: 1px solid #333; +} + +.address-item h4 { + margin: 0 0 0.5rem 0; + color: #646cff; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.address-item p { + margin: 0; + font-family: monospace; + font-size: 0.85rem; + color: #ccc; + word-break: break-all; +} + +.swapper-section h3 { + margin: 2rem 0 1rem 0; + color: #646cff; + border-top: 1px solid #333; + padding-top: 2rem; +} + +.swap-controls { + margin-bottom: 2rem; +} + +.swap-input-group { + margin-bottom: 1rem; +} + +.swap-input-group label { + display: block; + margin-bottom: 0.5rem; + color: #ccc; + font-size: 0.9rem; +} + +.swap-input-group input { + width: 100%; + margin-bottom: 0.5rem; +} + +.swap-buttons { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.swap-button { + background: linear-gradient(135deg, #4ecdc4 0%, #44a08d 100%); + border: none; + padding: 0.8rem; + border-radius: 8px; + color: white; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.swap-button:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 15px rgba(78, 205, 196, 0.3); +} + +.bridge-section h4 { + margin: 2rem 0 1rem 0; + color: #ff7b7b; + font-size: 1rem; +} + +.bridge-buttons { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.bridge-button { + background: linear-gradient(135deg, #ff7b7b 0%, #ff6b6b 100%); + border: none; + padding: 0.8rem; + border-radius: 8px; + color: white; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.bridge-button:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3); +} + +.quote-container { + background: #1a1a1a; + border-radius: 12px; + border: 1px solid #333; + padding: 1.5rem; +} + +.quote-container h2 { + margin: 0 0 1rem 0; + color: #646cff; +} + +.quotes-list { + max-height: 500px; + overflow-y: auto; +} + +.quote-item { + background: #2a2a2a; + border: 1px solid #444; + border-radius: 8px; + padding: 1rem; + margin-bottom: 1rem; +} + +.quote-item h4 { + margin: 0 0 0.5rem 0; + color: #4ecdc4; +} + +.quote-item p { + margin: 0.25rem 0; + font-size: 0.9rem; + color: #ccc; +} + +.quote-item strong { + color: #fff; +} + +.loading { + text-align: center; + color: #888; + font-style: italic; +} + +.error-message { + background: #2d1b1b; + border: 1px solid #ff6b6b; + color: #ff6b6b; + padding: 1rem; + border-radius: 8px; + margin: 1rem 0; +} + +/* Quote components styles */ +.quotes-list-container { + background: #1a1a1a; + border-radius: 12px; + border: 1px solid #333; + padding: 1.5rem; +} + +.quotes-header { + margin-bottom: 2rem; + border-bottom: 1px solid #333; + padding-bottom: 1rem; +} + +.quotes-header h2 { + margin: 0 0 0.5rem 0; + color: #646cff; + font-size: 1.8rem; +} + +.swap-summary p { + margin: 0.25rem 0; + color: #ccc; + font-size: 1rem; +} + +.swap-amount { + font-weight: bold; + color: #4ecdc4 !important; +} + +.quotes-list { + display: flex; + flex-direction: column; + gap: 1rem; + margin-bottom: 2rem; +} + +.quote-card { + background: #2a2a2a; + border: 2px solid #444; + border-radius: 12px; + padding: 1.5rem; + transition: all 0.2s ease; + position: relative; +} + +.quote-card:hover { + border-color: #646cff; + transform: translateY(-2px); + box-shadow: 0 4px 20px rgba(100, 108, 255, 0.1); +} + +.quote-card-best { + border-color: #4ecdc4; + background: linear-gradient(135deg, #2a2a2a 0%, #2d3d3a 100%); +} + +.quote-card-best::before { + content: "BEST RATE"; + position: absolute; + top: -8px; + right: 16px; + background: #4ecdc4; + color: #1a1a1a; + padding: 4px 12px; + font-size: 0.7rem; + font-weight: bold; + border-radius: 4px; + letter-spacing: 0.5px; +} + +.quote-card-selected { + border-color: #ff7b7b; + background: linear-gradient(135deg, #2a2a2a 0%, #3d2d2d 100%); +} + +.quote-provider { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.quote-provider h4 { + margin: 0; + color: #4ecdc4; + font-size: 1.1rem; + font-weight: 600; +} + +.quote-provider-badge { + background: #333; + color: #ccc; + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.quote-details { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + margin-bottom: 1.5rem; +} + +.quote-amounts { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.quote-amounts .sell-amount, +.quote-amounts .buy-amount { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.quote-amounts .label { + font-size: 0.8rem; + color: #888; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.quote-amounts .amount { + font-size: 1rem; + font-weight: 600; + font-family: monospace; +} + +.sell-amount .amount { + color: #ff7b7b; +} + +.buy-amount .amount { + color: #4ecdc4; +} + +.quote-info p { + margin: 0.25rem 0; + font-size: 0.85rem; + color: #888; +} + +.quote-actions { + display: flex; + gap: 1rem; + align-items: center; +} + +.execute-button { + background: linear-gradient(135deg, #646cff 0%, #747bff 100%); + border: none; + padding: 0.8rem 1.5rem; + border-radius: 8px; + color: white; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + flex: 1; +} + +.execute-button:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 15px rgba(116, 123, 255, 0.3); +} + +.execute-button:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +.quote-debug { + background: #1a1a1a; + border: 1px solid #333; + border-radius: 8px; + padding: 1rem; + margin-top: 1rem; +} + +.quote-debug h5 { + margin: 0 0 0.5rem 0; + color: #888; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.quote-debug pre { + background: #111; + border: 1px solid #222; + border-radius: 4px; + padding: 0.5rem; + margin: 0; + font-size: 0.75rem; + color: #ccc; + overflow-x: auto; + white-space: pre-wrap; + word-break: break-all; +} + +.quotes-info { + text-align: center; + padding-top: 1rem; + border-top: 1px solid #333; +} + +.quotes-info p { + margin: 0; + color: #888; + font-size: 0.9rem; +} + +.quotes-empty { + text-align: center; + padding: 3rem 2rem; + color: #888; +} + +.quotes-empty h3 { + margin: 0 0 1rem 0; + color: #646cff; +} + +.quotes-empty p { + margin: 0; + font-size: 1.1rem; +} + +/* Success and error cards */ +.card.success { + background: linear-gradient(135deg, #1b3d1b 0%, #2d4a2d 100%); + border: 1px solid #4ecdc4; + color: #4ecdc4; + padding: 1rem; + border-radius: 8px; + margin-bottom: 1rem; +} + +.card.error { + background: linear-gradient(135deg, #3d1b1b 0%, #4a2d2d 100%); + border: 1px solid #ff7b7b; + color: #ff7b7b; + padding: 1rem; + border-radius: 8px; + margin-bottom: 1rem; +} + +.card h3 { + margin: 0 0 0.5rem 0; + font-size: 1rem; +} + +.card p { + margin: 0; + font-size: 0.9rem; +} + +.wallet-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; +} + +@media (max-width: 768px) { + .app { + flex-direction: column; + } + + .sidebar { + width: 100%; + height: auto; + } + + .quote-details { + grid-template-columns: 1fr; + } + + .quote-actions { + flex-direction: column; + } +} \ No newline at end of file diff --git a/examples/swapper-demo/src/App.tsx b/examples/swapper-demo/src/App.tsx new file mode 100644 index 00000000..00f41e21 --- /dev/null +++ b/examples/swapper-demo/src/App.tsx @@ -0,0 +1,87 @@ +import { useState } from 'react' +import { PhantomProvider, AddressType, type PhantomSDKConfig } from '@phantom/react-sdk' +import { WalletSidebar } from './components/WalletSidebar' +import { QuoteStreaming } from './components/QuoteStreaming' +import type { SwapperSolanaQuoteRepresentation, SwapperEvmQuoteRepresentation, SwapperXChainQuoteRepresentation } from '@phantom/swapper-sdk' +import './App.css' + +type Quote = SwapperSolanaQuoteRepresentation | SwapperEvmQuoteRepresentation | SwapperXChainQuoteRepresentation + +interface QuotesData { + quotes: Quote[] + swapInfo: { + sellToken: string + buyToken: string + sellAmount: string + } + type?: string +} + +// Configuration for injected provider (Phantom extension) +const config: PhantomSDKConfig = { + appName: "SwapperSDK Demo", + providerType: "injected", // Use injected mode only as requested + addressTypes: [AddressType.solana, AddressType.ethereum, AddressType.bitcoinSegwit], + + // Enable debug for development + debug: { + enabled: true, + }, +} + +function App() { + const [quotesData, setQuotesData] = useState(null) + const [transactionStatus, setTransactionStatus] = useState<{ + type: 'success' | 'error' | null + message: string + }>({ type: null, message: '' }) + + const handleQuotesReceived = (quotes: Quote[], swapInfo: QuotesData['swapInfo'], quotesType?: string) => { + setQuotesData({ quotes, swapInfo, type: quotesType }) + setTransactionStatus({ type: null, message: '' }) // Clear previous status + } + + const handleTransactionComplete = (txHash: string) => { + setTransactionStatus({ + type: 'success', + message: `Transaction successful! Hash: ${txHash.substring(0, 10)}...` + }) + } + + const handleTransactionError = (error: string) => { + setTransactionStatus({ + type: 'error', + message: error + }) + } + + const clearQuotes = () => { + setQuotesData(null) + setTransactionStatus({ type: null, message: '' }) + } + + return ( + +
+ +
+
+

SwapperSDK Demo

+

Get quotes and execute swaps with transaction signing

+
+ +
+
+
+ ) +} + +export default App \ No newline at end of file diff --git a/examples/swapper-demo/src/components/QuoteCard.tsx b/examples/swapper-demo/src/components/QuoteCard.tsx new file mode 100644 index 00000000..ca02e2d7 --- /dev/null +++ b/examples/swapper-demo/src/components/QuoteCard.tsx @@ -0,0 +1,174 @@ +import React from 'react' +import type { SwapperSolanaQuoteRepresentation, SwapperEvmQuoteRepresentation, SwapperXChainQuoteRepresentation } from '@phantom/swapper-sdk' + +type Quote = SwapperSolanaQuoteRepresentation | SwapperEvmQuoteRepresentation | SwapperXChainQuoteRepresentation + +interface QuoteCardProps { + quote: Quote + isFirst: boolean + onSelect: (quote: Quote) => void + isExecuting: boolean + selectedQuoteId?: string + swapInfo?: { + sellToken: string + buyToken: string + sellAmount: string + } + quotesType?: string +} + +export const QuoteCard: React.FC = ({ + quote, + isFirst, + onSelect, + isExecuting, + selectedQuoteId, + swapInfo, + quotesType +}) => { + const formatAmount = (amount: string, decimals: number, symbol: string) => { + const value = parseFloat(amount) / Math.pow(10, decimals) + return `${value.toFixed(decimals === 9 ? 4 : decimals === 6 ? 2 : 2)} ${symbol}` + } + + // Determine token info based on swap direction + const getTokenInfo = (tokenSymbol: string) => { + switch (tokenSymbol.toUpperCase()) { + case 'SOL': + return { decimals: 9, symbol: 'SOL' } + case 'USDC': + return { decimals: 6, symbol: 'USDC' } + default: + // Default to 6 decimals for unknown tokens + return { decimals: 6, symbol: tokenSymbol } + } + } + + const formatPercentage = (value: number) => { + return `${(value * 100).toFixed(2)}%` + } + + const getQuoteType = () => { + // Use the quotesType from the response + if (quotesType) { + switch (quotesType) { + case 'solana': + return 'Solana' + case 'eip155': + return 'EVM' + case 'xchain': + return 'Cross-Chain' + case 'sui': + return 'Sui' + default: + return 'Unknown' + } + } + + // Fallback to old logic + if ('transactionData' in quote) { + return Array.isArray(quote.transactionData) ? 'Solana' : 'EVM' + } else if ('steps' in quote) { + return 'Cross-Chain' + } + return 'Unknown' + } + + const getProviderName = () => { + return quote.baseProvider?.name || quote.baseProvider?.id || 'Unknown Provider' + } + + const isSelected = selectedQuoteId === quote.baseProvider?.id + + return ( +
+
+
+

{getProviderName()}

+ {getQuoteType()} + {isFirst && Best Quote} +
+
+
+ You Pay: + + {swapInfo ? ( + formatAmount(quote.sellAmount, getTokenInfo(swapInfo.sellToken).decimals, getTokenInfo(swapInfo.sellToken).symbol) + ) : ( + formatAmount(quote.sellAmount, 6, 'USDC') + )} + +
+
+ You Get: + + {swapInfo ? ( + formatAmount(quote.buyAmount, getTokenInfo(swapInfo.buyToken).decimals, getTokenInfo(swapInfo.buyToken).symbol) + ) : ( + formatAmount(quote.buyAmount, 9, 'SOL') + )} + +
+
+
+ +
+ {quote.priceImpact !== undefined && ( +
+ Price Impact: + 0.01 ? 'negative' : ''}>{formatPercentage(quote.priceImpact)} +
+ )} + + {quote.slippageTolerance !== undefined && ( +
+ Slippage: + {formatPercentage(quote.slippageTolerance)} +
+ )} + + {/* EVM specific details */} + {'gas' in quote && ( +
+ Gas: + {quote.gas?.toLocaleString() || 'N/A'} +
+ )} + + {/* Cross-chain specific details */} + {'executionDuration' in quote && quote.executionDuration && ( +
+ Est. Time: + {Math.round(quote.executionDuration / 60)} min +
+ )} + + {/* Fees information */} + {quote.fees && quote.fees.length > 0 && ( +
+ Fees: + {quote.fees.length} fee(s) +
+ )} +
+ +
+ +
+ + {/* Debug information */} +
+
+ Quote Details +
{JSON.stringify(quote, null, 2)}
+
+
+
+ ) +} \ No newline at end of file diff --git a/examples/swapper-demo/src/components/QuoteStreaming.tsx b/examples/swapper-demo/src/components/QuoteStreaming.tsx new file mode 100644 index 00000000..06503768 --- /dev/null +++ b/examples/swapper-demo/src/components/QuoteStreaming.tsx @@ -0,0 +1,123 @@ +import React from 'react' +import { usePhantom, useAccounts, AddressType } from '@phantom/react-sdk' +import { QuotesList } from './QuotesList' +import type { SwapperSolanaQuoteRepresentation, SwapperEvmQuoteRepresentation, SwapperXChainQuoteRepresentation } from '@phantom/react-sdk' + +type Quote = SwapperSolanaQuoteRepresentation | SwapperEvmQuoteRepresentation | SwapperXChainQuoteRepresentation + +interface QuotesData { + quotes: Quote[] + swapInfo: { + sellToken: string + buyToken: string + sellAmount: string + } + type?: string +} + +interface QuoteStreamingProps { + quotesData: QuotesData | null + transactionStatus: { + type: 'success' | 'error' | null + message: string + } + onTransactionComplete: (txHash: string) => void + onTransactionError: (error: string) => void +} + +export const QuoteStreaming: React.FC = ({ + quotesData, + transactionStatus, + onTransactionComplete, + onTransactionError +}) => { + const { isConnected } = usePhantom() + const addresses = useAccounts() || [] + + const solanaAddress = addresses.find(addr => addr.addressType === AddressType.solana)?.address + const ethereumAddress = addresses.find(addr => addr.addressType === AddressType.ethereum)?.address + + const formatAddress = (address: string) => { + if (!address) return 'Not available' + return `${address.slice(0, 8)}...${address.slice(-8)}` + } + + return ( +
+ {/* Transaction Status */} + {transactionStatus.type && ( +
+

{transactionStatus.type === 'success' ? '✅ Transaction Successful' : '❌ Transaction Failed'}

+

{transactionStatus.message}

+
+ )} + + {/* Quotes Display */} + {quotesData ? ( + + ) : ( + <> +

SwapperSDK Transaction Demo

+ + {!isConnected ? ( +
+ Connect your wallet to see quotes and execute swaps +
+ ) : ( +
+

Ready for Live Trading

+

Use the controls in the sidebar to:

+
    +
  • Get Real Quotes: Fetch live prices from DEXs and aggregators
  • +
  • Execute Swaps: Sign and submit transactions directly from quotes
  • +
  • Cross-Chain Bridge: Bridge USDC between Ethereum ↔ Solana
  • +
+ +
+

Your Connected Addresses:

+ {solanaAddress && ( +

Solana: {formatAddress(solanaAddress)}

+ )} + {ethereumAddress && ( +

Ethereum: {formatAddress(ethereumAddress)}

+ )} +
+
+ )} + +
+

SwapperSDK + React SDK Integration

+
+
+

Swap Features

+
    +
  • Live quote comparison
  • +
  • Multiple DEX aggregators
  • +
  • Price impact calculation
  • +
  • Slippage protection
  • +
  • Gas estimation
  • +
+
+
+

Transaction Flow

+
    +
  • 1. Click swap button
  • +
  • 2. Review quotes here
  • +
  • 3. Click "Execute Swap"
  • +
  • 4. Sign with Phantom
  • +
  • 5. Transaction complete!
  • +
+
+
+
+ + )} +
+ ) +} \ No newline at end of file diff --git a/examples/swapper-demo/src/components/QuotesList.tsx b/examples/swapper-demo/src/components/QuotesList.tsx new file mode 100644 index 00000000..93db09df --- /dev/null +++ b/examples/swapper-demo/src/components/QuotesList.tsx @@ -0,0 +1,178 @@ +import React, { useMemo, useState } from 'react' +import { useSignAndSendTransaction, usePhantom, AddressType } from '@phantom/react-sdk' +import { VersionedTransaction } from '@solana/web3.js' +import { getSolanaTransactionFromQuote, inspectSolanaTransaction } from '@phantom/swapper-sdk' +import { QuoteCard } from './QuoteCard' +import type { SwapperSolanaQuoteRepresentation, SwapperEvmQuoteRepresentation, SwapperXChainQuoteRepresentation } from '@phantom/swapper-sdk' + +type Quote = SwapperSolanaQuoteRepresentation | SwapperEvmQuoteRepresentation | SwapperXChainQuoteRepresentation + +interface QuotesListProps { + quotes: Quote[] + swapInfo: { + sellToken: string + buyToken: string + sellAmount: string + } + quotesType?: string // Add the type from the quotes response + onTransactionComplete: (txHash: string) => void + onTransactionError: (error: string) => void +} + +export const QuotesList: React.FC = ({ + quotes, + swapInfo, + quotesType, + onTransactionComplete, + onTransactionError +}) => { + const { addresses } = usePhantom() + const { signAndSendTransaction, isSigning } = useSignAndSendTransaction() + const [selectedQuoteId, setSelectedQuoteId] = useState(null) + + const targetAddress = useMemo(() => { + // Get the first address that matches the quotes type + if (quotesType) { + switch (quotesType) { + case 'solana': + return addresses.find(addr => addr.addressType === AddressType.solana)?.address + case 'eip155': + return addresses.find(addr => addr.addressType === AddressType.ethereum)?.address + case 'xchain': + // For cross-chain, we need to determine based on the first step + // Default to Solana for now + return addresses.find(addr => addr.addressType === AddressType.solana)?.address + case 'sui': + // Sui not supported in this demo + return undefined + default: + return addresses.find(addr => addr.addressType === AddressType.solana)?.address + } + } + return null; + }, [addresses, quotesType]) + + + const executeSwap = async (quote: Quote) => { + if (!targetAddress) { + onTransactionError('No compatible address found for this quote') + return + } + + setSelectedQuoteId(quote.baseProvider?.id || 'unknown') + + try { + let transactions: VersionedTransaction[] + let networkId: string + + // Extract and parse transaction data based on quotes type + if (!quotesType) { + throw new Error('Quotes type not available') + } + + switch (quotesType) { + case 'solana': + if (!('transactionData' in quote)) { + throw new Error('Invalid Solana quote: missing transaction data') + } + + console.log('Parsing Solana transaction with SwapperSDK utility...') + + // Use the SwapperSDK utility function to parse transactions + transactions = getSolanaTransactionFromQuote(quote as SwapperSolanaQuoteRepresentation) + + // Inspect the first transaction for debugging + if (transactions.length > 0) { + inspectSolanaTransaction(transactions[0], 0) + } + + networkId = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' // Solana mainnet + break + + case 'eip155': + // EVM transactions are handled differently - they stay as string data + throw new Error('EVM transactions not implemented in this demo') + + case 'xchain': + throw new Error('Multi-step cross-chain transactions are not supported in this demo') + + case 'sui': + throw new Error('Sui transactions are not supported in this demo') + + default: + throw new Error(`Unsupported quotes type: ${quotesType}`) + } + + console.log('Executing swap with parsed transactions:', { + transactionCount: transactions.length, + firstTransaction: 'VersionedTransaction with ' + transactions[0].message.compiledInstructions.length + ' instructions', + networkId, + quote: quote.baseProvider + }) + + // Execute the first transaction (most swaps only have one transaction) + // For multi-step swaps, we'd need to execute all transactions in sequence + const transactionToExecute = transactions[0] + + const result = await signAndSendTransaction({ + transaction: transactionToExecute, + networkId: networkId as any, + }) + + console.log('Transaction completed:', result) + onTransactionComplete(result.rawTransaction || 'Transaction completed') + + } catch (error: any) { + console.error('Swap execution failed:', error) + onTransactionError(`Swap failed: ${error.message || 'Unknown error'}`) + } finally { + setSelectedQuoteId(null) + } + } + + if (!quotes || quotes.length === 0) { + return ( +
+

No Quotes Available

+

Use the swap controls in the sidebar to get quotes.

+
+ ) + } + + return ( +
+
+

Available Quotes

+
+

+ {swapInfo.sellToken}{swapInfo.buyToken} +

+

Amount: {swapInfo.sellAmount}

+
+
+ +
+ {quotes.map((quote, index) => ( + + ))} +
+ +
+

+ {quotes.length} quote{quotes.length !== 1 ? 's' : ''} found • + Best rate is highlighted • + Click "Execute Swap" to proceed +

+
+
+ ) +} \ No newline at end of file diff --git a/examples/swapper-demo/src/components/SwapperControls.tsx b/examples/swapper-demo/src/components/SwapperControls.tsx new file mode 100644 index 00000000..ac4cceb8 --- /dev/null +++ b/examples/swapper-demo/src/components/SwapperControls.tsx @@ -0,0 +1,258 @@ +import React, { useState } from 'react' +import SwapperSDK, { TOKENS, NetworkId } from '@phantom/swapper-sdk' +import { usePhantom, AddressType } from '@phantom/react-sdk' +import type { WalletAddress, SwapperSolanaQuoteRepresentation, SwapperEvmQuoteRepresentation, SwapperXChainQuoteRepresentation } from '@phantom/react-sdk' + +type Quote = SwapperSolanaQuoteRepresentation | SwapperEvmQuoteRepresentation | SwapperXChainQuoteRepresentation + +interface SwapperControlsProps { + addresses: WalletAddress[] + onQuotesReceived: (quotes: Quote[], swapInfo: { + sellToken: string + buyToken: string + sellAmount: string + }, quotesType?: string) => void +} + +const swapperSDK = new SwapperSDK({ + apiUrl: 'https://api.phantom.app', + options: { debug: true } +}) + +export const SwapperControls: React.FC = ({ addresses, onQuotesReceived }) => { + const { sdk } = usePhantom() + const [usdcAmount, setUsdcAmount] = useState('10') + const [solAmount, setSolAmount] = useState('0.1') + const [isSwapping, setIsSwapping] = useState(false) + const [bridgeAmount, setBridgeAmount] = useState('5') + const [lastResult, setLastResult] = useState(null) + + // Get addresses for specific networks (addresses prop is already safe from parent) + const safeAddresses = addresses || [] + const solanaAddress = safeAddresses.find(addr => addr.addressType === AddressType.solana)?.address + const ethereumAddress = safeAddresses.find(addr => addr.addressType === AddressType.ethereum)?.address + + const handleSwap = async (swapType: 'usdc-to-sol' | 'sol-to-usdc') => { + if (!sdk || !solanaAddress) { + setLastResult('Error: Solana address not found') + return + } + + setIsSwapping(true) + setLastResult(null) + + try { + let sellToken, buyToken, sellAmount, displayPair + + if (swapType === 'usdc-to-sol') { + sellToken = TOKENS.SOLANA_MAINNET.USDC + buyToken = TOKENS.SOLANA_MAINNET.SOL + sellAmount = String(parseFloat(usdcAmount) * 1_000_000) // USDC has 6 decimals + displayPair = { sell: 'USDC', buy: 'SOL' } + } else { + sellToken = TOKENS.SOLANA_MAINNET.SOL + buyToken = TOKENS.SOLANA_MAINNET.USDC + sellAmount = String(parseFloat(solAmount) * 1_000_000_000) // SOL has 9 decimals + displayPair = { sell: 'SOL', buy: 'USDC' } + } + + // Get quotes + const quotes = await swapperSDK.getQuotes({ + sellToken, + buyToken, + sellAmount, + from: { + address: solanaAddress, + networkId: NetworkId.SOLANA_MAINNET, + }, + slippageTolerance: 0.5, + }) + + console.log('Swap quotes:', quotes) + + if (quotes.quotes && quotes.quotes.length > 0) { + // Pass quotes to main view + onQuotesReceived(quotes.quotes as Quote[], { + sellToken: displayPair.sell, + buyToken: displayPair.buy, + sellAmount: swapType === 'usdc-to-sol' ? usdcAmount + ' USDC' : solAmount + ' SOL' + }, quotes.type) + setLastResult(`✅ Got ${quotes.quotes.length} quotes - check main view`) + } else { + setLastResult(`❌ No quotes available for ${displayPair.sell} → ${displayPair.buy}`) + } + + } catch (error: any) { + console.error('Swap failed:', error) + setLastResult(`❌ Swap failed: ${error.message}`) + } finally { + setIsSwapping(false) + } + } + + const handleBridge = async (bridgeType: 'eth-to-sol' | 'sol-to-eth') => { + if (!sdk || !solanaAddress || !ethereumAddress) { + setLastResult('Error: Both Solana and Ethereum addresses required for bridging') + return + } + + setIsSwapping(true) + setLastResult(null) + + try { + let sellToken, buyToken, sellAmount, fromAddress, fromNetwork, toAddress, toNetwork, displayPair + + if (bridgeType === 'eth-to-sol') { + sellToken = TOKENS.ETHEREUM_MAINNET.USDC + buyToken = TOKENS.SOLANA_MAINNET.USDC + sellAmount = String(parseFloat(bridgeAmount) * 1_000_000) // USDC has 6 decimals + fromAddress = ethereumAddress + fromNetwork = NetworkId.ETHEREUM_MAINNET + toAddress = solanaAddress + toNetwork = NetworkId.SOLANA_MAINNET + displayPair = { sell: 'ETH-USDC', buy: 'SOL-USDC' } + } else { + sellToken = TOKENS.SOLANA_MAINNET.USDC + buyToken = TOKENS.ETHEREUM_MAINNET.USDC + sellAmount = String(parseFloat(bridgeAmount) * 1_000_000) // USDC has 6 decimals + fromAddress = solanaAddress + fromNetwork = NetworkId.SOLANA_MAINNET + toAddress = ethereumAddress + toNetwork = NetworkId.ETHEREUM_MAINNET + displayPair = { sell: 'SOL-USDC', buy: 'ETH-USDC' } + } + + // Get bridge quotes + const quotes = await swapperSDK.getQuotes({ + sellToken, + buyToken, + sellAmount, + from: { + address: fromAddress, + networkId: fromNetwork, + }, + to: { + address: toAddress, + networkId: toNetwork, + }, + slippageTolerance: 1.0, + }) + + console.log('Bridge quotes:', quotes) + + if (quotes.quotes && quotes.quotes.length > 0) { + // Pass bridge quotes to main view + onQuotesReceived(quotes.quotes as Quote[], { + sellToken: displayPair.sell, + buyToken: displayPair.buy, + sellAmount: bridgeAmount + ' USDC' + }, quotes.type) + setLastResult(`✅ Got ${quotes.quotes.length} bridge quotes - check main view`) + } else { + setLastResult(`❌ No bridge quotes available for ${displayPair.sell} → ${displayPair.buy}`) + } + + } catch (error: any) { + console.error('Bridge failed:', error) + setLastResult(`❌ Bridge failed: ${error.message}`) + } finally { + setIsSwapping(false) + } + } + + return ( +
+

Swap & Bridge

+ + {lastResult && ( +
+ {lastResult} +
+ )} + +
+

Solana Swaps

+ +
+ + setUsdcAmount(e.target.value)} + min="0" + step="0.1" + disabled={isSwapping} + /> +
+ +
+ + setSolAmount(e.target.value)} + min="0" + step="0.01" + disabled={isSwapping} + /> +
+ +
+ + +
+
+ +
+

Cross-Chain Bridge

+ +
+ + setBridgeAmount(e.target.value)} + min="0" + step="0.1" + disabled={isSwapping} + /> +
+ +
+ + +
+ + {(!ethereumAddress || !solanaAddress) && ( +

+ Bridge requires both Ethereum and Solana addresses +

+ )} +
+
+ ) +} \ No newline at end of file diff --git a/examples/swapper-demo/src/components/WalletSidebar.tsx b/examples/swapper-demo/src/components/WalletSidebar.tsx new file mode 100644 index 00000000..cc2d858c --- /dev/null +++ b/examples/swapper-demo/src/components/WalletSidebar.tsx @@ -0,0 +1,111 @@ +import React from 'react' +import { usePhantom, useConnect, useAccounts, useDisconnect, AddressType } from '@phantom/react-sdk' +import { SwapperControls } from './SwapperControls' +import type { SwapperSolanaQuoteRepresentation, SwapperEvmQuoteRepresentation, SwapperXChainQuoteRepresentation } from '@phantom/react-sdk' + +type Quote = SwapperSolanaQuoteRepresentation | SwapperEvmQuoteRepresentation | SwapperXChainQuoteRepresentation + +interface WalletSidebarProps { + onQuotesReceived: (quotes: Quote[], swapInfo: { + sellToken: string + buyToken: string + sellAmount: string + }, quotesType?: string) => void + clearQuotes: () => void +} + +export const WalletSidebar: React.FC = ({ onQuotesReceived, clearQuotes }) => { + const { isConnected, error } = usePhantom() + const addresses = useAccounts() || [] + const { connect, isConnecting } = useConnect() + const { disconnect } = useDisconnect() + console.log(isConnected, addresses, error) + const formatAddress = (address: string, length: number = 8) => { + if (!address) return '' + return `${address.slice(0, length)}...${address.slice(-length)}` + } + + const getNetworkName = (addressType: any) => { + switch (addressType) { + case AddressType.solana: + return 'Solana' + case AddressType.ethereum: + return 'Ethereum' + case AddressType.bitcoinSegwit: + return 'Bitcoin (SegWit)' + default: + return String(addressType) + } + } + + return ( +
+ {!isConnected ? ( +
+

Connect Your Wallet

+

Connect to Phantom wallet to start swapping

+ + {error && ( +
+ {error.message} +
+ )} +
+ ) : ( +
+
+

Wallet Connected

+ +
+ +
+

Your Addresses:

+ {addresses.map((addr, index) => ( +
+

{getNetworkName(addr.addressType)}

+

{formatAddress(addr.address, 6)}

+
+ ))} +
+ + + +
+ +
+
+ )} +
+ ) +} \ No newline at end of file diff --git a/examples/swapper-demo/src/index.css b/examples/swapper-demo/src/index.css new file mode 100644 index 00000000..b231dc14 --- /dev/null +++ b/examples/swapper-demo/src/index.css @@ -0,0 +1,98 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + display: flex; + min-width: 320px; + min-height: 100vh; +} + +#root { + width: 100%; + height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + color: white; + cursor: pointer; + transition: border-color 0.25s; +} + +button:hover { + border-color: #646cff; +} + +button:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +input { + border-radius: 8px; + border: 1px solid #646cff; + padding: 0.6em 1.2em; + font-size: 1em; + background-color: #1a1a1a; + color: white; +} + +input:focus { + outline: none; + border-color: #747bff; +} + +.card { + padding: 1em; + background-color: #1a1a1a; + border-radius: 8px; + border: 1px solid #333; + margin: 0.5em 0; +} + +.quote-card { + background-color: #2a2a2a; + border: 1px solid #444; + margin-bottom: 0.5em; +} + +.error { + color: #ff6b6b; + background-color: #2d1b1b; + border-color: #ff6b6b; +} + +.success { + color: #51cf66; + background-color: #1b2d1b; + border-color: #51cf66; +} \ No newline at end of file diff --git a/examples/swapper-demo/src/main.tsx b/examples/swapper-demo/src/main.tsx new file mode 100644 index 00000000..cbe1cdf3 --- /dev/null +++ b/examples/swapper-demo/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) \ No newline at end of file diff --git a/examples/swapper-demo/tsconfig.json b/examples/swapper-demo/tsconfig.json new file mode 100644 index 00000000..7a7611e4 --- /dev/null +++ b/examples/swapper-demo/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/examples/swapper-demo/tsconfig.node.json b/examples/swapper-demo/tsconfig.node.json new file mode 100644 index 00000000..099658cf --- /dev/null +++ b/examples/swapper-demo/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} \ No newline at end of file diff --git a/examples/swapper-demo/vite.config.ts b/examples/swapper-demo/vite.config.ts new file mode 100644 index 00000000..4142f998 --- /dev/null +++ b/examples/swapper-demo/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + define: { + global: 'globalThis', + }, + resolve: { + alias: { + buffer: 'buffer', + }, + }, +}) \ No newline at end of file diff --git a/packages/react-sdk/src/PhantomProvider.tsx b/packages/react-sdk/src/PhantomProvider.tsx index dbb06d72..a06e8612 100644 --- a/packages/react-sdk/src/PhantomProvider.tsx +++ b/packages/react-sdk/src/PhantomProvider.tsx @@ -48,22 +48,7 @@ export function PhantomProvider({ children, config }: PhantomProviderProps) { const [currentProviderType, setCurrentProviderType] = useState<"injected" | "embedded" | null>(null); const [isPhantomAvailable, setIsPhantomAvailable] = useState(false); - // Check if Phantom extension is available (only for injected provider) - useEffect(() => { - const checkPhantomExtension = async () => { - try { - const available = await sdk.waitForPhantomExtension(1000); - setIsPhantomAvailable(available); - } catch (err) { - console.error("Error checking Phantom extension:", err); - setIsPhantomAvailable(false); - } - }; - - checkPhantomExtension(); - }, [sdk]); - - // Function to update connection state and provider info + // Function to update connection state and provider info const updateConnectionState = useCallback(async () => { try { const connected = sdk.isConnected(); @@ -86,6 +71,24 @@ export function PhantomProvider({ children, config }: PhantomProviderProps) { setError(err as Error); } }, [sdk]); + + // Check if Phantom extension is available (only for injected provider) + useEffect(() => { + const checkPhantomExtension = async () => { + try { + const available = await sdk.waitForPhantomExtension(1000); + setIsPhantomAvailable(available); + } catch (err) { + console.error("Error checking Phantom extension:", err); + setIsPhantomAvailable(false); + } + }; + + checkPhantomExtension(); + updateConnectionState(); + }, [sdk, updateConnectionState]); + + // Initialize connection state useEffect(() => { diff --git a/packages/react-sdk/src/hooks/useConnect.ts b/packages/react-sdk/src/hooks/useConnect.ts index 8d13cfa3..e4210ec8 100644 --- a/packages/react-sdk/src/hooks/useConnect.ts +++ b/packages/react-sdk/src/hooks/useConnect.ts @@ -17,6 +17,7 @@ export function useConnect() { try { const result = await context.sdk.connect(options); + await context.updateConnectionState(); return result; } catch (err) { @@ -35,6 +36,5 @@ export function useConnect() { isConnecting, error, currentProviderType: context.currentProviderType, - isPhantomAvailable: context.isPhantomAvailable, }; } diff --git a/packages/swapper-sdk/package.json b/packages/swapper-sdk/package.json index b419e572..e40936ec 100644 --- a/packages/swapper-sdk/package.json +++ b/packages/swapper-sdk/package.json @@ -39,6 +39,9 @@ "typescript": "^5.0.4" }, "dependencies": { + "@solana/web3.js": "^1.98.4", + "bs58": "^6.0.0", + "buffer": "^6.0.3", "eventsource": "^2.0.2" }, "files": [ diff --git a/packages/swapper-sdk/src/index.ts b/packages/swapper-sdk/src/index.ts index b4d5d5d9..f38d1eb2 100644 --- a/packages/swapper-sdk/src/index.ts +++ b/packages/swapper-sdk/src/index.ts @@ -30,4 +30,13 @@ export type { FeeType, } from "./types"; -export { SwapperSDK as default } from "./swapper-sdk"; \ No newline at end of file +export { SwapperSDK as default } from "./swapper-sdk"; + +// Transaction parsing utilities +export { + getSolanaTransactionFromQuote, + getEvmTransactionFromQuote, + getXChainTransactionFromQuote, + transactionStringToTransaction, + inspectSolanaTransaction +} from "./utils/transaction-parsers"; \ No newline at end of file diff --git a/packages/swapper-sdk/src/utils/transaction-parsers.ts b/packages/swapper-sdk/src/utils/transaction-parsers.ts new file mode 100644 index 00000000..fa2a1b56 --- /dev/null +++ b/packages/swapper-sdk/src/utils/transaction-parsers.ts @@ -0,0 +1,154 @@ +import { VersionedTransaction, Transaction } from '@solana/web3.js' +import { Buffer } from 'buffer' +import bs58 from 'bs58' +import type { + SwapperSolanaQuoteRepresentation, + SwapperEvmQuoteRepresentation, + SwapperXChainQuoteRepresentation +} from '../types/quotes' + +/** + * Converts a base58 encoded transaction string to a VersionedTransaction object + * @param transactionString - Base58 encoded serialized transaction + * @param encoding - Encoding format (should be "bs58" for Solana) + * @returns Parsed VersionedTransaction + */ +export function transactionStringToTransaction( + transactionString: string, + encoding: "bs58" +): VersionedTransaction { + try { + // Step 1: Decode the base58 string to raw bytes + const transactionBytes = bs58.decode(transactionString) + + // Step 2: Deserialize the bytes into a VersionedTransaction + const transaction = VersionedTransaction.deserialize(transactionBytes) + + return transaction + } catch (error) { + throw new Error(`Failed to parse transaction string: ${error}`) + } +} + +/** + * Parses Solana transaction data from a SwapperSolanaQuoteRepresentation + * @param quote - The Solana quote containing transaction data + * @returns Array of parsed VersionedTransaction objects + */ +export function getSolanaTransactionFromQuote(quote: SwapperSolanaQuoteRepresentation): VersionedTransaction[] { + if (!quote.transactionData || !Array.isArray(quote.transactionData)) { + throw new Error('Invalid Solana quote: missing or invalid transaction data') + } + + if (quote.transactionData.length === 0) { + throw new Error('No transaction data in Solana quote') + } + + const parsedTransactions: VersionedTransaction[] = [] + + for (const [index, txString] of quote.transactionData.entries()) { + try { + // Validate input + if (!txString || typeof txString !== 'string') { + throw new Error(`Invalid transaction string at index ${index}`) + } + + // Parse the transaction using base58 encoding + const transaction = transactionStringToTransaction(txString, "bs58") + + // Verify it's a valid transaction + if (!transaction.message || !transaction.message.staticAccountKeys) { + throw new Error(`Invalid transaction structure at index ${index}`) + } + + parsedTransactions.push(transaction) + + console.log(`✅ Transaction ${index} parsed successfully`) + console.log(` - ${transaction.message.staticAccountKeys.length} account keys`) + console.log(` - ${transaction.message.compiledInstructions.length} instructions`) + + // Log address table lookups for debugging + if (transaction.message.addressTableLookups && transaction.message.addressTableLookups.length > 0) { + console.log(` - ${transaction.message.addressTableLookups.length} address table lookups`) + } + + } catch (error) { + console.error(`❌ Failed to parse transaction ${index}:`, error) + throw new Error(`Failed to parse transaction ${index}: ${error}`) + } + } + + return parsedTransactions +} + +/** + * Gets EVM transaction data from a SwapperEvmQuoteRepresentation + * @param quote - The EVM quote containing transaction data + * @returns Raw transaction data string (not implemented yet) + */ +export function getEvmTransactionFromQuote(quote: SwapperEvmQuoteRepresentation): string { + if (!quote.transactionData || typeof quote.transactionData !== 'string') { + throw new Error('Invalid EVM quote: missing or invalid transaction data') + } + + // For now, just return the raw transaction data + // TODO: Implement proper EVM transaction parsing when needed + return quote.transactionData +} + +/** + * Gets cross-chain transaction data from a SwapperXChainQuoteRepresentation + * @param quote - The cross-chain quote containing transaction steps + * @returns Array of transaction step data (not implemented yet) + */ +export function getXChainTransactionFromQuote(quote: SwapperXChainQuoteRepresentation): string[] { + if (!quote.steps || !Array.isArray(quote.steps)) { + throw new Error('Invalid cross-chain quote: missing or invalid steps') + } + + // For now, just return the transaction data from each step + // TODO: Implement proper cross-chain transaction parsing when needed + return quote.steps.map(step => step.transactionData) +} + +/** + * Inspects a parsed Solana transaction and logs detailed information + * @param transaction - The VersionedTransaction to inspect + * @param index - Optional index for logging + */ +export function inspectSolanaTransaction(transaction: VersionedTransaction, index?: number): void { + const prefix = index !== undefined ? `Transaction ${index}:` : 'Transaction:' + + console.log(`${prefix} Details`) + console.log('- Version:', transaction.version) + console.log('- Signatures:', transaction.signatures.map(sig => sig?.toString() || 'unsigned')) + + // Message details + const message = transaction.message + console.log('Message Details:') + console.log('- Account Keys:', message.staticAccountKeys.map(key => key.toString())) + console.log('- Recent Blockhash:', message.recentBlockhash) + console.log('- Instructions Count:', message.compiledInstructions.length) + + // Instruction details + message.compiledInstructions.forEach((instruction, instructionIndex) => { + console.log(`Instruction ${instructionIndex}:`, { + programIdIndex: instruction.programIdIndex, + accountsLength: instruction.accountKeyIndexes.length, + dataLength: instruction.data.length, + data: Buffer.from(instruction.data).toString('hex').substring(0, 32) + '...' + }) + }) + + // Address table lookups (for versioned transactions) + if (message.addressTableLookups && message.addressTableLookups.length > 0) { + console.log('Address Table Lookups:', message.addressTableLookups.length) + message.addressTableLookups.forEach((lookup, lookupIndex) => { + console.log(`Lookup ${lookupIndex}:`, { + accountKey: lookup.accountKey.toString(), + writableIndexes: lookup.writableIndexes, + readonlyIndexes: lookup.readonlyIndexes + }) + }) + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f6f8ed9b..4b02c1a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1436,6 +1436,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/aix-ppc64@npm:0.21.5" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/aix-ppc64@npm:0.25.8" @@ -1457,6 +1464,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm64@npm:0.21.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/android-arm64@npm:0.25.8" @@ -1478,6 +1492,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm@npm:0.21.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/android-arm@npm:0.25.8" @@ -1499,6 +1520,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-x64@npm:0.21.5" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/android-x64@npm:0.25.8" @@ -1520,6 +1548,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-arm64@npm:0.21.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/darwin-arm64@npm:0.25.8" @@ -1541,6 +1576,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-x64@npm:0.21.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/darwin-x64@npm:0.25.8" @@ -1562,6 +1604,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-arm64@npm:0.21.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/freebsd-arm64@npm:0.25.8" @@ -1583,6 +1632,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-x64@npm:0.21.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/freebsd-x64@npm:0.25.8" @@ -1604,6 +1660,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm64@npm:0.21.5" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/linux-arm64@npm:0.25.8" @@ -1625,6 +1688,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm@npm:0.21.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/linux-arm@npm:0.25.8" @@ -1646,6 +1716,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ia32@npm:0.21.5" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/linux-ia32@npm:0.25.8" @@ -1667,6 +1744,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-loong64@npm:0.21.5" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/linux-loong64@npm:0.25.8" @@ -1688,6 +1772,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-mips64el@npm:0.21.5" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/linux-mips64el@npm:0.25.8" @@ -1709,6 +1800,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ppc64@npm:0.21.5" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/linux-ppc64@npm:0.25.8" @@ -1730,6 +1828,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-riscv64@npm:0.21.5" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/linux-riscv64@npm:0.25.8" @@ -1751,6 +1856,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-s390x@npm:0.21.5" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/linux-s390x@npm:0.25.8" @@ -1772,6 +1884,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-x64@npm:0.21.5" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/linux-x64@npm:0.25.8" @@ -1800,6 +1919,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/netbsd-x64@npm:0.21.5" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/netbsd-x64@npm:0.25.8" @@ -1828,6 +1954,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/openbsd-x64@npm:0.21.5" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/openbsd-x64@npm:0.25.8" @@ -1856,6 +1989,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/sunos-x64@npm:0.21.5" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/sunos-x64@npm:0.25.8" @@ -1877,6 +2017,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-arm64@npm:0.21.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/win32-arm64@npm:0.25.8" @@ -1898,6 +2045,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-ia32@npm:0.21.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/win32-ia32@npm:0.25.8" @@ -1919,6 +2073,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-x64@npm:0.21.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/win32-x64@npm:0.25.8" @@ -1944,7 +2105,7 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^2.1.3": +"@eslint/eslintrc@npm:^2.1.3, @eslint/eslintrc@npm:^2.1.4": version: 2.1.4 resolution: "@eslint/eslintrc@npm:2.1.4" dependencies: @@ -1968,6 +2129,13 @@ __metadata: languageName: node linkType: hard +"@eslint/js@npm:8.57.1": + version: 8.57.1 + resolution: "@eslint/js@npm:8.57.1" + checksum: 10c0/b489c474a3b5b54381c62e82b3f7f65f4b8a5eaaed126546520bf2fede5532a8ed53212919fed1e9048dcf7f37167c8561d58d0ba4492a4244004e7793805223 + languageName: node + linkType: hard + "@expo/cli@npm:0.24.20": version: 0.24.20 resolution: "@expo/cli@npm:0.24.20" @@ -2339,6 +2507,17 @@ __metadata: languageName: node linkType: hard +"@humanwhocodes/config-array@npm:^0.13.0": + version: 0.13.0 + resolution: "@humanwhocodes/config-array@npm:0.13.0" + dependencies: + "@humanwhocodes/object-schema": "npm:^2.0.3" + debug: "npm:^4.3.1" + minimatch: "npm:^3.0.5" + checksum: 10c0/205c99e756b759f92e1f44a3dc6292b37db199beacba8f26c2165d4051fe73a4ae52fdcfd08ffa93e7e5cb63da7c88648f0e84e197d154bbbbe137b2e0dd332e + languageName: node + linkType: hard + "@humanwhocodes/module-importer@npm:^1.0.1": version: 1.0.1 resolution: "@humanwhocodes/module-importer@npm:1.0.1" @@ -2346,7 +2525,7 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/object-schema@npm:^2.0.2": +"@humanwhocodes/object-schema@npm:^2.0.2, @humanwhocodes/object-schema@npm:^2.0.3": version: 2.0.3 resolution: "@humanwhocodes/object-schema@npm:2.0.3" checksum: 10c0/80520eabbfc2d32fe195a93557cef50dfe8c8905de447f022675aaf66abc33ae54098f5ea78548d925aa671cd4ab7c7daa5ad704fe42358c9b5e7db60f80696c @@ -3530,8 +3709,11 @@ __metadata: version: 0.0.0-use.local resolution: "@phantom/swapper-sdk@workspace:packages/swapper-sdk" dependencies: + "@solana/web3.js": "npm:^1.98.4" "@types/jest": "npm:^29.5.12" "@types/node": "npm:^20.11.0" + bs58: "npm:^6.0.0" + buffer: "npm:^6.0.3" dotenv: "npm:^16.4.1" eslint: "npm:8.53.0" eventsource: "npm:^2.0.2" @@ -4847,7 +5029,7 @@ __metadata: languageName: node linkType: hard -"@solana/web3.js@npm:^1.87.6, @solana/web3.js@npm:^1.98.2": +"@solana/web3.js@npm:^1.87.6, @solana/web3.js@npm:^1.98.2, @solana/web3.js@npm:^1.98.4": version: 1.98.4 resolution: "@solana/web3.js@npm:1.98.4" dependencies: @@ -5156,6 +5338,22 @@ __metadata: languageName: node linkType: hard +"@types/prop-types@npm:*": + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10c0/b59aad1ad19bf1733cf524fd4e618196c6c7690f48ee70a327eb450a42aab8e8a063fbe59ca0a5701aebe2d92d582292c0fb845ea57474f6a15f6994b0e260b2 + languageName: node + linkType: hard + +"@types/react-dom@npm:^18.2.22": + version: 18.3.7 + resolution: "@types/react-dom@npm:18.3.7" + peerDependencies: + "@types/react": ^18.0.0 + checksum: 10c0/8bd309e2c3d1604a28a736a24f96cbadf6c05d5288cfef8883b74f4054c961b6b3a5e997fd5686e492be903c8f3380dba5ec017eff3906b1256529cd2d39603e + languageName: node + linkType: hard + "@types/react-dom@npm:^19.1.2": version: 19.1.7 resolution: "@types/react-dom@npm:19.1.7" @@ -5193,6 +5391,16 @@ __metadata: languageName: node linkType: hard +"@types/react@npm:^18.2.66": + version: 18.3.23 + resolution: "@types/react@npm:18.3.23" + dependencies: + "@types/prop-types": "npm:*" + csstype: "npm:^3.0.2" + checksum: 10c0/49331800b76572eb2992a5c44801dbf8c612a5f99c8f4e4200f06c7de6f3a6e9455c661784a6c5469df96fa45622cb4a9d0982c44e6a0d5719be5f2ef1f545ed + languageName: node + linkType: hard + "@types/react@npm:~19.0.10": version: 19.0.14 resolution: "@types/react@npm:19.0.14" @@ -5264,7 +5472,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^7.18.0": +"@typescript-eslint/eslint-plugin@npm:^7.18.0, @typescript-eslint/eslint-plugin@npm:^7.2.0": version: 7.18.0 resolution: "@typescript-eslint/eslint-plugin@npm:7.18.0" dependencies: @@ -5287,7 +5495,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/parser@npm:^7.18.0": +"@typescript-eslint/parser@npm:^7.18.0, @typescript-eslint/parser@npm:^7.2.0": version: 7.18.0 resolution: "@typescript-eslint/parser@npm:7.18.0" dependencies: @@ -5546,7 +5754,7 @@ __metadata: languageName: node linkType: hard -"@vitejs/plugin-react@npm:^4.0.3, @vitejs/plugin-react@npm:^4.4.1": +"@vitejs/plugin-react@npm:^4.0.3, @vitejs/plugin-react@npm:^4.2.1, @vitejs/plugin-react@npm:^4.4.1": version: 4.7.0 resolution: "@vitejs/plugin-react@npm:4.7.0" dependencies: @@ -7918,6 +8126,86 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.21.3": + version: 0.21.5 + resolution: "esbuild@npm:0.21.5" + dependencies: + "@esbuild/aix-ppc64": "npm:0.21.5" + "@esbuild/android-arm": "npm:0.21.5" + "@esbuild/android-arm64": "npm:0.21.5" + "@esbuild/android-x64": "npm:0.21.5" + "@esbuild/darwin-arm64": "npm:0.21.5" + "@esbuild/darwin-x64": "npm:0.21.5" + "@esbuild/freebsd-arm64": "npm:0.21.5" + "@esbuild/freebsd-x64": "npm:0.21.5" + "@esbuild/linux-arm": "npm:0.21.5" + "@esbuild/linux-arm64": "npm:0.21.5" + "@esbuild/linux-ia32": "npm:0.21.5" + "@esbuild/linux-loong64": "npm:0.21.5" + "@esbuild/linux-mips64el": "npm:0.21.5" + "@esbuild/linux-ppc64": "npm:0.21.5" + "@esbuild/linux-riscv64": "npm:0.21.5" + "@esbuild/linux-s390x": "npm:0.21.5" + "@esbuild/linux-x64": "npm:0.21.5" + "@esbuild/netbsd-x64": "npm:0.21.5" + "@esbuild/openbsd-x64": "npm:0.21.5" + "@esbuild/sunos-x64": "npm:0.21.5" + "@esbuild/win32-arm64": "npm:0.21.5" + "@esbuild/win32-ia32": "npm:0.21.5" + "@esbuild/win32-x64": "npm:0.21.5" + 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-x64": + 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: 10c0/fa08508adf683c3f399e8a014a6382a6b65542213431e26206c0720e536b31c09b50798747c2a105a4bbba1d9767b8d3615a74c2f7bf1ddf6d836cd11eb672de + languageName: node + linkType: hard + "esbuild@npm:^0.25.0, esbuild@npm:~0.25.0": version: 0.25.8 resolution: "esbuild@npm:0.25.8" @@ -8200,6 +8488,15 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-react-refresh@npm:^0.4.6": + version: 0.4.20 + resolution: "eslint-plugin-react-refresh@npm:0.4.20" + peerDependencies: + eslint: ">=8.40" + checksum: 10c0/2ccf4ba28f1dcbcb9e773e46eae1e61e568bba69281a700eb26fd762152e4e90a78c991f9c8173342a7cd2a82f3f52fedb40a1e81360cef9c40ea5b814fa3613 + languageName: node + linkType: hard + "eslint-plugin-react@npm:^7.33.0": version: 7.37.5 resolution: "eslint-plugin-react@npm:7.37.5" @@ -8302,6 +8599,54 @@ __metadata: languageName: node linkType: hard +"eslint@npm:^8.57.0": + version: 8.57.1 + resolution: "eslint@npm:8.57.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.2.0" + "@eslint-community/regexpp": "npm:^4.6.1" + "@eslint/eslintrc": "npm:^2.1.4" + "@eslint/js": "npm:8.57.1" + "@humanwhocodes/config-array": "npm:^0.13.0" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@nodelib/fs.walk": "npm:^1.2.8" + "@ungap/structured-clone": "npm:^1.2.0" + ajv: "npm:^6.12.4" + chalk: "npm:^4.0.0" + cross-spawn: "npm:^7.0.2" + debug: "npm:^4.3.2" + doctrine: "npm:^3.0.0" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^7.2.2" + eslint-visitor-keys: "npm:^3.4.3" + espree: "npm:^9.6.1" + esquery: "npm:^1.4.2" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^6.0.1" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + globals: "npm:^13.19.0" + graphemer: "npm:^1.4.0" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + is-path-inside: "npm:^3.0.3" + js-yaml: "npm:^4.1.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + levn: "npm:^0.4.1" + lodash.merge: "npm:^4.6.2" + minimatch: "npm:^3.1.2" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + strip-ansi: "npm:^6.0.1" + text-table: "npm:^0.2.0" + bin: + eslint: bin/eslint.js + checksum: 10c0/1fd31533086c1b72f86770a4d9d7058ee8b4643fd1cfd10c7aac1ecb8725698e88352a87805cf4b2ce890aa35947df4b4da9655fb7fdfa60dbb448a43f6ebcf1 + languageName: node + linkType: hard + "espree@npm:^9.6.0, espree@npm:^9.6.1": version: 9.6.1 resolution: "espree@npm:9.6.1" @@ -11681,7 +12026,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.0.0, loose-envify@npm:^1.4.0": +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -13004,7 +13349,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.5.3": +"postcss@npm:^8.4.43, postcss@npm:^8.5.3": version: 8.5.6 resolution: "postcss@npm:8.5.6" dependencies: @@ -13325,6 +13670,18 @@ __metadata: languageName: node linkType: hard +"react-dom@npm:^18.2.0": + version: 18.3.1 + resolution: "react-dom@npm:18.3.1" + dependencies: + loose-envify: "npm:^1.1.0" + scheduler: "npm:^0.23.2" + peerDependencies: + react: ^18.3.1 + checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 + languageName: node + linkType: hard + "react-fast-compare@npm:^3.2.2": version: 3.2.2 resolution: "react-fast-compare@npm:3.2.2" @@ -13577,6 +13934,15 @@ __metadata: languageName: node linkType: hard +"react@npm:^18.2.0": + version: 18.3.1 + resolution: "react@npm:18.3.1" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 + languageName: node + linkType: hard + "read-yaml-file@npm:^1.1.0": version: 1.1.0 resolution: "read-yaml-file@npm:1.1.0" @@ -13946,7 +14312,7 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^4.34.9": +"rollup@npm:^4.20.0, rollup@npm:^4.34.9": version: 4.46.2 resolution: "rollup@npm:4.46.2" dependencies: @@ -14148,6 +14514,15 @@ __metadata: languageName: node linkType: hard +"scheduler@npm:^0.23.2": + version: 0.23.2 + resolution: "scheduler@npm:0.23.2" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78 + languageName: node + linkType: hard + "scheduler@npm:^0.26.0": version: 0.26.0 resolution: "scheduler@npm:0.26.0" @@ -14910,6 +15285,30 @@ __metadata: languageName: node linkType: hard +"swapper-demo@workspace:examples/swapper-demo": + version: 0.0.0-use.local + resolution: "swapper-demo@workspace:examples/swapper-demo" + dependencies: + "@phantom/react-sdk": "workspace:^" + "@phantom/swapper-sdk": "workspace:^" + "@solana/web3.js": "npm:^1.98.4" + "@types/react": "npm:^18.2.66" + "@types/react-dom": "npm:^18.2.22" + "@typescript-eslint/eslint-plugin": "npm:^7.2.0" + "@typescript-eslint/parser": "npm:^7.2.0" + "@vitejs/plugin-react": "npm:^4.2.1" + bs58: "npm:^6.0.0" + buffer: "npm:^6.0.3" + eslint: "npm:^8.57.0" + eslint-plugin-react-hooks: "npm:^4.6.0" + eslint-plugin-react-refresh: "npm:^0.4.6" + react: "npm:^18.2.0" + react-dom: "npm:^18.2.0" + typescript: "npm:^5.2.2" + vite: "npm:^5.2.0" + languageName: unknown + linkType: soft + "symbol-tree@npm:^3.2.4": version: 3.2.4 resolution: "symbol-tree@npm:3.2.4" @@ -15874,6 +16273,49 @@ __metadata: languageName: node linkType: hard +"vite@npm:^5.2.0": + version: 5.4.19 + resolution: "vite@npm:5.4.19" + dependencies: + esbuild: "npm:^0.21.3" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.43" + rollup: "npm:^4.20.0" + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/c97601234dba482cea5290f2a2ea0fcd65e1fab3df06718ea48adc8ceb14bc3129508216c4989329c618f6a0470b42f439677a207aef62b0c76f445091c2d89e + languageName: node + linkType: hard + "vite@npm:^6.3.5": version: 6.3.5 resolution: "vite@npm:6.3.5"