Skip to main content
This guide demonstrates how to create atomic swaps from EVM-compatible chains to Solana. This flow is simpler than Solana-to-EVM as it doesn’t require on-chain order publishing.

Overview

EVM to Solana swaps combine elements of both EVM and Solana workflows:
  • Orders are submitted to the relayer (like EVM-to-EVM)
  • No on-chain publishing required (unlike Solana-to-EVM)
  • Destination uses Solana addresses
  • Secret management follows the standard pattern

Prerequisites

  • EVM wallet with sufficient token balance
  • Token allowance approved for Limit Order Protocol
  • Solana destination wallet address
  • Dev Portal API key from portal.1inch.dev

Complete Example

This example swaps 10 USDT from Ethereum to USDT on Solana.
1

Setup and Import Dependencies

Import required modules and configure providers.
import {
  NetworkEnum,
  SDK,
  SolanaAddress,
  HashLock,
  EvmAddress,
  PrivateKeyProviderConnector,
  OrderStatus
} from '@1inch/cross-chain-sdk'
import { JsonRpcProvider, TransactionRequest, computeAddress } from 'ethers'
import { randomBytes } from 'node:crypto'
import { setTimeout } from 'node:timers/promises'
import assert from 'node:assert'

const authKey = process.env.DEV_PORTAL_API_TOKEN
assert(authKey, 'Please provide DEV_PORTAL_API_TOKEN')

const signerPrivateKey = process.env.EVM_PRIVATE_KEY
assert(signerPrivateKey, 'Please provide EVM_PRIVATE_KEY')
2

Initialize SDK with Provider

Set up the ethers provider and SDK with blockchain provider.
const NODE_URL = 'https://web3.1inch.io/1'
const ethersRpcProvider = new JsonRpcProvider(NODE_URL)

const ethersProviderConnector = {
  eth: {
    call(transactionConfig: TransactionRequest): Promise<string> {
      return ethersRpcProvider.call(transactionConfig)
    }
  },
  extend(): void {}
}

const connector = new PrivateKeyProviderConnector(
  signerPrivateKey,
  ethersProviderConnector
)

const sdk = new SDK({
  url: 'https://api.1inch.com/fusion-plus',
  authKey,
  blockchainProvider: connector
})
The blockchainProvider is required for order creation as it signs transactions.
3

Configure Addresses and Tokens

Set up the maker, receiver, and token addresses.
const maker = computeAddress(signerPrivateKey)
const receiver = '93FP8NG2JrScb9xzNsJrzAze8gJJtr1TgQWUCHDgP3BW'

const USDT_SOL = 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'
const USDT_ETHEREUM = '0xdac17f958d2ee523a2206206994597c13d831ec7'

const amount = 10_000_000n // 10 USDT
const srcToken = EvmAddress.fromString(USDT_ETHEREUM)
const dstToken = SolanaAddress.fromString(USDT_SOL)

const srcChainId = NetworkEnum.ETHEREUM
const dstChainId = NetworkEnum.SOLANA
For native SOL on the destination, use SolanaAddress.NATIVE instead of a token address.
4

Get Quote

Request a quote for the EVM to Solana swap.
const quote = await sdk.getQuote({
  amount: amount.toString(),
  srcChainId,
  dstChainId,
  srcTokenAddress: srcToken.toString(),
  dstTokenAddress: dstToken.toString(),
  enableEstimate: true,
  walletAddress: maker
})

const preset = quote.getPreset(quote.recommendedPreset)
assert(quote.quoteId)
console.log('Got preset', preset)
5

Generate Secrets and Create Order

Generate secrets and create an EVM order with Solana receiver.
function getSecret(): string {
  return '0x' + randomBytes(32).toString('hex')
}

const secrets = Array.from({ length: preset.secretsCount }).map(getSecret)
const secretHashes = secrets.map(HashLock.hashSecret)
const leaves = HashLock.getMerkleLeaves(secrets)

const hashLock = secrets.length > 1
  ? HashLock.forMultipleFills(leaves)
  : HashLock.forSingleFill(secrets[0])

// Create EVM order with Solana receiver
const order = quote.createEvmOrder({
  hashLock,
  receiver: SolanaAddress.fromString(receiver),
  preset: quote.recommendedPreset
})
The key difference is using SolanaAddress.fromString() for the receiver.
6

Submit Order

Submit the order to the relayer (no on-chain publishing needed).
const { orderHash } = await sdk.submitOrder(
  srcChainId,
  order,
  quote.quoteId,
  secretHashes
)

console.log('Submitted order to relayer', orderHash)
Unlike Solana-to-EVM, EVM-to-Solana orders don’t require separate on-chain publishing.
7

Monitor and Share Secrets

Monitor escrow deployments and share secrets when ready.
const alreadyShared = new Set<number>()

while (true) {
  const readyToAcceptSecrets = await sdk.getReadyToAcceptSecretFills(orderHash)
  const idxes = readyToAcceptSecrets.fills.map((f) => f.idx)

  for (const idx of idxes) {
    if (!alreadyShared.has(idx)) {
      // Verify escrow addresses before sharing secrets
      await sdk.submitSecret(orderHash, secrets[idx])
        .catch((err) => console.error('Failed to submit secret', err))
      alreadyShared.add(idx)
      console.log('Submitted secret', secrets[idx])
    }
  }

  // Check if order finished
  const { status } = await sdk.getOrderStatus(orderHash)

  if (
    status === OrderStatus.Executed ||
    status === OrderStatus.Expired ||
    status === OrderStatus.Refunded
  ) {
    break
  }

  await setTimeout(5000)
}

const statusResponse = await sdk.getOrderStatus(orderHash)
console.log(statusResponse)

Key Differences

The main difference is the receiver address type:
// EVM-to-EVM
const order = quote.createEvmOrder({
  receiver: EvmAddress.fromString(receiver),
  // ...
})

// EVM-to-Solana
const order = quote.createEvmOrder({
  receiver: SolanaAddress.fromString(receiver),
  // ...
})
Everything else follows the standard EVM flow.
EVM-to-Solana is simpler:No announceOrder required:
// Solana-to-EVM requires announcement
const orderHash = await sdk.announceOrder(order, quoteId, secretHashes)

// EVM-to-Solana uses direct submission
const { orderHash } = await sdk.submitOrder(srcChainId, order, quoteId, secretHashes)
No on-chain publishing:
// Solana-to-EVM requires on-chain publishing
const ix = SvmSrcEscrowFactory.DEFAULT.createOrder(order, {...})
await connection.sendTransaction(tx, [makerSigner])

// EVM-to-Solana doesn't need this step
Use Solana token addresses for the destination:
// Solana USDT address
const dstToken = SolanaAddress.fromString(
  'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'
)

// Or native SOL
const dstToken = SolanaAddress.NATIVE

Error Handling

Implement robust error handling for production use:
try {
  const { orderHash } = await sdk.submitOrder(
    srcChainId,
    order,
    quote.quoteId,
    secretHashes
  )
} catch (error) {
  if (error.message.includes('insufficient allowance')) {
    console.error('Token allowance not approved')
    // Guide user to approve token
  } else if (error.message.includes('insufficient balance')) {
    console.error('Insufficient token balance')
  } else if (error.message.includes('invalid receiver')) {
    console.error('Invalid Solana address format')
  } else {
    console.error('Order submission failed:', error)
  }
  throw error
}

Secret Sharing Error Handling

Handle errors during secret submission gracefully:
for (const idx of idxes) {
  if (!alreadyShared.has(idx)) {
    try {
      await sdk.submitSecret(orderHash, secrets[idx])
      alreadyShared.add(idx)
      console.log('Submitted secret', idx)
    } catch (err) {
      console.error('Failed to submit secret', idx, err)
      // Don't add to alreadyShared - retry on next iteration
    }
  }
}

Best Practices

Validate Addresses

Verify Solana receiver addresses are valid before creating orders.

Handle Retries

Implement retry logic for secret submission failures.

Monitor Status

Continuously monitor order status with appropriate intervals.

Log Everything

Log all steps for debugging and tracking purposes.

Receiver Address Validation

Validate Solana addresses before using them:
import { PublicKey } from '@solana/web3.js'

function isValidSolanaAddress(address: string): boolean {
  try {
    new PublicKey(address)
    return true
  } catch {
    return false
  }
}

const receiver = '93FP8NG2JrScb9xzNsJrzAze8gJJtr1TgQWUCHDgP3BW'
if (!isValidSolanaAddress(receiver)) {
  throw new Error('Invalid Solana receiver address')
}

Next Steps

Solana to EVM

Learn about swaps from Solana to EVM

Order Lifecycle

Understand order states and monitoring

Build docs developers (and LLMs) love