// New create sknet engine and register handlers. func New(ee engine.Exchange, quit chan bool) *sknet.Engine { engine := sknet.New(ee.GetSecKey(), quit) engine.Use(sknet.Logger()) engine.Register("/create/account", api.CreateAccount(ee)) engine.Register("/create/deposit_address", api.GetNewAddress(ee)) engine.Register("/get/account/balance", api.GetAccountBalance(ee)) engine.Register("/get/address/balance", api.GetAddrBalance(ee)) engine.Register("/withdrawl", api.Withdraw(ee)) engine.Register("/create/order", api.CreateOrder(ee)) engine.Register("/get/coins", api.GetCoins(ee)) engine.Register("/get/orders", api.GetOrders(ee)) // utxos handler engine.Register("/get/utxos", api.GetUtxos(ee)) // output history handler engine.Register("/get/output", api.GetOutput(ee)) // transaction handler engine.Register("/inject/tx", api.InjectTx(ee)) engine.Register("/get/tx", api.GetTx(ee)) engine.Register("/get/rawtx", api.GetRawTx(ee)) engine.Register("/admin/update/credit", api.UpdateCredit(ee)) return engine }
// GetUtxos get unspent output of specific address. func GetUtxos(egn engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { var req pp.GetUtxoReq var rlt *pp.EmptyRes for { if err := c.BindJSON(&req); err != nil { rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) logger.Error(err.Error()) break } coin, err := egn.GetCoin(req.GetCoinType()) if err != nil { rlt = pp.MakeErrRes(err) logger.Error(err.Error()) break } res, err := coin.GetUtxos(req.GetAddresses()) if err != nil { rlt = pp.MakeErrRes(err) logger.Error(err.Error()) break } return c.SendJSON(&res) } return c.Error(rlt) } }
// GetAddrBalance get balance of specific address. func GetAddrBalance(ee engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { var rlt *pp.EmptyRes for { req := pp.GetAddrBalanceReq{} if err := c.BindJSON(&req); err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) break } coin, err := ee.GetCoin(req.GetCoinType()) if err != nil { rlt = pp.MakeErrRes(err) logger.Error(err.Error()) break } addrs := strings.Split(req.GetAddrs(), ",") b, err := coin.GetBalance(addrs) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } res := pp.GetAddrBalanceRes{ Result: pp.MakeResultWithCode(pp.ErrCode_Success), Balance: &b, } return c.SendJSON(&res) } return c.Error(rlt) } }
// GetBalance return balance of specific account. func GetAccountBalance(ee engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { rlt := &pp.EmptyRes{} for { req := pp.GetAccountBalanceReq{} if err := c.BindJSON(&req); err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) break } // validate pubkey pubkey := req.GetPubkey() if err := validatePubkey(pubkey); err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongPubkey) break } a, err := ee.GetAccount(pubkey) if err != nil { rlt = pp.MakeErrResWithCode(pp.ErrCode_NotExits) break } bal := a.GetBalance(req.GetCoinType()) bres := pp.GetAccountBalanceRes{ Result: pp.MakeResultWithCode(pp.ErrCode_Success), Balance: &pp.Balance{Amount: pp.PtrUint64(bal)}, } return c.SendJSON(&bres) } return c.Error(rlt) } }
// GetRawTx return rawtx of specifc tx. func GetRawTx(egn engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { var rlt *pp.EmptyRes for { req := pp.GetRawTxReq{} if err := c.BindJSON(&req); err != nil { rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) break } coin, err := egn.GetCoin(req.GetCoinType()) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } rawtx, err := coin.GetRawTx(req.GetTxid()) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) break } res := pp.GetRawTxRes{ Result: pp.MakeResultWithCode(pp.ErrCode_Success), CoinType: req.CoinType, Rawtx: pp.PtrString(rawtx), } return c.SendJSON(&res) } return c.Error(rlt) } }
// GetCoins get supported coins. func GetCoins(egn engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { coins := pp.CoinsRes{ Result: pp.MakeResultWithCode(pp.ErrCode_Success), Coins: egn.GetSupportCoins(), } return c.SendJSON(&coins) } }
// createBtcWithdrawTx create withdraw transaction. // amount is the number of coins that want to withdraw. // toAddr is the address that the coins will be sent to. func createBtcWithdrawTx(egn engine.Exchange, amount uint64, toAddr string) (*BtcTxResult, error) { uxs, err := egn.ChooseUtxos(bitcoin.Type, amount+egn.GetBtcFee(), ChooseUtxoTm) if err != nil { return nil, err } utxos := uxs.([]bitcoin.Utxo) for _, u := range utxos { logger.Debug("using utxos: txid:%s vout:%d addr:%s", u.GetTxid(), u.GetVout(), u.GetAddress()) } var success bool defer func() { if !success { // put utxos back to pool if withdraw failed. go func() { egn.PutUtxos(bitcoin.Type, utxos) }() } }() var totalAmounts uint64 for _, u := range utxos { totalAmounts += u.GetAmount() } fee := egn.GetBtcFee() outAddrs := []bitcoin.TxOut{} chgAmt := totalAmounts - fee - amount chgAddr := "" if chgAmt > 0 { // generate a change address chgAddr = egn.GetNewAddress(bitcoin.Type) outAddrs = append(outAddrs, bitcoin.TxOut{Addr: toAddr, Value: amount}, bitcoin.TxOut{Addr: chgAddr, Value: chgAmt}) } else { outAddrs = append(outAddrs, bitcoin.TxOut{Addr: toAddr, Value: amount}) } // change utxo to UtxoWithkey utxoKeys, err := makeBtcUtxoWithkeys(utxos, egn) if err != nil { return nil, err } logger.Debug("creating transaction...") tx, err := bitcoin.NewTransaction(utxoKeys, outAddrs) if err != nil { logger.Error(err.Error()) return nil, err } success = true rlt := BtcTxResult{ Tx: tx, UsingUtxos: utxos[:], ChangeAddr: chgAddr, } return &rlt, nil }
func makeBtcUtxoWithkeys(utxos []bitcoin.Utxo, egn engine.Exchange) ([]bitcoin.UtxoWithkey, error) { utxoks := make([]bitcoin.UtxoWithkey, len(utxos)) for i, u := range utxos { key, err := egn.GetAddrPrivKey(bitcoin.Type, u.GetAddress()) if err != nil { return []bitcoin.UtxoWithkey{}, err } utxoks[i] = bitcoin.NewUtxoWithKey(u, key) } return utxoks, nil }
// GetOrders get order list. func GetOrders(egn engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { rlt := &pp.EmptyRes{} for { req := pp.GetOrderReq{} if err := c.BindJSON(&req); err != nil { rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) break } op, err := order.TypeFromStr(req.GetType()) if err != nil { rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) logger.Error(err.Error()) break } ords, err := egn.GetOrders(req.GetCoinPair(), op, req.GetStart(), req.GetEnd()) if err != nil { rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) logger.Error(err.Error()) break } res := pp.GetOrderRes{ CoinPair: req.CoinPair, Type: req.Type, Orders: make([]*pp.Order, len(ords)), } for i := range ords { res.Orders[i] = &pp.Order{ Id: &ords[i].ID, Type: req.Type, Price: &ords[i].Price, Amount: &ords[i].Amount, RestAmt: &ords[i].RestAmt, CreatedAt: &ords[i].CreatedAt, } } res.Result = pp.MakeResultWithCode(pp.ErrCode_Success) return c.SendJSON(&res) } return c.Error(rlt) } }
// UpdateCredit update credit. func UpdateCredit(ee engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { var rlt *pp.EmptyRes for { req := pp.UpdateCreditReq{} if err := c.BindJSON(&req); err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) break } // validate the dst pubkey. dstPubkey := req.GetDst() if err := validatePubkey(dstPubkey); err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongPubkey) break } // get account. a, err := ee.GetAccount(dstPubkey) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongPubkey) break } // get coin type. if err := a.SetBalance(req.GetCoinType(), req.GetAmount()); err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } ee.SaveAccount() res := pp.UpdateCreditRes{ Result: pp.MakeResultWithCode(pp.ErrCode_Success), } return c.SendJSON(&res) } return c.Error(rlt) } }
// GetTx get transaction by id. func GetTx(egn engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { var rlt *pp.EmptyRes for { req := pp.GetTxReq{} if err := c.BindJSON(&req); err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) break } coin, err := egn.GetCoin(req.GetCoinType()) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } // brief validate transaction id if !coin.ValidateTxid(req.GetTxid()) { rlt = pp.MakeErrRes(errors.New("invalid transaction id")) break } tx, err := coin.GetTx(req.GetTxid()) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } res := pp.GetTxRes{ Result: pp.MakeResultWithCode(pp.ErrCode_Success), CoinType: req.CoinType, Tx: tx, } return c.SendJSON(&res) } return c.Error(rlt) } }
// IsAdmin middleware for checking if the account is admin. func IsAdmin(ee engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { var rlt *pp.EmptyRes for { req := pp.UpdateCreditReq{} if err := c.BindJSON(&req); err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) break } if !ee.IsAdmin(req.GetPubkey()) { logger.Error("not admin") rlt = pp.MakeErrResWithCode(pp.ErrCode_UnAuthorized) break } return c.Next() } return c.Error(rlt) } }
// InjectTx inject transaction. func InjectTx(egn engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { var rlt *pp.EmptyRes for { req := pp.InjectTxnReq{} if err := c.BindJSON(&req); err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) break } // get coin gateway coin, err := egn.GetCoin(req.GetCoinType()) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } // inject tx. txid, err := coin.InjectTx(req.GetTx()) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } res := pp.InjectTxnRes{ Result: pp.MakeResultWithCode(pp.ErrCode_Success), Txid: pp.PtrString(txid), } return c.SendJSON(&res) } return c.Error(rlt) } }
func getWithdrawReqParams(c *sknet.Context, ee engine.Exchange) (*ReqParams, error) { rp := NewReqParams() req := pp.WithdrawalReq{} if err := c.BindJSON(&req); err != nil { return nil, err } // validate pubkey pubkey := req.GetPubkey() if err := validatePubkey(pubkey); err != nil { return nil, err } a, err := ee.GetAccount(pubkey) if err != nil { return nil, err } rp.Values["account"] = a rp.Values["cointype"] = req.GetCoinType() rp.Values["amt"] = req.GetCoins() rp.Values["outAddr"] = req.GetOutputAddress() return rp, nil }
// GetNewAddress account create new address for depositing. func GetNewAddress(ee engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { rlt := &pp.EmptyRes{} for { req := pp.GetDepositAddrReq{} if err := c.BindJSON(&req); err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) break } // validate pubkey pubkey := req.GetPubkey() if err := validatePubkey(pubkey); err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongPubkey) break } at, err := ee.GetAccount(pubkey) if err != nil { rlt = pp.MakeErrResWithCode(pp.ErrCode_NotExits) break } ct := req.GetCoinType() // get the new address for depositing addr := ee.GetNewAddress(ct) // add the new address to engin for watching it's utxos. at.AddDepositAddress(ct, addr) ee.WatchAddress(ct, addr) ds := pp.GetDepositAddrRes{ Result: pp.MakeResultWithCode(pp.ErrCode_Success), CoinType: req.CoinType, Address: &addr, } return c.SendJSON(&ds) } return c.Error(rlt) } }
// Withdraw api for handlering withdraw process. func Withdraw(ee engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { rlt := &pp.EmptyRes{} for { reqParam, err := getWithdrawReqParams(c, ee) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } cp := reqParam.Values["cointype"].(string) a := reqParam.Values["account"].(account.Accounter) amt := reqParam.Values["amt"].(uint64) outAddr := reqParam.Values["outAddr"].(string) // get handler for creating txIns and txOuts base on the coin type. createTxInOut, err := getTxInOutHandler(cp) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } // create txIns and txOuts. inOutSet, err := createTxInOut(ee, a, amt, outAddr) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } var success bool defer func() { if !success { // if not success, invoke the teardown, for putting back utxos, and reset balance. inOutSet.Teardown() } }() // get coin gateway. coin, err := ee.GetCoin(cp) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } // create raw tx rawtx, err := coin.CreateRawTx(inOutSet.TxIns, inOutSet.TxOuts) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } // sign the tx rawtx, err = coin.SignRawTx(rawtx, getAddrPrivKey(ee, cp)) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } // inject the transaction. txid, err := coin.InjectTx(rawtx) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrRes(err) break } success = true resp := pp.WithdrawalRes{ Result: pp.MakeResultWithCode(pp.ErrCode_Success), NewTxid: &txid, } return c.SendJSON(&resp) } return c.Error(rlt) } }
// CreateOrder create specifc order. func CreateOrder(egn engine.Exchange) sknet.HandlerFunc { return func(c *sknet.Context) error { rlt := &pp.EmptyRes{} req := &pp.OrderReq{} for { if err := c.BindJSON(req); err != nil { rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) logger.Error(err.Error()) break } // validate pubkey pubkey := req.GetPubkey() if err := validatePubkey(pubkey); err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongPubkey) break } // get order type op, err := order.TypeFromStr(req.GetType()) if err != nil { rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) logger.Error(err.Error()) break } // find the account acnt, err := egn.GetAccount(pubkey) if err != nil { rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongPubkey) logger.Error(err.Error()) break } cp, bal, err := needBalance(op, req) if err != nil { rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) logger.Error(err.Error()) break } if acnt.GetBalance(cp) < bal { err := fmt.Errorf("%s balance is not sufficient", cp) rlt = pp.MakeErrRes(err) logger.Debug(err.Error()) break } var success bool if op == order.Bid { defer func() { if success { egn.SaveAccount() } else { acnt.IncreaseBalance(cp, bal) } }() // decrease the balance, in case of double use the coins. logger.Info("account:%s decrease %s:%d", acnt.GetID(), cp, bal) if err := acnt.DecreaseBalance(cp, bal); err != nil { rlt = pp.MakeErrRes(err) logger.Error(err.Error()) break } } odr := order.New(pubkey, op, req.GetPrice(), req.GetAmount()) oid, err := egn.AddOrder(req.GetCoinPair(), *odr) if err != nil { logger.Error(err.Error()) rlt = pp.MakeErrResWithCode(pp.ErrCode_WrongRequest) break } success = true logger.Info(fmt.Sprintf("new %s order:%d", op, oid)) res := pp.OrderRes{ Result: pp.MakeResultWithCode(pp.ErrCode_Success), OrderId: &oid, } return c.SendJSON(&res) } return c.Error(rlt) } }
func createSkyWithdrawTx(egn engine.Exchange, amount uint64, toAddr string) (*SkyTxResult, error) { uxs, err := egn.ChooseUtxos(skycoin.Type, amount, ChooseUtxoTm) if err != nil { return nil, err } utxos := uxs.([]skycoin.Utxo) for _, u := range utxos { logger.Debug("using skycoin utxos:%s", u.GetHash()) } var success bool defer func() { if !success { go func() { egn.PutUtxos(skycoin.Type, utxos) }() } }() var totalAmounts uint64 var totalHours uint64 for _, u := range utxos { totalAmounts += u.GetCoins() totalHours += u.GetHours() } outAddrs := []skycoin.TxOut{} chgAmt := totalAmounts - amount chgHours := totalHours / 4 chgAddr := "" if chgAmt > 0 { // generate a change address chgAddr = egn.GetNewAddress(skycoin.Type) outAddrs = append(outAddrs, skycoin.MakeUtxoOutput(toAddr, amount, chgHours/2), skycoin.MakeUtxoOutput(chgAddr, chgAmt, chgHours/2)) } else { outAddrs = append(outAddrs, skycoin.MakeUtxoOutput(toAddr, amount, chgHours/2)) } keys := make([]cipher.SecKey, len(utxos)) for i, u := range utxos { k, err := egn.GetAddrPrivKey(skycoin.Type, u.GetAddress()) if err != nil { panic(err) } keys[i] = cipher.MustSecKeyFromHex(k) } logger.Debug("creating skycoin transaction...") tx := skycoin.NewTransaction(utxos, keys, outAddrs) if err := tx.Verify(); err != nil { return nil, err } success = true rlt := SkyTxResult{ Tx: tx, UsingUtxos: utxos[:], ChangeAddr: chgAddr, } return &rlt, nil }
func createBtcTxInOut(ee engine.Exchange, a account.Accounter, amount uint64, outAddr string) (*txInOutResult, error) { var rlt txInOutResult // verify the outAddr if _, err := cipher.BitcoinDecodeBase58Address(outAddr); err != nil { return nil, errors.New("invalid bitcoin address") } var err error // decrease balance and check if the balance is sufficient. if err := a.DecreaseBalance("bitcoin", amount+ee.GetBtcFee()); err != nil { return nil, err } var utxos []bitcoin.Utxo // choose sufficient utxos. uxs, err := ee.ChooseUtxos("bitcoin", amount+ee.GetBtcFee(), ChooseUtxoTm) if err != nil { return nil, err } utxos = uxs.([]bitcoin.Utxo) for _, u := range utxos { logger.Debug("using utxos: txid:%s vout:%d addr:%s", u.GetTxid(), u.GetVout(), u.GetAddress()) rlt.TxIns = append(rlt.TxIns, coin.TxIn{ Txid: u.GetTxid(), Vout: u.GetVout(), }) } var totalAmounts uint64 for _, u := range utxos { totalAmounts += u.GetAmount() } fee := ee.GetBtcFee() txOuts := []bitcoin.TxOut{} chgAmt := totalAmounts - fee - amount chgAddr := "" if chgAmt > 0 { // generate a change address chgAddr = ee.GetNewAddress(bitcoin.Type) txOuts = append(txOuts, bitcoin.TxOut{Addr: outAddr, Value: amount}, bitcoin.TxOut{Addr: chgAddr, Value: chgAmt}) } else { txOuts = append(txOuts, bitcoin.TxOut{Addr: outAddr, Value: amount}) } rlt.TxOuts = txOuts rlt.Teardown = func() { a.IncreaseBalance(bitcoin.Type, amount+ee.GetBtcFee()) ee.PutUtxos(bitcoin.Type, utxos) } return &rlt, nil }
func getAddrPrivKey(ee engine.Exchange, cp string) coin.GetPrivKey { return func(addr string) (string, error) { return ee.GetAddrPrivKey(cp, addr) } }