Full documentation on the OutLayer dashboard.
This project implements token swaps using NEAR Intents protocol, executed off-chain via NEAR OutLayer platform.
User → ft_transfer_call → Intents Contract
↓
request_execution → outlayer.near
↓
OutLayer Worker (compiles + runs WASI)
↓
WASI Binary (Rust + HTTP)
1. Get quote from NEAR Intents API
2. Publish signed swap intent
3. Wait for settlement
4. Withdraw tokens to swap contract
↓
Callback → Intents Contract
↓
ft_transfer → User (tokens out)
- Target:
wasm32-wasip2(requires HTTP support) - Input: JSON with swap parameters (tokens, amounts, sender)
- Output: JSON with swap result (success, amount_out, intent_hash)
- Secrets: Requires
OPERATOR_PRIVATE_KEYandOPERATOR_ACCOUNT_IDfrom env vars - HTTP: Makes requests to NEAR Intents API (
https://solver-relay-v2.chaindefuser.com/rpc)
- Based on: near-intents-onchain
- Modified for: OutLayer integration instead of direct yield/resume
- Key features:
- Token whitelist with defuse asset IDs
ft_on_transfer- accepts tokens and initiates swaprequest_execution- calls OutLayer with secretson_swap_result- callback with result or refund
# Add target
rustup target add wasm32-wasip2
# Build release
cargo build --target wasm32-wasip2 --release
# Output: target/wasm32-wasip2/release/intents-example.wasmcd intents-contract
./build.sh
# Output: intents-contract/res/intents_contract.wasmStore the operator's private key in OutLayer secrets:
# Using dashboard (http://localhost:3000/secrets)
# Or via contract call:
near call outlayer.near store_secrets '{
"repo": "github.com/out-layer/intents-example",
"branch": "main",
"profile": "production",
"encrypted_secrets": [1,2,3,...], # Encrypted JSON
"access_condition": {"AllowAll": {}}
}' --accountId operator.testnet --deposit 0.1
# Encrypted JSON format:
# {
# "OPERATOR_PRIVATE_KEY": "ed25519:...",
# "OPERATOR_ACCOUNT_ID": "operator.testnet"
# }near contract deploy intents-swap.testnet \
use-file intents-contract/res/intents_contract.wasm \
with-init-call new \
json-args '{
"owner_id": "owner.testnet",
"operator_id": "operator.testnet",
"secrets_profile": "production"
}' \
prepaid-gas '100.0 Tgas' \
attached-deposit '0 NEAR' \
network-config testnet \
sign-with-keychain \
send# Whitelist WNEAR
near call intents-swap.testnet whitelist_token '{
"token_id": "wrap.near",
"symbol": "WNEAR",
"decimals": 24,
"defuse_asset_id": "nep141:wrap.near"
}' --accountId owner.testnet
# Whitelist USDC
near call intents-swap.testnet whitelist_token '{
"token_id": "17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"symbol": "USDC",
"decimals": 6,
"defuse_asset_id": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1"
}' --accountId owner.testnet# User transfers tokens to swap contract with message
near call wrap.near ft_transfer_call '{
"receiver_id": "intents-swap.testnet",
"amount": "1000000000000000000000000",
"msg": "{\"Swap\":{\"token_out\":\"17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1\",\"min_amount_out\":\"900000\"}}"
}' --accountId user.testnet --depositYocto 1 --gas 300000000000000
# Contract will:
# 1. Call OutLayer with WASI repo
# 2. WASI binary executes swap via NEAR Intents
# 3. Callback transfers output tokens to user# Get contract config
near view intents-swap.testnet get_config
# Check if token is whitelisted
near view intents-swap.testnet is_token_whitelisted '{"token_id":"wrap.near"}'
# Get token configuration
near view intents-swap.testnet get_token_config '{"token_id":"wrap.near"}'Based on NEAR Intents supported tokens:
- WNEAR:
wrap.near→nep141:wrap.near - USDC:
17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1 - LONK:
token.lonkingnearbackto2024.near - NEKO:
ftv2.nekotoken.near - BLACKDRAGON:
blackdragon.tkn.near - SHITZU:
token.0xshitzu.near - NEARVIDIA:
nearnvidia.near
- HTTP Client:
wasi-http-clientcrate for API requests - Cryptography:
ed25519-dalekfor NEP-413 signing - Borsh: NEP-413 payload serialization
- Base58: Key encoding/decoding (
bs58crate)
- Gas: 50 TGas for callback, reserves most gas for OutLayer execution
- Deposit: 0.05 NEAR minimum to cover OutLayer costs (refunded to user)
- Refunds: Automatic refund on failure (WASI error, insufficient liquidity, etc.)
- Storage: Tracks pending swaps until callback completes
- Secrets: Operator private key stored encrypted in OutLayer
- Access Control: Only whitelisted tokens can be swapped
- Pause: Owner can pause contract in emergency
- Refunds: All failed swaps automatically refund input tokens
# Build WASI
cargo build --target wasm32-wasip2 --release
# Test with wasmtime (requires secrets as env vars)
export OPERATOR_PRIVATE_KEY="ed25519:..."
export OPERATOR_ACCOUNT_ID="operator.testnet"
echo '{
"sender_id": "user.testnet",
"token_in": "nep141:wrap.near",
"token_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount_in": "1000000000000000000000000",
"min_amount_out": "900000",
"swap_contract_id": "intents-swap.testnet"
}' | wasmtime run --wasi preview2 target/wasm32-wasip2/release/intents-example.wasm- Deploy contract and whitelist tokens
- Store operator secrets in OutLayer
- Execute test swap with small amount
- Check logs for swap execution
- Verify user received output tokens
- Ensure secrets are stored in OutLayer contract
- Check
secrets_profilematches in contract config - Verify
operator_idis correct
- Call
whitelist_tokenfor both input and output tokens - Check token addresses match exactly
- Quote from NEAR Intents API returned amount <
min_amount_out - Try reducing
min_amount_outor increasingamount_in - Check liquidity on NEAR Intents platform
- NEAR Intents API timeout (30 seconds)
- Check solver availability on NEAR Intents
- Retry swap later
Tests storage registration for fungible tokens:
cd ../wasi-test-runner
cargo run --release -- \
--wasm ../intents-example/target/wasm32-wasip2/release/intents-example.wasm \
--input-file ../intents-example/test-storage.json \
--env "SWAP_CONTRACT_ID=your-account.testnet" \
--env "SWAP_CONTRACT_PRIVATE_KEY=ed25519:YOUR_KEY" \
--env "NEAR_RPC_URL=https://rpc.testnet.near.org" \
--max-instructions 50000000000What it does:
- Checks if account is registered with
wrap.testnet - If not registered: calls
storage_deposit(costs ~0.00125 NEAR) - If registered: shows current balance and skips transaction
Input: test-storage.json
{
"action": "test_storage",
"token_contract": "wrap.testnet"
}Tests complete USDC → WNEAR swap using NEAR Intents API:
cd ../wasi-test-runner
cargo run --release -- \
--wasm ../intents-example/target/wasm32-wasip2/release/intents-example.wasm \
--input-file ../intents-example/test-swap-usdc-wnear.json \
--env "SWAP_CONTRACT_ID=publishintent.near" \
--env "SWAP_CONTRACT_PRIVATE_KEY=ed25519:YOUR_KEY" \
--env "NEAR_RPC_URL=https://rpc.mainnet.near.org" \
--max-instructions 100000000000- Swap contract must have at least 0.01 USDC
- Sender must be registered with wrap.near (storage deposit)
- Uses mainnet (NEAR Intents only works on mainnet)
What it does:
- Pre-flight: Get quote + check storage
- Deposit 0.01 USDC to intents.near
- Publish swap intent to NEAR Intents API
- Wait for settlement (max 30 seconds)
- Withdraw WNEAR to sender
Input: test-swap-usdc-wnear.json
{
"sender_id": "publishintent.near",
"token_in": "nep141:17208628...36133a1",
"token_out": "nep141:wrap.near",
"amount_in": "10000",
"min_amount_out": "1000000000000000000000",
"swap_contract_id": "publishintent.near"
}See: TEST_SWAP_FLOW.md for detailed testing guide.
MIT