API Reference (v1 spot)
Setup
Prerequisites
Create a NEAR testnet wallet and install the NEAR CLI. Make sure you can interact with testnet.
near login
CLI
You can experiment with Tonic using the CLI.
npm i -g @tonic-foundation/cli
tonic --help
TypeScript
Yarn
yarn add near-api-js @tonic-foundation/tonic @tonic-foundation/utils
NPM
npm install near-api-js @tonic-foundation/tonic @tonic-foundation/utils
Reference Documentation
tip: testnet faucets
We've deployed a few test token contracts, some of which are used in the examples below. Mint some from the testnet faucet if you want to follow along, and feel free to use them in your own experiments.
fake-usdc.testnet
(6 decimals)fake-usdt.testnet
(6 decimals)fake-eth.testnet
(18 decimals)fake-btc.testnet
(8 decimals)
Mint commands (our testnet tokens don't require a storage deposit)
Mint 1000 "USDC"
near call fake-usdc.testnet ft_mint \
'{"account_id": "your-account.testnet", "amount": "1000000000"}' \
--accountId your-account.testnet
Mint 10 "wBTC"
near call fake-btc.testnet ft_mint \
'{"account_id": "your-account.testnet", "amount": "1000000000"}' \
--accountId your-account.testnet
Connect
- CLI
- Browser
- Node
The Tonic CLI is configured the same way as the NEAR CLI. See the NEAR CLI repo for detailed information. TL;DR - if you've logged into an account with the NEAR CLI, you can use that account with the Tonic CLI.
near login
import * as nearApi from 'near-api-js';
import { Tonic } from '@tonic-foundation/tonic';
const CONTRACT_ACCOUNT = 'v1.orderbook.testnet';
const NEAR_CONFIG: nearApi.ConnectConfig = {
networkId: 'testnet',
nodeUrl: 'https://rpc.testnet.near.org',
walletUrl: 'https://wallet.testnet.near.org',
helperUrl: 'https://helper.testnet.near.org',
headers: {},
};
function getNearConfig(): nearApi.ConnectConfig {
return {
keyStore: new nearApi.keyStores.BrowserLocalStorageKeyStore(),
...NEAR_CONFIG,
};
}
const near = new nearApi.Near(getNearConfig());
const wallet = new nearApi.WalletConnection(near, CONTRACT_ACCOUNT);
const tonic = new Tonic(wallet.account(), CONTRACT_ACCOUNT);
(async () => {
console.log(await tonic.getMarkets());
})();
In this example, we read a key from the default NEAR credentials directory (used by the NEAR CLI).
Check out the NEAR SDK docs for more key management options.
import * as nearApi from 'near-api-js';
import path from 'path';
import { homedir } from 'os';
import { Tonic } from '@tonic-foundation/tonic';
const CONTRACT_ACCOUNT = 'v1.orderbook.testnet';
const NEAR_ACCOUNT_ID = 'your-account.example.testnet';
const HOME_DIR = homedir();
const CREDENTIALS_DIR = '.near-credentials';
const DEFAULT_NEAR_CREDENTIALS_DIR = path.join(HOME_DIR, CREDENTIALS_DIR);
const NEAR_CONFIG: nearApi.ConnectConfig = {
networkId: 'testnet',
nodeUrl: 'https://rpc.testnet.near.org',
walletUrl: 'https://wallet.testnet.near.org',
helperUrl: 'https://helper.testnet.near.org',
headers: {},
};
function getKeystore(credentialsDir: string) {
return new nearApi.keyStores.UnencryptedFileSystemKeyStore(credentialsDir);
}
function getNearConfig(credentialsDir: string): nearApi.ConnectConfig {
return {
keyStore: getKeystore(credentialsDir),
...NEAR_CONFIG,
};
}
(async () => {
const near = new nearApi.Near(getNearConfig(DEFAULT_NEAR_CREDENTIALS_DIR));
const account = await near.account(NEAR_ACCOUNT_ID);
const tonic = new Tonic(account, CONTRACT_ACCOUNT);
console.log(await tonic.getMarkets());
})();
Further Reading
List markets
Get a paginated list of all markets from the chain.
- CLI
- JS
tonic list-markets
tonic list-markets --offset 100 --limit 100
await tonic.listMarkets();
await tonic.listMarkets({ offset: 100, limit: 100 });
List markets (data API)
Get a list of all markets supported by market making partners from the data API.
- CLI
curl https://data-api.testnet.tonic.foundation/api/v1/markets?quoteToken=WBTC&baseToken=USDC
Get information about a market
Get data about a market, including its token pair, orderbook, and fees.
- CLI
- JS
tonic get-market $MARKET_ID
await tonic.getMarket(marketId);
Get orderbook and prices
- CLI
- JS
tonic get-open-orders 1434171647166217159
await tonic.getOpenOrders(marketId);
Create an account
NEAR uses storage staking to cover the cost of on-chain storage. You must stake NEAR with the contract in order to place orders and hold balances.
- CLI
- JS
tonic storage-deposit 0.1 --accountId your-account.testnet
You can deposit on behalf of other accounts as well.
tonic storage-deposit 0.1 other-account.testnet --accountId your-account.testnet
await tonic.storageDeposit({ amount: 0.1 });
await tonic.storageDeposit({ amount: 0.1, accountId: "other-account.testnet" });
Deposit funds
Deposit funds into the DEX to begin trading.
- CLI
- JS
# deposit 1000 "USDC"
tonic deposit fake-usdc.testnet 1000 --accountId your-account.testnet
# despoit 10 NEAR
tonic deposit near 10 --accountId your-account.testnet
import BN from "bn.js";
import { decimalToBn } from "@tonic-foundation/utils";
const USDC_DECIMALS = 6;
const NEAR_DECIMALS = 24;
// deposit 1000 USDC
await tonic.deposit("fake-usdc.testnet", decimalToBn(1000, USDC_DECIMALS));
// deposit 10 NEAR
await tonic.deposit(decimalToBn(10, NEAR_DECIMALS));
Withdraw funds
- CLI
- JS
# withdraw 100 "USDC"
tonic withdraw fake-usdc.testnet 100 --accountId your-account.testnet
# withdraw all "USDC"
tonic withdraw fake-usdc.testnet all --accountId your-account.testnet
# withdraw 10 NEAR
tonic withdraw near 10 --accountId your-account.testnet
# withdraw 10 NEAR to another account
tonic withdraw near 10 other-account.testnet --accountId your-account.testnet
// withdraw 100 "USDC"
tonic.withdraw('fake-usdc.testnet', decimalToBn(100, USDC_DECIMALS)); // TODO: flag for raw vs decimal formatted everywhere it's relevant?
// withdraw 10 NEAR
tonic.withdraw('near', { amount: 10 });
// withdraw 10 NEAR to another account
tonic withdraw('near', decimalToBn(10, NEAR_DECIMALS));
Place an order
- CLI
- JS
Place a limit buy for 1 NEAR @ 10 USDC.
tonic place-order $NEAR_USDC_MARKET_ID --buy --type limit --price 10 --quantity 1
tonic place-order $NEAR_USDC_MARKET_ID --buy --type market --quantity 1
const market = await tonic.getMarket(NEAR_USDC_MARKET_ID);
await market.placeOrder({
orderType: "Limit",
side: "Buy",
limitPrice: 10,
maxQuantity: 1,
});
Cancel an order
- CLI
- JS
tonic cancel-order $MARKET_ID $ORDER_ID --accountId your-account.testnet
await tonic.cancelOrder(marketId, orderId);
Cancel all orders
- CLI
- JS
tonic cancel-all-orders $MARKET_ID --accountId your-account.testnet
await tonic.cancelAllOrders(marketId);
List all orders
- CLI
- JS
tonic get-open-orders $MARKET_ID --accountId your-account.testnet
await tonic.getOpenOrders(marketId);
Execute batch actions
- Single market
- Multiple markets
You can execute batch actions in a single market with a high-level API.
import { Tonic } from '@tonic-foundation/tonic';
/**
* Atomically cancel all orders in a market and place a new limit buy.
*/
export async function batchSingle(tonic: Tonic) {
const market = await tonic.getMarket('example-market');
const batch = market.createBatchAction();
batch
.cancelAllOrders()
.newOrder({
side: 'Buy',
orderType: 'Limit',
maxQuantity: 10,
limitPrice: 100,
});
return await tonic.executeBatch(batch);
}
A lower-level API is available for performing batch actions in multiple markets.
import { Tonic } from '@tonic-foundation/tonic';
import { BatchActionV1 } from '@tonic-foundation/tonic/lib/batch';
import { BN } from 'bn.js';
const USDC_DECIMALS = 6;
const WBTC_DECIMALS = 8;
function denomination(decimals: number) {
return new BN(10).pow(new BN(decimals));
}
/**
* Atomically cancel all orders in multiple markets and place a new order in
* each.
*/
export async function batchMultiple(tonic: Tonic) {
const batch = new BatchActionV1();
const marketIds = ['market-1', 'market-2', 'market-3'];
for (const marketId of marketIds) {
batch.cancelAllOrdersV1(marketId);
batch.newOrderV1(marketId, {
order_type: 'Limit',
side: 'Buy',
// limit buy 10 wbtc @ 100 usdc
quantity: new BN(10).mul(denomination(WBTC_DECIMALS)),
limit_price: new BN(100).mul(denomination(USDC_DECIMALS)),
client_id: null,
});
}
return await tonic.executeBatch(batch);
}
Create a market
Alt heading: list your token on Tonic
Tonic supports spot trading with native NEAR and NEP-141 fungible tokens.
Terminology
In the following sections, we'll often refer to base tokens, quote tokens, and lot sizes.
Base and quote tokens refer to the same concepts as in
forex. For example, in
the wBTC/USDC
market, wBTC is the base token, and USDC is the quote token.
Assets trade on Tonic in fixed size lots. Practically speaking, the base lot size is the smallest increment in order size that you can make when placing an order, and the quote lot size is the smallest price tick. For example, the test USDC token has 6 decimals. To use it as a quote currency in a market with a minimum price tick of 0.01 USDC, we'd need to set the quote lot size to 10000.
NOTE: token accounts
The DEX account must have a storage deposit with every token it lists. This is only required the first time a new token is listed on the DEX.
near call ${TOKEN_CONTRACT} storage_deposit \
'{"account_id": "v1.orderbook.testnet", "registration_only": true}' \
--deposit 0.1 \
--accountId your-account.testnet
- CLI
- JS
Create a market between two fungible tokens.
# 3 zeroes in wBTC lot size = 0.00001 wBTC min tick
tonic create-market \
--base fake-btc.testnet \
--quote fake-usdc.testnet \
--baseLotSize 1000 \
--quoteLotSize 10000 \
--accountId your-account.testnet
Create a market between a fungible token and native NEAR.
# 20 zeroes in NEAR lot size = 0.0001 NEAR tick
tonic create-market \
--base near \
--quote fake-usdc.testnet \
--baseLotSize 100000000000000000000 \
--quoteLotSize 10000 \
--accountId your-account.testnet
Create a market between two fungible tokens.
let { result: marketId } = await this.tonic.createMarket({
baseToken: "fake-btc.testnet",
baseLotSize: 1000, // 0.00001 wBTC
quoteToken: "fake-usdc.testnet",
quoteLotSize: 10000, // 0.01 USDC
takerFeeBaseRate: 20, // rate in bps (must be 20)
makerRebateBaseRate: 0, // rate in bps (must be 0)
});
Create a market between a fungible token and native NEAR.
let { result: marketId } = await this.tonic.createMarket({
baseToken: "near",
baseLotSize: 100000000000000000000, // 0.0001 NEAR
quoteToken: "fake-usdc.testnet",
quoteLotSize: 10000, // 0.01 USDC
takerFeeBaseRate: 20, // rate in bps (must be 20)
makeRebateBaseRate: 0, // rate in bps (must be 0)
});