sniper-js
Version:
Core JS Module for Sniper.io Platform
400 lines (333 loc) • 10.9 kB
JavaScript
'use strict'
// import { abi as dexAbi } from '../../build/contracts/SniperExchange'
import { BigNumber } from 'bignumber.js'
import Assert from './component/Assert'
import Util from './component/Util'
import Web3lib from './component/Web3lib'
import ContractManager from './component/ContractManager'
import Firebase from './component/Firebase'
import {
toBuffer,
bufferToHex,
hashPersonalMessage
} from 'ethereumjs-util'
const cfg = CFG
const NULL_ADDR = cfg.addr.null
export default class Sniper {
constructor(opts) {
let err = this._checkOptions(opts)
if (err) return
this.Web3lib = Web3lib
this.w3 = null
this.provider = opts.provider
this.webworker = opts.webworker ? opts.webworker : false
this.db = new Firebase()
}
/**
* Instantiate Web3 & Contracts
* @return {Promise}
*/
async init() {
return new Promise((resolve, reject) => {
this.w3 = new Web3lib(this.provider)
this.contract = new ContractManager(this.w3)
resolve()
})
}
_checkOptions(opts) {
let error = false
if (!opts.provider) {
console.error('Missing provider option')
error = true
}
if (!opts.exchangeAddr) {
console.warn('Missing exchangeAddr option')
}
return error
} // _checkOptions
getWeb3() { return this.w3 }
changeProvider(provider) {
if (this.w3 && provider) {
this.provider = provider
this.w3._setProvider(this.provider)
} else {
console.error('Could not change provider')
}
}
getGasPriceAsync() { return this.w3._getGasPrice() }
newAccount(numAddr) { this.w3._newAccount(numAddr) }
getAccountsAsync() { return this.w3._getAccounts() }
/**
* Create Order
* @param {bool} addPrefix - Adds ethereum signed message prefix
* @param {string} makerAddr - Maker Address
* @param {string} takerAddr - Taker Address
* @param {string} makerTokenAddr - Maker Token Address
* @param {string} takerTokenAddr - Maker Token Address
* @param {BigNumber} makerTokenAmt - Maker Token Amount
* @param {BigNumber} takerTokenAmt - Taker Token Amount
* @param {Number} expirationUnixLen - Expiration length in seconds
* @param {string} base - ETH || EOS
* @return {Point} A Point object.
*/
async createOrderAsync(
addPrefix,
makerAddr,
takerAddr,
makerTokenAddr,
takerTokenAddr,
makerTokenAmt,
takerTokenAmt,
expirationUnixLen,
base = 'ETH'
) {
return new Promise( async (resolve, reject) => {
const usePrefix = addPrefix ? true : false
const order = {
base: base,
salt: Util.genSalt(),
maker: makerAddr,
taker: takerAddr = takerAddr ? takerAddr : NULL_ADDR,
makerTokenAddress: makerTokenAddr,
takerTokenAddress: takerTokenAddr,
makerTokenAmount: makerTokenAmt,
takerTokenAmount: takerTokenAmt,
exchangeAddress: cfg.addr.dex,
expirationTimestampInSec: new BigNumber(Date.now() + expirationUnixLen)
}
if (Assert.orderJson(order)) {
reject(new Error('Error creating order. Please check the console log and report!'))
}
const orderHashHex = Util.getOrderHash(order)
let sig = null
try {
sig = await this.signOrderHashAsync(orderHashHex, order.maker, usePrefix)
} catch (e) {
reject(new Error(e))
return
}
// verification, attempt VRS & RSV
let ecSig = null
const validV = [27, 28]
const ecSigVRS = Util.parseSigHexToVRS(sig)
if (validV.includes(ecSigVRS.v)) {
if (this.verifySignature(orderHashHex, order.maker, ecSigVRS)) {
ecSig = ecSigVRS
}
}
const ecSigRSV = Util.parseSigHexToRSV(sig)
if (validV.includes(ecSigRSV.v)) {
if (this.verifySignature(orderHashHex, order.maker, ecSigRSV)) {
ecSig = ecSigRSV
}
}
if (!ecSig) reject(new Error('Could not sign order: invalid signature.'))
resolve(
Object.assign(order, {
hashHex: orderHashHex,
sig: ecSig
})
)
}) // Promise
}
/**
* Signs Order Hash
* @param {string} orderHash - Order hash/message to sign
* @param {string} signerAddr - Signer's address
* @param {boolean} addPrefix - adds Ethereum signed message prefix
* @return {object} VRS
*/
async signOrderHashAsync(orderHash, signerAddr, addPrefix) {
return new Promise(async (resolve, reject) => {
if (!Util.isHex(orderHash)) {
reject('signOrderHashAsync: Invalid orderHash')
}
if (!(await Util.isSenderAddressAsync(signerAddr, this.w3))) {
reject('signOrderHashAsync: Invalid signerAddr')
}
let msgHashHex = orderHash
if (addPrefix) {
const orderHashBuff = toBuffer(orderHash);
const msgHashBuff = hashPersonalMessage(orderHashBuff);
msgHashHex = bufferToHex(msgHashBuff);
}
const sig = await this.w3._sign(cfg.msg_prefix + msgHashHex, signerAddr)
resolve(sig)
})
}
sanitizeOrderIn(o) {
return Assert.sanitizeOrderIn(o)
}
sanitizeOrderOut(o) {
return Assert.sanitizeOrderOut(o)
}
/**
* Externally-available order hashing
* @param {object} order - complete order data
* @return {string}
*/
getOrderHash(order) {
return Util.getOrderHash(order)
}
/**
* Verify Order Hash
* @param {string} data - message to sign (eg. Order Hash)
* @param {string} signerAddr - Signer's address
* @param {object} ecSigVRS - adds Ethereum signed message prefix
* @return {bool} VRS
*/
verifySignature(data, signerAddr, ecSig) {
const validVParamValues = [27, 28]
if (
!validVParamValues.includes(ecSig.v) ||
!Util.isHex(data) ||
typeof ecSig != 'object' ||
typeof ecSig.v != 'number' ||
typeof ecSig.r != 'string' ||
typeof ecSig.s != 'string' ||
!Assert.isAddress(signerAddr)
) {
return false
}
return Util.verifySignature(cfg.msg_prefix + data, signerAddr, ecSig)
}
pollTransactionAsync(txHash, frequencyS = 1, timeoutS = 1200) {
const frequencyMS = frequencyS * 1000
const timeoutMS = timeoutS * 1000
let timeTracker = 0
return new Promise((resolve, reject) => {
let pollTx = setInterval(async () => {
if (timeTracker > timeoutMS) {
console.warn('Sniper.pollTransactionAsync: timeout for', txHash)
clearInterval(pollTx)
resolve()
}
timeTracker += frequencyMS
const receipt = await this.w3.getTransactionReceipt(txHash)
if (receipt) {
clearInterval(pollTx)
resolve(receipt)
}
}, frequencyMS)
})
}
sendTransactionAsync(fromAddr, toAddr, amount, gasPrice, nonce) {
return this.w3.sendTransaction(fromAddr, toAddr, amount, gasPrice, nonce)
}
getEthBalanceAsync(addr) {
return this.w3.getEthBalance(addr)
}
// Token-specific Functionality
getBalanceAsync(tokenAddr, ownerAddr) {
return this.contract.token.getBalanceAsync(tokenAddr, ownerAddr)
}
setUnlimitedApprovalAsync(tokenAddr, ownerAddr, spenderAddr) {
return this.contract.token.setUnlimitedAllowanceAsync(tokenAddr, ownerAddr, spenderAddr)
}
getAllowanceAsync(tokenAddr, ownerAddr, spenderAddr) {
return this.contract.token.getAllowanceAsync(tokenAddr, ownerAddr, spenderAddr)
}
transferAsync(tokenAddr, ownerAddr, toAddr, amount) {
return this.contract.token.transfer(
tokenAddr,
ownerAddr,
toAddr,
amount
)
}
transferFromAsync(tokenAddr, fromAddr, toAddr, spenderAddr, amount) {
return this.contract.token.transferFrom(
tokenAddr,
fromAddr,
toAddr,
spenderAddr,
amount
)
}
depositAsync(tokenAddr, ownerAddr, amount) {
return this.contract.token.deposit(
tokenAddr,
ownerAddr,
amount
)
}
withdrawAsync(tokenAddr, ownerAddr, amount) {
return this.contract.token.withdraw(
tokenAddr,
ownerAddr,
amount
)
}
// TODO
fillOrder(order) { return }
submitOrderAsync(order) { return this.db._submitOrder(order) }
submitOrderUnsafe(order) { return this.db._submitOrderInsecure(order) }
listenOrderBook(pair, cb) { this.db._listenOrderBook(pair, cb) }
getOrderBookAsync(pair) { return this.db._getOrderBook(pair) }
}
// dev testing
( async () => {
// let snpr = new Sniper({
// provider: web3
// })
// await snpr.init()
//
// const accounts = await snpr.getAccountsAsync()
// const ethToDeposit = BigNumber(1000000000000000000) // web3.toWei('1', 'ether')
// const txDeposit = await snpr.sendTransactionAsync(
// accounts[0],
// cfg.addr.weth,
// ethToDeposit
// )
// const balance = await snpr.getBalanceAsync(cfg.addr.weth, accounts[0])
// console.log('balance:', balance)
// testing2 addr: 0x7ad2ca2081e4b40147e8b8a6cd3c0907ee09e469
// const tx = await snpr.setUnlimitedApprovalAsync(cfg.addr.weth, accounts[0])
// console.log(tx)
// const receipt = await snpr.pollTransactionAsync(tx.transactionHash)
//
// const balance = await snpr.getBalanceAsync(cfg.addr.weth, accounts[0])
// console.log('balance:', balance)
//
// const allowance = await snpr.getAllowanceAsync(cfg.addr.weth, '0xfaBe65f11fE3EB25636333ca740A8C605494B9b1', '0xBadb56702F42e8FA4D826234FF8744215EB511F3')
// console.log(allowance)
})()
//
// let count = 0;
// let onOrder = (orders) => {
// console.log(count, orders)
// count++
// }
//
// snpr.listenOrderBook('ETH:0x6089982faab51b5758974cf6a502d15ca300a4eb', onOrder)
//
//
// snpr.init().then(() => {
//
// // console.log('standalone verification test:')
// // console.log(snpr.verifySignature(
// // '0xd09e3e833a8581ecc01df146b197f63d243ceb023572044922f547836950599f',
// // '0xfaBe65f11fE3EB25636333ca740A8C605494B9b1',
// // {v: 28, r: '0xf7e34636dcb9a4b844e37bb28a998f96cb56a592fd508c738aee96a3222cba93', s: '0x6b8cf351dcfe6a9af1e9c6921b88af028421f2ebb09d87e9b86d6ad37785b7ae'}
// // ))
//
// snpr.createOrderAsync(
// false,
// '0xfaBe65f11fE3EB25636333ca740A8C605494B9b1', // mAddr
// null, // tAddr
// '0xfaBe65f11fE3EB25636333ca740A8C605494B9b1', // mTokenAddr
// '0x6089982faab51b5758974cf6a502d15ca300a4eb', // tTokenAddr
// new BigNumber(1), // makerTokenAmt
// new BigNumber(1), // takerTokenAmt
// 50000, // expiry length
// 'ETH'
// ).then((order) => {
// // console.log(order)
// let hash = snpr.getOrderHash(order)
// console.log('verification test:', snpr.verifySignature(hash, order.maker, order.sig))
// return order
// }).then((order) => {
// snpr.submitOrderAsync(order)
// })
//
// })