├── .gitignore ├── README.md ├── package.json ├── src ├── 0x.ts ├── Veil.ts ├── errors.ts ├── index.ts └── provider.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | yarn.lock 4 | yarn-error.log 5 | .env 6 | .env.prod 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `veil-js` 2 | 3 | `veil-js` is a TypeScript/Javascript library for interacting with the Veil markets and trading API. 4 | 5 | Install: 6 | 7 | ```bash 8 | yarn add veil-js 9 | ``` 10 | 11 | # v1 Docs 12 | 13 | _Note: these are the docs for version 2.0.0, which makes some changes to the `Veil` constructor and the `createQuote` method._ For v1 docs, please [go here](https://github.com/veilco/veil-js/blob/dacc4e388494a528e210a615f315a511409c7367/README.md). 14 | 15 | ## Questions? 16 | 17 | Join us on [Discord](https://discord.gg/aBfTCVU) or email us at `hello@veil.market`. If you encounter a problem using `veil-js`, feel free to [open an issue](https://github.com/veilco/veil-js/issues). 18 | 19 | `veil-js` is maintained by [@gkaemmer](https://github.com/gkaemmer), [@mertcelebi](https://github.com/mertcelebi), and [@pfletcherhill](https://github.com/pfletcherhill). 20 | 21 | ## Usage 22 | 23 | You can use the API with or without authenticating using your ethereum address. The constructor has the following signature: 24 | 25 | ```typescript 26 | new Veil(options: { 27 | mnemonic?: string, 28 | address?: string, 29 | apiUrl?: string = 'https://api.kovan.veil.co', 30 | provider?: Provider 31 | }) 32 | ``` 33 | 34 | Note that the default API is our testnet server. If you want to use mainnet, you must explicitly pass `"https://api.veil.co"` as the third constructor parameter. 35 | 36 | Full example: 37 | 38 | ```typescript 39 | import Veil from "veil-js"; 40 | 41 | // Without authentication 42 | const veil = new Veil(); 43 | const markets = await veil.getMarkets(); 44 | console.log(markets); // { results: [{ slug: "...", ... }], total: 35, ... } 45 | 46 | // With authentication 47 | // Note: you must have registered on Veil using this address 48 | const mnemonic = 49 | "unveil unveil unveil unveil unveil unveil unveil unveil unveil unveil unveil unveil"; 50 | const address = "0x5b5eae94bf37ff266955e46fdd38932346cc67e8"; 51 | const veil = new Veil({ mnemonic, address }); 52 | const myOrders = await veil.getUserOrders(markets[0]); 53 | ``` 54 | 55 | ## Pages 56 | 57 | All API methods that return lists (`getMarkets`, `getBids`, `getAsks`, `getOrderFills`, and `getUserOrders`) return `Page` objects that have the following form: 58 | 59 | ```js 60 | { 61 | results: [ ... ], 62 | total: 35, 63 | page: 0, 64 | pageSize: 100, // Depends on method 65 | } 66 | ``` 67 | 68 | All of these methods also take a optional `page` argument, which you can use to fetch additional pages. 69 | 70 | ## Shares primer 71 | 72 | Veil markets are built on [Augur](https://docs.augur.net/) and inherit the basic mechanics of Augur shares and prices. 73 | 74 | A Veil market has two tokens: LONG and SHORT. By holding shares of LONG or SHORT tokens, you hold a "position" in the market. When the market ends, its LONG and SHORT shares are redeemable for ETH, with the rates depending on the market's result. 75 | 76 | In yes/no markets (e.g. "Will ETH be above \$100 at the end of 2018?"), the payout goes entirely to one share token—LONG if the market resolves to "Yes" and SHORT if the market resolves to "No". 77 | 78 | In scalar markets (e.g. "What will be the price of ETH at the end of 2018?"), the payout is split between LONG and SHORT tokens according to where the result (e.g. the price of ETH) falls within the market's "bounds" (set by `minPrice` and `maxPrice`). 79 | 80 | > Note: Together, 1 LONG share and 1 SHORT share are always redeemable for exactly 1 ETH. 81 | 82 | The price of a Veil share token is therefore always somewhere between 0 and 1 ETH per share, depending on what the market predicts that each token's payout will be. 83 | 84 | ## Units and types 85 | 86 | - Dates are returned as an integer number of milliseconds since the Unix epoch. 87 | - Numbers are returned as strings for precision. We recommend using a library such as [bignumber.js](https://github.com/MikeMcl/bignumber.js/) to perform math on them. 88 | 89 | ## Methods 90 | 91 | All methods return promises, and can be used with `async/await`. 92 | 93 | ### `veil.getMarkets(params: { channel?: string; status?: "open" | "resolved", page?: number })` 94 | 95 | Fetches all markets, optionally filtered by `channel` (`btc`, `rep`, `meme`) or status (`open` or `resolved`). A maximum of 10 markets are returned per page, and you can specify pages using the `page` option. 96 | 97 | Example response: 98 | 99 | ```js 100 | { 101 | results: [ 102 | ... 103 | ], 104 | total: 35, 105 | page: 0, 106 | pageSize: 10 107 | } 108 | ``` 109 | 110 | See `getMarket` for an example market object. 111 | 112 | ### `veil.getMarket(slug: string)` 113 | 114 | Fetches details about a single market. Example response: 115 | 116 | ```js 117 | { 118 | name: 119 | "What will be the 7-day average gas price on the Ethereum blockchain at 12am UTC on December 1, 2018?", 120 | address: "0x4ebfc291176e4b6d0dbd555ef37541681c0c07eb", 121 | details: "For details see https://veil.market/contract/gas-gwei.", 122 | createdAt: 1543017685308, 123 | endsAt: 1543622400000, 124 | numTicks: "10000", 125 | minPrice: "12750000000000000000", 126 | maxPrice: "14980000000000000000", 127 | limitPrice: null, 128 | type: "scalar", 129 | uid: "4190e964-1e8d-4b11-85b8-ac421634fcda", 130 | slug: "gas-gwei-7d-2018-12-01", 131 | result: null, 132 | longBuybackOrder: null, 133 | shortBuybackOrder: null, 134 | longToken: "0x88596d175e3098d4a4d51195b55153cf4b5058b8", 135 | shortToken: "0x598b46d68e3e03f810a45d7d8dc9af5afdbafd56", 136 | denomination: "Gwei", 137 | index: "gas-gwei-7d", 138 | predictedPrice: "5845", 139 | metadata: {}, 140 | finalValue: null 141 | } 142 | ``` 143 | 144 | ### `veil.getBids(market: Market, tokenType: "long" | "short")` and `veil.getAsks(market: Market, tokenType: "long" | "short")` 145 | 146 | The `getBids` and `getAsks` methods let you fetch the order book for a market. You can fetch orders for either LONG or SHORT tokens by passing `tokenType` as a second argument (in Veil markets, the LONG and SHORT order books are always mirror images of each other). 147 | 148 | Bids are sorted by price descending, and asks are sorted by price ascending, so you can get the spread of a market by comparing the first bid and first ask. 149 | 150 | Example response: 151 | 152 | ```js 153 | { 154 | results: [ 155 | { 156 | price: "7000", 157 | tokenAmount: "100000000000000" 158 | }, 159 | { 160 | price: "7100", 161 | tokenAmount: "50000000000000" 162 | }, 163 | ... 164 | ], 165 | total: 45, 166 | page: 0, 167 | pageSize: 10000 168 | } 169 | ``` 170 | 171 | ### `veil.getOrderFills(market: Market, tokenType: "long" | "short", options?: { page: number })` 172 | 173 | Fetches the order fill history in a market for tokens of type `tokenType` (LONG or SHORT). Example response: 174 | 175 | ```js 176 | { 177 | results: [ 178 | { 179 | createdAt: 1544094244674, 180 | status: "completed", 181 | tokenAmount: "100000000000000", 182 | price: "4320", 183 | side: "buy", 184 | uid: "dac7a4b5-bed5-4a07-85db-f5ff97a7f3d1", 185 | }, 186 | ... 187 | ], 188 | total: 45, 189 | page: 0, 190 | pageSize: 100 191 | } 192 | ``` 193 | 194 | ### `veil.createQuote(market: Market, side: "buy" | "sell", tokenType: "long" | "short", params: MarketOrderParams | LimitOrderParams)` 195 | 196 | Creates a Veil quote, which is used to calculate fees and generate an unsigned 0x order, which is required to create a Veil order. 197 | 198 | The `params` argument must be one of the following types (depending on whether you wish to create a market order or a limit order): 199 | 200 | ```typescript 201 | // Create a market order with particular amount of ETH 202 | interface MarketOrderCurrencyParams { 203 | type: "market"; 204 | currencyAmount: number | BigNumber; 205 | } 206 | 207 | // Create a market order for a particular amount of tokens 208 | interface MarketOrderTokenParams { 209 | type: "market"; 210 | tokenAmount: number | BigNumber; 211 | } 212 | 213 | // Create a limit order 214 | interface LimitOrderParams { 215 | type: "limit"; 216 | tokenAmount: number | BigNumber; 217 | price: number | BigNumber; 218 | } 219 | ``` 220 | 221 | > **Note**: when passing a `BigNumber` instance, `price` is a number between 0 and `market.numTicks`, which is normally 10000 for Veil markets (except scalars). A price of 6000 is equivalent to 0.6 ETH/share. 222 | 223 | Example response: 224 | 225 | ```js 226 | { 227 | uid: "5d93b874-bde1-4af1-b7af-ae726943f549", 228 | orderHash: "0x39c5934cff5e608743f845a8c6950cc897ed75d8127023887d9715fa3c60c27c", 229 | createdAt: 1543510274469, 230 | expiresAt: 100000000000000, 231 | quoteExpiresAt: 1543510334469, 232 | token: "0x598b46d68e3e03f810a45d7d8dc9af5afdbafd56", 233 | currency: "0xe7a67a41b4d41b60e0efb60363df163e3cb6278f", 234 | side: "buy", 235 | type: "limit", 236 | currencyAmount: "119550000000000000", 237 | tokenAmount: "50000000000000", 238 | fillableTokenAmount: "0", 239 | feeAmount: "1195500000000000", 240 | price: "2391", 241 | zeroExOrder: { 242 | salt: "35666599517228498817069108086005958238926633694259560734477953229163342485507", 243 | makerFee: "0", 244 | takerFee: "0", 245 | makerAddress: "0x8f736a3d32838545f17d0c58d683247bee1a7ea5", 246 | takerAddress: "0xe779275c0e3006fe67e9163e991f1305f1b6fe99", 247 | senderAddress: "0xe779275c0e3006fe67e9163e991f1305f1b6fe99", 248 | makerAssetData: "0xf47261b0000000000000000000000000e7a67a41b4d41b60e0efb60363df163e3cb6278f", 249 | takerAssetData: "0xf47261b0000000000000000000000000598b46d68e3e03f810a45d7d8dc9af5afdbafd56", 250 | exchangeAddress: "0x35dd2932454449b14cee11a94d3674a936d5d7b2", 251 | makerAssetAmount: "120745500000000000", 252 | takerAssetAmount: "50000000000000", 253 | feeRecipientAddress: "0x0000000000000000000000000000000000000000", 254 | expirationTimeSeconds: "100000000600" 255 | } 256 | } 257 | ``` 258 | 259 | ### `veil.createOrder(quote: Quote, options?: { postOnly: boolean })` 260 | 261 | Creates an order using an generated quote. This method signs the 0x order using your mnemonic and address provided to the constructor. 262 | 263 | Example response: 264 | 265 | ```js 266 | { 267 | uid: "77fb963c-6b78-48ce-b030-7a08246e1f9f", 268 | status: "open", 269 | createdAt: 1543510274884, 270 | expiresAt: 100000000000000, 271 | type: "limit", 272 | tokenType: "short", 273 | side: "buy", 274 | price: "2391", 275 | token: "0x598b46d68e3e03f810a45d7d8dc9af5afdbafd56", 276 | tokenAmount: "50000000000000", 277 | tokenAmountFilled: "0", 278 | currency: "0xe7a67a41b4d41b60e0efb60363df163e3cb6278f", 279 | currencyAmount: "119550000000000000", 280 | currencyAmountFilled: "0", 281 | postOnly: false, 282 | market: null 283 | } 284 | ``` 285 | 286 | ### `veil.cancelOrder(uid: string)` 287 | 288 | Cancels an order. Returns the order that was canceled. 289 | 290 | ### `veil.getUserOrders(market: Market, options?: { page?: number, status?: "open" | "filled" | "canceled" | "expired" })` 291 | 292 | Fetches all orders that you've created in a particular market, including orders that have been filled. 293 | 294 | Example response: 295 | 296 | ```js 297 | { 298 | results: [ 299 | { 300 | uid: "3e4fd40d-176f-432f-8f0b-d0a600d55a1f", 301 | status: "filled", 302 | createdAt: 1543509213537, 303 | expiresAt: 100000000000000, 304 | type: "limit", 305 | tokenType: "short", 306 | side: "buy", 307 | price: "2293", 308 | token: "0x598b46d68e3e03f810a45d7d8dc9af5afdbafd56", 309 | tokenAmount: "100000000000000", 310 | tokenAmountFilled: "0", 311 | currency: "0xe7a67a41b4d41b60e0efb60363df163e3cb6278f", 312 | currencyAmount: "229300000000000000", 313 | currencyAmountFilled: "0", 314 | postOnly: false, 315 | market: null 316 | fills: [...] // Order fills associated with the order 317 | }, 318 | ... 319 | ], 320 | total: 45, 321 | page: 0, 322 | pageSize: 10000 323 | } 324 | ``` 325 | 326 | ### `veil.getMarketBalances(market: Market)` 327 | 328 | Requires authentication. Returns the market balances for the authenticated user. Example response: 329 | 330 | ```js 331 | { 332 | "longBalance": "10000000000000", 333 | "longBalanceClean": "100000000000000000", // long_balance * market.num_ticks 334 | "shortBalance": "40000000000000", 335 | "shortBalanceClean": "400000000000000000", // short_balance * market.num_ticks 336 | "veilEtherBalance": "200000000000000000", 337 | "etherBalance": "200000000000000000" 338 | } 339 | ``` 340 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "veil-js", 3 | "version": "2.0.0", 4 | "description": "Veil API Wrapper", 5 | "main": "dist/index.js", 6 | "author": "Veil, Inc.", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc", 10 | "watch": "tsc --watch", 11 | "prepublishOnly": "npm run build", 12 | "postinstall": "rm -f node_modules/web3/index.d.ts" 13 | }, 14 | "dependencies": { 15 | "@0x/order-utils": "^7.0.0", 16 | "@0x/subproviders": "^4.0.0", 17 | "@0x/utils": "^4.2.0", 18 | "@0x/web3-wrapper": "^6.0.0", 19 | "ethereum-types": "^2.1.0", 20 | "ethers": "^4.0.1", 21 | "humps": "^2.0.1", 22 | "lodash": "^4.17.11", 23 | "node-fetch": "^2.2.0" 24 | }, 25 | "devDependencies": { 26 | "@types/lodash": "^4.14.117", 27 | "@types/node": "^10.9.4", 28 | "@types/node-fetch": "^2.1.2", 29 | "@types/web3": "^1.0.3", 30 | "ts-node": "^7.0.1", 31 | "typescript": "^3.0.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/0x.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "@0x/utils"; 2 | import { 3 | signatureUtils, 4 | orderHashUtils, 5 | Order, 6 | SignedOrder 7 | } from "@0x/order-utils"; 8 | import { Provider } from "ethereum-types"; 9 | 10 | export async function signOrder( 11 | provider: Provider, 12 | zeroExOrder: Order 13 | ): Promise { 14 | const orderHash = orderHashUtils.getOrderHashHex({ 15 | ...zeroExOrder, 16 | expirationTimeSeconds: new BigNumber(zeroExOrder.expirationTimeSeconds), 17 | makerFee: new BigNumber(zeroExOrder.makerFee), 18 | makerAssetAmount: new BigNumber(zeroExOrder.makerAssetAmount), 19 | salt: new BigNumber(zeroExOrder.salt), 20 | takerFee: new BigNumber(zeroExOrder.takerFee), 21 | takerAssetAmount: new BigNumber(zeroExOrder.takerAssetAmount) 22 | }); 23 | const signature = await signatureUtils.ecSignHashAsync( 24 | provider, 25 | orderHash, 26 | zeroExOrder.makerAddress 27 | ); 28 | 29 | // Append signature to order 30 | const signedOrder = { 31 | ...zeroExOrder, 32 | signature 33 | }; 34 | return signedOrder; 35 | } 36 | -------------------------------------------------------------------------------- /src/Veil.ts: -------------------------------------------------------------------------------- 1 | const { camelizeKeys } = require("humps"); 2 | import some = require("lodash/some"); 3 | import getProvider from "./provider"; 4 | import { BigNumber } from "@0x/utils"; 5 | import { Order as ZeroExOrder } from "@0x/order-utils"; 6 | import { Provider } from "ethereum-types"; 7 | import { signOrder } from "./0x"; 8 | import fetch from "node-fetch"; 9 | import { VeilError } from "./errors"; 10 | import { Web3Wrapper } from "@0x/web3-wrapper"; 11 | import { utils } from "ethers"; 12 | 13 | export interface Market { 14 | slug: string; 15 | uid: string; 16 | endsAt: number; 17 | shortToken: string; 18 | longToken: string; 19 | numTicks: string; 20 | minPrice: string; 21 | maxPrice: string; 22 | orders?: Order[]; 23 | index: string; 24 | limitPrice: string; 25 | type: string; 26 | channel: string; 27 | } 28 | 29 | export interface Order { 30 | uid: string; 31 | price: string; 32 | side: "buy" | "sell"; 33 | tokenAmount: string; 34 | tokenAmountUnfilled: string; 35 | status: "open" | "filled" | "canceled"; 36 | tokenType: "short" | "long"; 37 | fills: PartialOrderFill[]; 38 | } 39 | 40 | export interface PartialOrderFill { 41 | uid: string; 42 | createdAt: string; 43 | status: "pending" | "completed" | "failed"; 44 | tokenAmount: string; 45 | } 46 | 47 | export interface OrderFill { 48 | uid: string; 49 | price: string; 50 | side: "buy" | "sell"; 51 | tokenAmount: string; 52 | status: "pending" | "completed"; 53 | createdAt: number; 54 | } 55 | 56 | export interface OrderBookRow { 57 | price: string; 58 | tokenAmount: string; 59 | } 60 | 61 | export interface Quote { 62 | uid: string; 63 | zeroExOrder: ZeroExOrder; 64 | } 65 | 66 | export interface DataFeedEntry { 67 | value: string; 68 | timestamp: number; 69 | } 70 | 71 | export interface DataFeed { 72 | uid: string; 73 | name: string; 74 | description: string; 75 | denomination: string; 76 | entries: DataFeedEntry[]; 77 | } 78 | 79 | export interface Page { 80 | results: T[]; 81 | page: number; 82 | pageSize: number; 83 | total: number; 84 | } 85 | 86 | export interface MarketBalances { 87 | longBalance: string; 88 | shortBalance: string; 89 | longBalanceClean: string; 90 | shortBalanceClean: string; 91 | veilEtherBalance: string; 92 | etherBalance: string; 93 | } 94 | 95 | interface LimitOrderParams { 96 | type: "limit"; 97 | price: number | BigNumber; 98 | tokenAmount: number | BigNumber; 99 | } 100 | 101 | interface MarketOrderCurrencyParams { 102 | type: "market"; 103 | currencyAmount: number | BigNumber; 104 | } 105 | 106 | interface MarketOrderTokenParams { 107 | type: "market"; 108 | tokenAmount: number | BigNumber; 109 | } 110 | 111 | type MarketOrderParams = MarketOrderCurrencyParams | MarketOrderTokenParams; 112 | 113 | const API_HOST_DEFAULT = "https://api.kovan.veil.co"; 114 | 115 | const TEN_18 = new BigNumber(10).pow(18); 116 | export function toWei(amount: number) { 117 | return new BigNumber(amount.toString()).times(TEN_18); 118 | } 119 | 120 | export function fromWei(amount: BigNumber | string) { 121 | return new BigNumber(amount.toString()).div(TEN_18); 122 | } 123 | 124 | export function toShares(amount: number, numTicks: string | number) { 125 | return new BigNumber(amount.toString()) 126 | .times(TEN_18) 127 | .div(new BigNumber(numTicks)); 128 | } 129 | 130 | export function fromShares( 131 | amount: BigNumber | string, 132 | numTicks: string | number 133 | ) { 134 | return new BigNumber(amount.toString()) 135 | .times(new BigNumber(numTicks)) 136 | .div(TEN_18); 137 | } 138 | 139 | export function encodeParams(params: Object) { 140 | return Object.entries(params) 141 | .map(kv => kv.map(encodeURIComponent).join("=")) 142 | .join("&"); 143 | } 144 | 145 | interface VeilOptions { 146 | mnemonic?: string; 147 | address?: string; 148 | apiHost?: string; 149 | provider?: Provider; 150 | } 151 | 152 | const defaultOptions: Partial = { 153 | apiHost: API_HOST_DEFAULT 154 | }; 155 | 156 | export default class Veil { 157 | provider: Provider; 158 | apiHost: string = API_HOST_DEFAULT; 159 | address: string; 160 | jwt: string; 161 | isSetup = false; 162 | 163 | constructor( 164 | mnemonicOrOptions?: string | VeilOptions, 165 | address?: string, 166 | apiHost?: string 167 | ) { 168 | if (mnemonicOrOptions) { 169 | if (typeof mnemonicOrOptions === "string") { 170 | this.provider = getProvider(mnemonicOrOptions); 171 | } else if (typeof mnemonicOrOptions === "object") { 172 | // We have an options object 173 | const options = { ...defaultOptions, ...mnemonicOrOptions }; 174 | if (options.mnemonic) this.provider = getProvider(options.mnemonic); 175 | if (options.provider) this.provider = options.provider; 176 | if (options.address) this.address = options.address; 177 | if (options.apiHost) this.apiHost = options.apiHost; 178 | } else { 179 | throw new Error("Invalid options object passed to Veil()"); 180 | } 181 | } 182 | if (address) { 183 | console.warn( 184 | "Passing an address as the second argument to Veil() is deprecated. Please use the options object instead." 185 | ); 186 | this.address = address.toLowerCase(); 187 | } 188 | if (apiHost) { 189 | console.warn( 190 | "Passing an apiHost as the third argument to Veil() is deprecated. Please use the options object instead." 191 | ); 192 | this.apiHost = apiHost; 193 | } 194 | } 195 | 196 | async fetch( 197 | url: string, 198 | params: any = {}, 199 | method: "POST" | "GET" | "DELETE" = "GET" 200 | ) { 201 | if (method === "GET") url = url + "?" + encodeParams(params); 202 | const response = await fetch(url, { 203 | method, 204 | body: method !== "GET" ? JSON.stringify(params) : undefined, 205 | headers: { 206 | "Content-Type": "application/json", 207 | Accept: "application/json", 208 | ...(this.jwt ? { Authorization: `Bearer ${this.jwt}` } : {}) 209 | } 210 | }); 211 | const json = await response.json(); 212 | if (json.errors) throw new VeilError(json.errors, url); 213 | return camelizeKeys(json.data); 214 | } 215 | 216 | async retry(func: () => Promise) { 217 | while (true) { 218 | try { 219 | const result = await func(); 220 | return result; 221 | } catch (e) { 222 | if (some(e.errors, (err: any) => err.message.match("jwt expired"))) { 223 | await this.authenticate(); 224 | } else { 225 | throw e; 226 | } 227 | } 228 | } 229 | } 230 | 231 | async setup() { 232 | if (!this.jwt) await this.authenticate(); 233 | this.isSetup = true; 234 | } 235 | 236 | async authenticate() { 237 | if (!this.provider || !this.address) 238 | throw new VeilError([ 239 | "You tried calling an authenticated method without passing an address and a mnemonic or provider to the Veil constructor" 240 | ]); 241 | const challenge = await this.createSessionChallenge(); 242 | const web3 = new Web3Wrapper(this.provider); 243 | const signature = await web3.signMessageAsync( 244 | this.address, 245 | utils.hexlify(utils.toUtf8Bytes(challenge.uid)) 246 | ); 247 | const session = await this.createSession({ 248 | signature, 249 | challengeUid: challenge.uid, 250 | message: challenge.uid 251 | }); 252 | this.jwt = session.token; 253 | return true; 254 | } 255 | 256 | async createSessionChallenge() { 257 | const url = `${this.apiHost}/api/v1/session_challenges`; 258 | const challenge: { uid: string } = await this.fetch(url, {}, "POST"); 259 | return challenge; 260 | } 261 | 262 | async createSession(params: { 263 | challengeUid: string; 264 | signature: string; 265 | message: string; 266 | }) { 267 | const url = `${this.apiHost}/api/v1/sessions`; 268 | const session: { token: string } = await this.fetch(url, params, "POST"); 269 | return session; 270 | } 271 | 272 | async getMarkets( 273 | params: { 274 | channel?: string; 275 | status?: "open" | "resolved"; 276 | page?: number; 277 | } = {} 278 | ) { 279 | const url = `${this.apiHost}/api/v1/markets`; 280 | const page: Page = await this.fetch(url, params); 281 | return page; 282 | } 283 | 284 | async createOrder(quote: Quote, options: { postOnly?: boolean } = {}) { 285 | if (!this.isSetup) await this.setup(); 286 | 287 | const signedOrder = await signOrder(this.provider, quote.zeroExOrder); 288 | const params = { 289 | order: { 290 | zeroExOrder: signedOrder, 291 | quoteUid: quote.uid, 292 | ...options 293 | } 294 | }; 295 | 296 | const url = `${this.apiHost}/api/v1/orders`; 297 | const order: Order = await this.retry(() => 298 | this.fetch(url, params, "POST") 299 | ); 300 | return order; 301 | } 302 | 303 | async createQuote( 304 | market: Market, 305 | side: "buy" | "sell", 306 | tokenType: "long" | "short", 307 | params: LimitOrderParams | MarketOrderParams 308 | ) { 309 | if (params.type !== "limit" && params.type !== "market") 310 | throw new Error("Type (market or limit) is required to create a quote"); 311 | if (!this.isSetup) await this.setup(); 312 | const token = tokenType === "long" ? market.longToken : market.shortToken; 313 | 314 | interface QuoteInput { 315 | side: "buy" | "sell"; 316 | token: string; 317 | type: "limit" | "market"; 318 | price?: string; 319 | tokenAmount?: string; 320 | currencyAmount?: string; 321 | } 322 | let quoteParams: Partial = { 323 | side, 324 | token 325 | }; 326 | 327 | if (params.type === "limit") { 328 | const zero = new BigNumber(0); 329 | const numTicks = new BigNumber(market.numTicks); 330 | let tokenAmount = params.tokenAmount; 331 | let price = params.price; 332 | if (typeof tokenAmount === "number") 333 | tokenAmount = toShares(tokenAmount, market.numTicks); 334 | tokenAmount = tokenAmount.decimalPlaces(0); 335 | 336 | if (typeof price === "number") 337 | price = new BigNumber(price.toString()).times(numTicks); 338 | if (price.lt(zero)) price = zero; 339 | if (price.gt(numTicks)) price = numTicks; 340 | price = price.decimalPlaces(0); 341 | 342 | quoteParams = { 343 | ...quoteParams, 344 | price: price.toString(), 345 | tokenAmount: tokenAmount.toString(), 346 | type: "limit" 347 | }; 348 | } else { 349 | if ("currencyAmount" in params) { 350 | let currencyAmount = params.currencyAmount; 351 | if (typeof currencyAmount === "number") 352 | currencyAmount = toWei(currencyAmount); 353 | currencyAmount = currencyAmount.decimalPlaces(0); 354 | quoteParams = { 355 | ...quoteParams, 356 | currencyAmount: currencyAmount.toString(), 357 | type: "market" 358 | }; 359 | } else { 360 | if (!params.tokenAmount) 361 | throw new Error( 362 | "Either tokenAmount or currencyAmount is required to create a market order" 363 | ); 364 | let tokenAmount = params.tokenAmount; 365 | if (typeof tokenAmount === "number") 366 | tokenAmount = toShares(tokenAmount, market.numTicks); 367 | tokenAmount = tokenAmount.decimalPlaces(0); 368 | quoteParams = { 369 | ...quoteParams, 370 | tokenAmount: tokenAmount.toString(), 371 | type: "market" 372 | }; 373 | } 374 | } 375 | 376 | const url = `${this.apiHost}/api/v1/quotes`; 377 | const quote: Quote = await this.retry(() => 378 | this.fetch(url, { quote: quoteParams }, "POST") 379 | ); 380 | return quote; 381 | } 382 | 383 | async cancelOrder(uid: string) { 384 | if (!this.isSetup) await this.setup(); 385 | 386 | const url = `${this.apiHost}/api/v1/orders/${uid}`; 387 | const order: Order = await this.retry(() => this.fetch(url, {}, "DELETE")); 388 | return order; 389 | } 390 | 391 | async getUserOrders( 392 | market: Market, 393 | options?: { 394 | page?: number; 395 | status?: "open" | "filled" | "canceled" | "expired"; 396 | } 397 | ) { 398 | if (!this.isSetup) await this.setup(); 399 | 400 | const url = `${this.apiHost}/api/v1/orders`; 401 | const page: Page = await this.retry(() => 402 | this.fetch(url, { 403 | ...options, 404 | market: market.slug 405 | }) 406 | ); 407 | return page; 408 | } 409 | 410 | async getBids( 411 | market: Market, 412 | tokenType: "long" | "short", 413 | options?: { page?: number } 414 | ) { 415 | if (tokenType !== "long" && tokenType !== "short") 416 | throw new Error( 417 | `Invalid tokenType: "${tokenType}". Must be either "long" or "short".` 418 | ); 419 | const url = `${this.apiHost}/api/v1/markets/${ 420 | market.slug 421 | }/${tokenType}/bids`; 422 | const page: Page = await this.fetch(url, options); 423 | return page; 424 | } 425 | 426 | async getAsks( 427 | market: Market, 428 | tokenType: "long" | "short", 429 | options?: { page?: number } 430 | ) { 431 | if (tokenType !== "long" && tokenType !== "short") 432 | throw new Error( 433 | `Invalid tokenType: "${tokenType}". Must be either "long" or "short".` 434 | ); 435 | const url = `${this.apiHost}/api/v1/markets/${ 436 | market.slug 437 | }/${tokenType}/asks`; 438 | const page: Page = await this.fetch(url, options); 439 | return page; 440 | } 441 | 442 | async getOrderFills( 443 | market: Market, 444 | tokenType: "long" | "short", 445 | options?: { page?: number } 446 | ) { 447 | if (tokenType !== "long" && tokenType !== "short") 448 | throw new Error( 449 | `Invalid tokenType: "${tokenType}". Must be either "long" or "short".` 450 | ); 451 | const url = `${this.apiHost}/api/v1/markets/${ 452 | market.slug 453 | }/${tokenType}/order_fills`; 454 | const page: Page = await this.fetch(url, options); 455 | return page; 456 | } 457 | 458 | async getDataFeed(dataFeedSlug: string, scope: "day" | "month" = "month") { 459 | const url = `${this.apiHost}/api/v1/data_feeds/${dataFeedSlug}`; 460 | const params = { scope }; 461 | const dataFeed: DataFeed = await this.fetch(url, params); 462 | return dataFeed; 463 | } 464 | 465 | getScalarRange(market: Market): [number, number] { 466 | if (!market.minPrice || !market.maxPrice) 467 | throw new Error("Market does not have min and max price"); 468 | return [ 469 | fromWei(market.minPrice).toNumber(), 470 | fromWei(market.maxPrice).toNumber() 471 | ]; 472 | } 473 | 474 | async getMarket(slug: string) { 475 | const url = `${this.apiHost}/api/v1/markets/${slug}`; 476 | const market: Market = await this.fetch(url); 477 | if (!market) throw new Error(`Market not found: ${slug}`); 478 | return market; 479 | } 480 | 481 | async getMarketBalances(market: Market) { 482 | if (!this.isSetup) await this.setup(); 483 | const url = `${this.apiHost}/api/v1/markets/${market.slug}/balances`; 484 | const balances: MarketBalances = await this.retry(() => this.fetch(url)); 485 | return balances; 486 | } 487 | } 488 | -------------------------------------------------------------------------------- /src/errors.ts: -------------------------------------------------------------------------------- 1 | export class VeilError extends Error { 2 | errors: any[]; 3 | url?: string; 4 | 5 | constructor(errors: any[], url?: string) { 6 | super("Veil Error"); 7 | this.errors = errors; 8 | if (url) this.url = url; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Veil"; 2 | export * from "./Veil"; 3 | export * from "./errors"; 4 | -------------------------------------------------------------------------------- /src/provider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Web3ProviderEngine, 3 | MnemonicWalletSubprovider 4 | } from "@0x/subproviders"; 5 | 6 | export default function getProvider(mnemonic: string) { 7 | const provider = new Web3ProviderEngine(); 8 | provider.addProvider(new MnemonicWalletSubprovider({ mnemonic })); 9 | 10 | // web3-provider-engine prevents requests from going out before you do this 11 | (provider as any)._ready.go(); 12 | 13 | return provider; 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "experimentalDecorators": true, 5 | "lib": ["es2017"], 6 | "target": "es5", 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "noImplicitAny": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "removeComments": false, 13 | "preserveConstEnums": true, 14 | "sourceMap": true, 15 | "skipLibCheck": true, 16 | "declaration": true, 17 | "outDir": "dist" 18 | }, 19 | "include": ["src/**/*.ts", "typings/**/*.ts"] 20 | } 21 | --------------------------------------------------------------------------------