Game plan
-
Single DEX swaps for REF and Jumbo
- Study REF's router
- Study the provider object returned by the wallet adapter.
- Study Jupiter's SDK. Follow its naming conventions.
- Write SDK: wrapper on a graphQL API
-
/quote
: Get a ranked list of possible routes -
/swap
: Get serialized TX to swap
-
-
Split routes (v2): Use
NearWalletSelector.signAndSendTransactions
. The wallet can sign multiple TXs directed to different contracts using https://github.com/ref-finance/ref-ui/blob/19d5bee1c40e0b98686965e19a590a7f3ea27ac0/src/utils/sender-wallet.ts#L163. Eg. for adding liquidity in REF, you first callft_transfer_call()
for each token, then call theadd_liquidity
function on REF. -
Multi leg swaps and Orderbook based swaps- todo
-
Aurora based swaps- Copy code from REF's UI. This is an add-on for the aggregator.
-
TXs
- Token storage deposit: https://explorer.mainnet.near.org/transactions/AVdCHhFM46mYdAUes4EqbXEMfDo8sYfMqC6oCKF5qspT
- Transfer token to Aurora: https://explorer.mainnet.near.org/transactions/84kGefNaj3ZKZeFGJi5pW7NBjHAfPMtLvHXVdDP47xNN
- Opaque messages sent to Aurora: https://explorer.mainnet.near.org/transactions/5oFCN1aLrCCGJXLi2PEAWB7xwJ1ZnUiGrQGMTSvap2E9, https://explorer.mainnet.near.org/transactions/8TC2XcdUsKNqSnsWQfkkx5WUkmkmXLx6KM9MoGkvzwdA, https://explorer.mainnet.near.org/transactions/5KnMNnaswMULq2b4MiKrveUJDecaNKgZ4LtoeFgA37Jw
DEX aggregator test setup
-
Single DEX swap
- Best route tests are not immediate priority. We can setup local pools and compare results.
- No tests for returned TX structure.
-
Multi leg swaps
-
Deploy an instance of test-token for every token needed by pools.
-
Deploy pools for every token pair
-
Test cases:
- Ref- single and double swap
- Combination of Ref and Jumbo
Stableswap has the same swap interface as regular pools, so we do not need separate tests. Mainnet stableswap pool ID is 1910.
-
Have separate unit tests to find best route
-
Execution plan
- Call
ft_transfer_call
on the input token withv1.comet.near
as the destination. - The args are
{
"receiver_id": "v1.comet.near",
"amount" : "100000",
"msg": {
"referral_id": "vault.comet.near",
"dexes": [
{
"dex": "v2.ref-finance.near",
"token_in": "wrap.near",
// Do not decode
"actions": [{
"pool_id": 0,
"token_in": "wrap.near",
"token_out": "dai.near",
"amount_in": "100000",
"min_amount_out": "5000",
}],
},
{
"dex": "spot.spin-fi.testnet",
"token_in": "dai.near",
// do not decode
"actions": [{
"market_id": 1,
"drop": ["23"],
"place": [
{
"price": "2000000000000000000000000",
"quantity": "1000000000000000000000000",
"ttl": 604800,
"market_order": true,
"order_type": "Bid",
"client_order_id": 1
}
]
}]
}
]
}
}
-
v1.comet.near
will get this message throughft_on_transfer
. -
For each dex
- Read the
dex
andtoken_in
. - Call
token_in::ft_transfer_call
, withdex
as thereciever_id
. The message format should be
{ "force": 0, "actions": {}, // decoded actions array "referral_id": "" // the passed reciever ID }
- Read the
-
Keep a whitelist of allowed DEXes to prevent fund loss.
-
Spin needs 3 calls
- Deposit input token with
ft_transfer_call
- Place market orders with
place_bid
orplace_ask
. Market orders are executed immediately. Multi-market swaps can be atomically performed usingbatch_ops
. - Withdraw tokens with
withdraw
function
- Deposit input token with
Gas research
-
1 TGas = 10^12 gas
-
3 pool swap on REF: 47 TGas
- Tx to reciept conversion: 2 Tgas
- Transfer wNEAR to Ref: 10 TGas
- Ref swap: 22 TGas
- Transfer output token to user: 5 TGas
- Withdraw callback: 3 Tgas
- Token transfer callback: 2 Tgas
-
Max gas per call: 300 TGas. This should be sufficient for 3 REF-like swaps.
-
swap() based method: takes 139 TGas.
-
Instant swap method: 121 TGas
- 100 TGas if ft_balance_of and callback is removed
- Instant swap without outbound transfer: 84
wNEAR -> USDT issue
-
REF UI returns wrap.near -> DAI (6b175474e89094c44da98b954eedeac495271d0f.factory.bridge.near) -> USDT
-
SDK returns wrap.near -> USDC (a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.factory.bridge.near) -> USDT
It's using uni v2 algorithm on the stable pool 1910.
Stableswap support
- Rated pools
- Filter them in stableSmart, but include in hybrid smart.
Spin integration
- Read output amount with
near view spot.spin-fi.near dry_run_swap '{"swap":{"market_id":1,"price":"3267000"},"token":"near.near","amount":"1000000000000000000000000000"}'
Result
{
refund: '251170000000000000000000000',
received: '2470760500',
fee: '2470760'
}
- Only single hops are supported
- Save a JSON file of spin markets
- Dealing with refunds- if refund amount is large, the swap is not competitive and will be subsumed by another DEX. We can skip this field.
- Market 1 (NEAR/USDC) deals with NEAR, not wNEAR. We need wrapper logic before this pool can be supported.
- `price` field is for slippage. It's needed for swaps and dry runs.
- Read markets with
NEAR_ENV=mainnet near view spot.spin-fi.near get_markets
[
{
id: 2,
ticker: 'USN/USDC',
base: { id: 3, symbol: 'USN', decimal: 18, address: 'usn' },
quote: {
id: 2,
symbol: 'USDC',
decimal: 6,
address: 'a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.factory.bridge.near'
},
fees: {
maker_fee: '200',
taker_fee: '400',
decimals: 6,
is_rebate: true
},
availability: { allow_place: true, allow_cancel: true },
limits: {
tick_size: '1000',
step_size: '1000000000000000',
min_base_quantity: '500000000000000000',
max_base_quantity: '501000000000000000000',
min_quote_quantity: '500000',
max_quote_quantity: '501000000',
max_bid_count: 20,
max_ask_count: 20
}
},
]
- Read orderbooks with
NEAR_ENV=mainnet near view spot.spin-fi.near get_orderbook '{ "market_id": 1, "limit": 4 }'
{
ask_orders: [
{ price: '3290000', quantity: '461670000000000000000000000' },
{ price: '3300000', quantity: '24290000000000000000000000' },
{ price: '3700000', quantity: '5050000000000000000000000' },
{ price: '3750000', quantity: '5000000000000000000000000' }
],
bid_orders: [
{ price: '3270000', quantity: '720890000000000000000000000' },
{ price: '3260000', quantity: '38220000000000000000000000' },
{ price: '3000000', quantity: '14000000000000000000000000' },
{ price: '2900000', quantity: '20000000000000000000000000' }
]
}
- bid = buy, ask = sell
- Quantity is in terms of base.
- Price: units of base currency (NEAR), per unit of quote currency with decimal places included (USDC)
-
Swap TX
- Price field is for slippage
- Run
ft_transfer_call()
on the input token
{
"receiver_id": "spot.spin-fi.near",
"amount": "500000",
"msg": "{\"market_id\":2,\"price\":\"1100000\"}"
}
Issues
- Tried to swap 100 Aurora for USDC.
{
id: 3,
ticker: 'AURORA/USDC',
base: {
id: 4,
symbol: 'AURORA',
decimal: 18,
address: 'aaaaaa20d9e0e2461697782ef11675f668207961.factory.bridge.near'
},
quote: {
id: 2,
symbol: 'USDC',
decimal: 6,
address: 'a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.factory.bridge.near'
},
fees: {
maker_fee: '500',
taker_fee: '1000',
decimals: 6,
is_rebate: true
},
availability: { allow_place: true, allow_cancel: true },
limits: {
tick_size: '10000',
step_size: '10000000000000000',
min_base_quantity: '100000000000000000',
max_base_quantity: '300000000000000000000',
min_quote_quantity: '500000',
max_quote_quantity: '500000000',
max_bid_count: 20,
max_ask_count: 20
}
},
Tonic integration
- Fetch markets:
J5mggeEGCyXVUibvYTe9ydVBrELECRUu23VRk2TwC2is
is USN/USDC market. USN is base, USDC quote.
curl https://data-api.mainnet.tonic.foundation/api/v1/markets
near view v1.orderbook.near list_markets '{ "from_index": 0, "limit": 10 }'
- Swap using
ft_transfer_call()
on input token
{
"receiver_id": "v1.orderbook.near",
"amount" : "100000",
"msg": {
"type": "Swap",
"market_id": "J5mggeEGCyXVUibvYTe9ydVBrELECRUu23VRk2TwC2is",
"side": "Buy",
"min_output_token": 0
}
}
NEAR_ENV=mainnet near call a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.factory.bridge.near ft_transfer_call '{"receiver_id": "v1.orderbook.near", "amount": "1000000", "msg": "{ \"type\": \"Swap\", \"market_id\": \"J5mggeEGCyXVUibvYTe9ydVBrELECRUu23VRk2TwC2is\", \"side\": \"Buy\", \"min_output_token\": \"0\" }"}' --deposit 0.000000000000000000000001 --accountId monkeyis.near --gas 300000000000000
- Unlike Spin, Tonic always charges taker fee on the quote token.
- The output amount is rounded as a multiple of lot size. Dust is lost to the exchange.
{ refund: '0', received: '1000000', fee: '400' }
Wrapped NEAR
- Wrap
near call wrap.near near_deposit --deposit 1 --accountId monkeyis.near
- Unwrap
near call wrap.near near_withdraw '{ "amount": "1000000000000000000000000" }' --accountId monkeyis.near --depositYocto 1
-
TX structure
- Input token is wNEAR: Batch near_deposit action with ft_transfer_call. Have a check to prepend the wrapping instruction.
- Input token is NEAR on orderbooks: Have a check to call the alternate function.
- Output token is NEAR, but swap gives out wNEAR: Append an unwrapping action.
-