Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example implementation #7

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
# multicoin-wallet
# multicoin-wallet

- Proxy: forward requests to interal btcd / btcwallet api
- Multicoin: serve multicoin api


### Wallet:
The wallet struture is copied over from skycoin with some modifications.

The available wallets are:
1) Skycoin Deterministic Wallet (Sequential Deterministic Wallet)
2) Bip44 Wallet
3) Collections Wallet
4) XPub Wallet ( Watch only wallets )


> Note: The public keys for skycoin and bitcoin are compressed public keys while eth pubkeys are uncompressed.



6 changes: 1 addition & 5 deletions cmd/multicoin/multicoin.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package multicoin
package main

import (
"flag"
Expand All @@ -21,10 +21,6 @@ var (
parseFlags = true
)

func init() {

}

func main() {
if parseFlags {
flag.Parse()
Expand Down
74 changes: 74 additions & 0 deletions cmd/proxy/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package main

import (
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"time"

"github.com/SkycoinProject/dmsg"
"github.com/SkycoinProject/dmsg/cipher"
"github.com/SkycoinProject/dmsg/disc"
)

var (
PK = "0311607e59d1d0dc07fa33c641d31af10b8081de57b1c3c0d732804099f6a64dcb"
SK = "f36543b56f5bd8b93cac088c1550c9081e9bd8302b18f09fc7e42ed9270aae65"
)

func main() {
var sPK cipher.PubKey
var sSK cipher.SecKey
_ = sPK.Set(PK)
_ = sSK.Set(SK)

dmsgClient := dmsg.NewClient(sPK, sSK, disc.NewHTTP("http://dmsg.discovery.skywire.cc"), dmsg.DefaultConfig())
go dmsgClient.Serve()

time.Sleep(time.Second) // wait for dmsg client to be ready

// port where server will listen
serverPort := uint16(8080)

btcdrpcurl, err := url.Parse("http://127.0.0.1:18554")
if err != nil {
panic(err)
}

proxy := httputil.NewSingleHostReverseProxy(btcdrpcurl)

// prepare server route handling
mux := http.NewServeMux()
mux.HandleFunc("/", handler(proxy))

// run the server
srv := &http.Server{
Handler: mux,
}

list, err := dmsgClient.Listen(serverPort)
if err != nil {
panic(err)
}

sErr := make(chan error, 1)
go func() {
sErr <- srv.Serve(list)
close(sErr)
}()

var retErr error
select {
case retErr = <-sErr:
fmt.Println(retErr)
}
}

func handler(p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL)
p.ServeHTTP(w, r)
}
}
14 changes: 8 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ module github.com/SkycoinProject/multicoin-wallet
go 1.13

require (
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/NYTimes/gziphandler v1.1.1
github.com/SkycoinProject/dmsg v0.1.1-0.20200323190518-7370d0e02392
github.com/SkycoinProject/dmsg-http v0.0.0-20200318122149-9977e5986a7d
github.com/SkycoinProject/skycoin v0.27.0
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/boltdb/bolt v1.3.1 // indirect
github.com/btcsuite/btcd v0.20.1-beta
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/btcsuite/btcd v0.20.1-beta.0.20200325095142-cfcf4fb7625a
github.com/btcsuite/btcutil v1.0.1
github.com/ethereum/go-ethereum v1.9.12
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc // indirect
github.com/sirupsen/logrus v1.4.2
github.com/sirupsen/logrus v1.5.0
github.com/stretchr/testify v1.5.1
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 // indirect
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
)
214 changes: 210 additions & 4 deletions go.sum

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions pkg/api/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
"github.com/SkycoinProject/multicoin-wallet/pkg/coin"
)

//go:generate mockery -name Gatewayer -case underscore -inpkg -testonly

// Gateway is the api gateway
type Gateway struct {
*coin.CoinManager
Expand All @@ -22,5 +20,5 @@ func NewGateway(cm *coin.CoinManager) *Gateway {

// Gatewayer interface for Gateway methods
type Gatewayer interface {
SetupMultiCoinRoutes(prefix string, handler func(endpoint string, handler http.Handler))
SetupCoinRoutes(prefix string, webhandler func(string, http.Handler))
}
8 changes: 7 additions & 1 deletion pkg/api/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ type muxConfig struct {
host string
}

// HTTPError is included in an HTTPResponse
type HTTPError struct {
Message string `json:"message"`
Code int `json:"code"`
}

// Server exposes an HTTP API
type Server struct {
server *http.Server
Expand Down Expand Up @@ -113,7 +119,7 @@ func newServerMux(c muxConfig, gateway Gatewayer) *http.ServeMux {
webHandler("/api/"+apiVersion1+endpoint, handler)
}

gateway.SetupMultiCoinRoutes("/multicoin", webHandlerV1)
gateway.SetupCoinRoutes("/multicoin", webHandlerV1)

return mux
}
51 changes: 47 additions & 4 deletions pkg/coin/btc/btc.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,64 @@
package btc

import (
"encoding/json"
"fmt"
"net/http"

"github.com/btcsuite/btcd/rpcclient"
"github.com/SkycoinProject/multicoin-wallet/pkg/coin/btc/rpc"
)

type BTC struct {
rpc *rpcclient.Client
rpc *rpc.Client
}

func New() *BTC {
// run btcd node with --notls flag
client := rpc.NewClient("dmsg://0311607e59d1d0dc07fa33c641d31af10b8081de57b1c3c0d732804099f6a64dcb:8080/")
return &BTC{
rpc: &rpcclient.Client{},
rpc: client,
}
}

func (btc *BTC) SetupRoutes(prefix string, handler func(endpoint string, handler http.Handler)) {
func (btc *BTC) SetupRoutes(prefix string, webhandler func(string, http.Handler)) {
webhandler(fmt.Sprintf("%s/balance", prefix), BalanceHandler(btc.rpc))
}

func BalanceHandler(rpc *rpc.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
amt, err := rpc.GetBalance("account1")
if err != nil {
Error500(w, err.Error())
}

SendJSONOr500(w, amt)
}
}

// SendJSONOr500 writes an object as JSON, writing a 500 error if it fails
func SendJSONOr500(w http.ResponseWriter, m interface{}) {
out, err := json.MarshalIndent(m, "", " ")
if err != nil {
Error500(w, err.Error())
return
}

w.Header().Add("Content-Type", "application/json")

if _, err := w.Write(out); err != nil {
Error500(w, err.Error())
}
}

func Error500(w http.ResponseWriter, msg string) {
ErrorXXX(w, http.StatusInternalServerError, msg)
}

func ErrorXXX(w http.ResponseWriter, status int, msg string) {
httpMsg := fmt.Sprintf("%d %s", status, http.StatusText(status))
if msg != "" {
httpMsg = fmt.Sprintf("%s - %s", httpMsg, msg)
}

http.Error(w, httpMsg, status)
}
132 changes: 132 additions & 0 deletions pkg/coin/btc/rpc/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package rpc

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"

"github.com/SkycoinProject/dmsg"
dmsghttp "github.com/SkycoinProject/dmsg-http"
"github.com/SkycoinProject/dmsg/cipher"
"github.com/SkycoinProject/dmsg/disc"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcutil"
)

const (
dialTimeout = 60 * time.Second
httpClientTimeout = 120 * time.Second
tlsHandshakeTimeout = 60 * time.Second

// ContentTypeJSON json content type header
ContentTypeJSON = "application/json"
// ContentTypeForm form data content type header
ContentTypeForm = "application/x-www-form-urlencoded"
)

type Client struct {
httpClient *http.Client
addr string
}

// rawResponse is a partially-unmarshaled JSON-RPC response. For this
// to be valid (according to JSON-RPC 1.0 spec), ID may not be nil.
type rawResponse struct {
Result json.RawMessage `json:"result"`
Error *btcjson.RPCError `json:"error"`
}

func NewClient(addr string) *Client {
discovery := disc.NewHTTP("http://dmsg.discovery.skywire.cc")
cPK, cSK := cipher.GenerateKeyPair()
dmsgClient := dmsg.NewClient(cPK, cSK, discovery, dmsg.DefaultConfig())
go dmsgClient.Serve()

time.Sleep(time.Second) // wait for dmsg client to be ready

dmsgTransport := dmsghttp.Transport{
DmsgClient: dmsgClient,
}

httpClient := &http.Client{
Transport: dmsgTransport,
Timeout: httpClientTimeout,
}
addr = strings.TrimRight(addr, "/")
addr += "/"
return NewClientWithHTTPClient(addr, httpClient)
}

func NewClientWithHTTPClient(addr string, httpClient *http.Client) *Client {
return &Client{
httpClient: httpClient,
addr: addr,
}
}

func (c *Client) GetBalance(account string) (*btcutil.Amount, error) {
cmd := btcjson.NewGetBalanceCmd(&account, nil)

resp, err := c.sendCmd(cmd)
if err != nil {
return nil, err
}

fmt.Println(resp)

// Unmarshal result as a floating point number.
var balance float64
err = json.Unmarshal(resp.Result, &balance)
if err != nil {
return nil, err
}

amount, err := btcutil.NewAmount(balance)
if err != nil {
return nil, err
}

return &amount, nil

}

func (c *Client) sendCmd(cmd interface{}) (*rawResponse, error) {
// Marshal the command.
marshalledJSON, err := btcjson.MarshalCmd(1, cmd)
if err != nil {
return nil, err
}

bodyReader := bytes.NewReader(marshalledJSON)
httpReq, err := http.NewRequest("POST", c.addr, bodyReader)
if err != nil {
return nil, err
}

httpReq.Close = true
httpReq.Header.Set("Content-Type", "application/json")
httpReq.SetBasicAuth("user", "password")
httpResponse, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, err
}

// Read the raw bytes and close the response.
respBytes, err := ioutil.ReadAll(httpResponse.Body)
_ = httpResponse.Body.Close()
if err != nil {
return nil, err
}

var resp rawResponse
err = json.Unmarshal(respBytes, &resp)
if err != nil {
return nil, err
}

return &resp, nil
}
2 changes: 1 addition & 1 deletion pkg/coin/coin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import (
)

type Coin interface {
SetupRoutes(prefix string, handler func(endpoint string, handler http.Handler))
SetupRoutes(prefix string, webhandler func(string, http.Handler))
}
Loading