A comprehensive proof-of-concept for performing trust-minimized, cross-chain atomic swaps between EVM-based assets, native Bitcoin assets (both on-chain and via Lightning Network), and TON blockchain assets.
The core EVM-side logic is powered by the 1inch Fusion SDK, enabling users to sign sophisticated, hash-locked limit orders off-chain, which are then executed by a Resolver on the blockchain.
Atomic swaps are smart contract mechanisms that enable cryptocurrency exchanges between different blockchains without trusted third parties. The "atomicity" guarantees an "all-or-nothing" operation: either both parties successfully receive their funds, or the entire transaction reverts with no loss of original assets (minus transaction fees).
This is achieved using Hashed Time-Lock Contracts (HTLCs).
- Node.js (v18+)
pnpmpackage manager (npm install -g pnpm)- Docker
- Polar - Desktop application for local Lightning and Bitcoin test networks
-
Clone Repository
git clone https://github.com/unite-defi-fusion-extensions/cross-chain-resolver-example.git cd cross-chain-resolver-example -
Install Dependencies
pnpm install
-
Configure Environment
cp .env.example .env
Critical Detail: Ethereum and 1inch SDK use
keccak256, while Bitcoin and Lightning Network usesha256. The swap logic must correctly generate and use the appropriate hash for each chain from the same original secret.
- Download and install Polar from lightningpolar.com
- Create Network: Launch Polar β "Create Network" β Configure with 2 LND nodes and 1 Bitcoin Core node
- Start Network: Click "Start Network" (downloads Docker images and starts nodes)
- Open Channel: Create a balanced channel between LND1 and LND2
Fill your .env file with Polar network credentials:
# LND Nodes
LND_RPC=https://127.0.0.1:8080
LND_MACAROON=0201036c6e...
LND_RPC2=https://127.0.0.1:8081
LND_MACAROON2=0201036c6e...
# Bitcoin Core RPC
BTC_RPC_HOST="http://127.0.0.1:18443"
BTC_RPC_USER="polaruser"
BTC_RPC_PASS="polarpass"Getting Credentials from Polar:
- LND Nodes: Click node β "Connect" tab β Copy "REST Host" and "Admin Macaroon (Hex)"
- Bitcoin Node: Click bitcoind β "Connect" tab β Copy "RPC URL"
Scenario: User buys USDC with Lightning Sats
Test File: lightning.spec.ts
sequenceDiagram
participant User
participant Resolver
participant EVM Chain
participant Lightning Network
User->>User: 1. Generate Secret (S) & Hashes
User->>Resolver: 2. Present signed Fusion Order
Resolver->>User: 3. Provide sha256-locked HODL Invoice
User->>Lightning Network: 4. Pay HODL Invoice
Lightning Network-->>Resolver: 5. Notify payment is held (Accepted)
Resolver->>EVM Chain: 6. Submit Order, lock USDC in Escrow
User->>EVM Chain: 7. Withdraw USDC (reveals Secret S)
EVM Chain-->>Resolver: 8. Resolver learns Secret S from chain
Resolver->>Lightning Network: 9. Settle Invoice with Secret S to claim Sats
- Secret Generation: User generates secret (
S) and correspondingkeccak256andsha256hashes - Order Signing: User creates and signs off-chain 1inch Fusion order
- HODL Invoice: Resolver provides HODL Invoice locked with
sha256hash - Payment Held: User pays invoice β moves to
ACCEPTEDstate (secret not yet revealed) - On-Chain Execution: Resolver submits signed order to EVM chain, locks USDC
- Claim & Reveal: User withdraws USDC by submitting secret
Son-chain (public reveal) - Final Settlement: Resolver uses learned secret to settle HODL invoice and claim Sats
Scenario: User buys USDC with BTC
Test File: bitcoin.spec.ts
Bitcoin HTLC:
OP_IF
# Claim path: requires a valid signature and the correct secret
OP_SHA256 <hash_of_secret> OP_EQUALVERIFY <resolver_pubkey> OP_CHECKSIG
OP_ELSE
# Refund path: requires the timelock to have passed and the user's signature
<locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP <user_pubkey> OP_CHECKSIG
OP_ENDIF
sequenceDiagram
participant User
participant Resolver
participant EVM Chain
participant Bitcoin Blockchain
User->>User: 1. Generate Secret (S) & Hashes
User->>Resolver: 2. Present signed Fusion Order
User->>Bitcoin Blockchain: 3. Create & Fund sha256-locked HTLC
Bitcoin Blockchain-->>Resolver: 4. Resolver sees confirmed HTLC
Resolver->>EVM Chain: 5. Submit Order, lock USDC in Escrow
User->>EVM Chain: 6. Withdraw USDC (reveals Secret S)
EVM Chain-->>Resolver: 7. Resolver learns Secret S from chain
Resolver->>Bitcoin Blockchain: 8. Claim BTC from HTLC with Secret S
- Secret Generation: User generates secret (
S) and itskeccak256/sha256hashes - Order Signing: User creates and signs off-chain 1inch Fusion order
- Bitcoin HTLC: User sends BTC to special script address locked with
sha256hash - On-Chain Execution: Resolver sees confirmed HTLC β submits order to EVM chain, locks USDC
- Claim & Reveal: User withdraws USDC by providing secret
S(reveals on EVM chain) - Final Claim: Resolver uses learned secret to claim BTC from HTLC
# Lightning Network swap test
pnpm test -- lightning.spec.ts
# On-chain Bitcoin swap test
pnpm test -- bitcoin.spec.tsTON swaps support bidirectional exchanges between TON Jettons and EVM-based USDC using custom HTLC contracts with synchronized hash locks and time locks.
Technical Note: Both TON and EVM HTLC contracts use
sha256hashing, ensuring hash compatibility across chains from the same original secret.
- Escrow Contract: Custom smart contract deployed fresh for each test
- Jetton System: TON's token standard (similar to ERC-20)
- Smart Contract Capabilities:
- Random Context ID: Unique 32-bit identifier for each deployment
- Hashlock Mapping: Efficient swap lookup via dictionary storage
- Jetton Transfer Integration: Handles incoming deposit notifications
- Timelock Safety: Unix timestamp validation
- State Persistence: All swap data stored with proper serialization
OP_DEPOSIT_NOTIFICATION (0xdeadbeef): Handle jetton depositsOP_COMPLETE_SWAP (0x87654321): Complete swap with secret revealOP_REFUND_SWAP (0xabcdef12): Refund expired swapsOP_INITIALIZE (1): Initialize contract with jetton wallet
- HashedTimelockERC20: Standard HTLC contract for ERC-20 tokens
- USDC Token: Standard ERC-20 implementation
- Anvil Fork: Local Ethereum fork for testing
sequenceDiagram
participant User
participant Resolver
participant TON Chain
participant EVM Chain
User->>User: 1. Generate Secret (S) & compute sha256/keccak256 hashes
Note over User,EVM Chain: TON β EVM Direction (Jettons β USDC)
User->>TON Chain: 2. Deposit Jettons to Escrow (with hashlock + timelock)
User->>EVM Chain: 3. Create HTLC with USDC (same hashlock/timelock)
Note over User,EVM Chain: Completion Path
Resolver->>EVM Chain: 4. Withdraw USDC (reveals secret S on-chain)
Resolver->>TON Chain: 5. Complete TON swap using revealed secret S
Note over User,EVM Chain: Alternative: Refund Path (if expired)
User->>EVM Chain: 4a. Refund USDC (after timelock expires)
User->>TON Chain: 5a. Refund Jettons (after timelock expires)
Each test deploys a new TON escrow contract with random salt, ensuring clean state and unique addresses.
Both chains use compatible timelock mechanisms:
- TON: Unix timestamp in seconds
- EVM: Unix timestamp in seconds (via block.timestamp)
- Happy Path: Secret reveal on EVM, completion on TON
- Refund Path: Timeout-based refunds on both chains
// Jetton accounting
expect(userJettonBefore - userJettonAfter).toBeGreaterThanOrEqual(swapAmountJetton);
expect(contractJettonAfter - contractJettonBefore).toBeGreaterThanOrEqual(swapAmountJetton);
// USDC accounting
expect(userUsdcBefore - userUsdcAfter).toBeGreaterThanOrEqual(amountUsdc);
expect(htlcBal).toBeGreaterThanOrEqual(amountUsdc);// Deposit Message (Jetton Transfer + Swap Payload)
const depositMessage = createTonSwapDepositMessage(
amount, depositor, recipient, hashLock, timeLock, swapContractAddress
);
// Completion Message
const completeMessage = createTonCompleteSwapMessage(swapId, secret);
// Refund Message
const refundMessage = createTonRefundSwapMessage(swapId);function tonHashLockFromSecret(secret: Uint8Array): bigint {
const hashHex = crypto.createHash('sha256').update(Buffer.from(secret)).digest('hex');
return BigInt('0x' + hashHex);
};; Persistent storage globals
global int ctx_id; // Random 32-bit contract identifier
global slice ctx_jetton_wallet; // Associated jetton wallet address
global int ctx_swap_counter; // Monotonic counter for swap IDs
global cell ctx_swaps; // Dictionary: swapId -> swap data
global cell ctx_hashlock_map; // Dictionary: hashlock -> swapId
global int ctx_initialized; // Initialization flag
Key Contract Methods:
get_swap_by_hashlock(int hashlock): Lookup swaps by hash lockhas_swap(int swapId): Check swap existenceget_id(): Returns unique contract identifieris_initialized(): Validates contract readiness
Add TON testnet wallet mnemonics to your .env:
# TON Testnet Wallets (required for TON tests)
TON_USER_MNEMONIC="your twelve word mnemonic phrase here for user wallet"
TON_RESOLVER_MNEMONIC="your twelve word mnemonic phrase here for resolver wallet"The test suite covers:
- Swap Creation: Deploy contracts, initialize, create swaps on both chains
- Completion Path: Full atomic swap with secret reveal and completion
- Refund Path: Timeout handling and refund mechanisms
# Ensure TON_USER_MNEMONIC and TON_RESOLVER_MNEMONIC are set in .env
pnpm test-ton -- ton-eth.spec.tsYour final .env file should include all necessary configurations:
SRC_CHAIN_RPC=https://eth.merkle.io
DST_CHAIN_RPC=wss://bsc-rpc.publicnode.com
SRC_CHAIN_CREATE_FORK=true
DST_CHAIN_CREATE_FORK=true
# LND POLAR
LND_RPC=https://127.0.0.1:8080
LND_MACAROON=0201036c6e...
LND_RPC2=https://127.0.0.1:8081
LND_MACAROON2=0201036c6e...
# Bitcoin Core (polar) RPC credentials
BTC_RPC_HOST="http://127.0.0.1:18443"
BTC_RPC_USER="polaruser"
BTC_RPC_PASS="polarpass"
# TON Testnet Wallets
TON_USER_MNEMONIC="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
TON_RESOLVER_MNEMONIC="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon"# Lightning Network swaps
pnpm test -- lightning.spec.ts
# On-chain Bitcoin swaps
pnpm test -- bitcoin.spec.ts
# TON blockchain swaps
pnpm test-ton -- ton-eth.spec.ts