├── doc
├── ss01.png
├── ss02.png
├── ss03.png
├── ss04.png
├── ss05.png
├── ss06.png
└── ss07.png
├── src
├── alice
│ ├── html
│ │ └── alice
│ │ │ ├── AIRSKY.png
│ │ │ ├── MELON.png
│ │ │ ├── MONECRE.png
│ │ │ ├── productlogo.png
│ │ │ ├── qr_button.png
│ │ │ ├── index.html
│ │ │ ├── alice.css
│ │ │ ├── alice.js
│ │ │ └── bootstrap-theme.min.css
│ ├── doc.go
│ └── main.go
├── dave
│ ├── html
│ │ └── dave
│ │ │ ├── coffee.jpg
│ │ │ ├── index.html
│ │ │ ├── order.js
│ │ │ ├── list.js
│ │ │ ├── list.html
│ │ │ ├── order.html
│ │ │ ├── order.css
│ │ │ └── jquery.qrcode.min.js
│ ├── doc.go
│ └── main.go
├── lib
│ ├── doc.go
│ ├── cyclic.go
│ └── http.go
├── democonf
│ ├── doc.go
│ ├── democonf.json
│ └── democonf.go
├── rpc
│ ├── doc.go
│ ├── helper.go
│ └── rpc.go
├── charlie
│ ├── doc.go
│ └── main.go
├── fred
│ ├── doc.go
│ └── main.go
└── bob
│ ├── doc.go
│ └── main.go
├── COPYING
├── stop_demo.sh
├── README.md
└── start_demo.sh
/doc/ss01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/doc/ss01.png
--------------------------------------------------------------------------------
/doc/ss02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/doc/ss02.png
--------------------------------------------------------------------------------
/doc/ss03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/doc/ss03.png
--------------------------------------------------------------------------------
/doc/ss04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/doc/ss04.png
--------------------------------------------------------------------------------
/doc/ss05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/doc/ss05.png
--------------------------------------------------------------------------------
/doc/ss06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/doc/ss06.png
--------------------------------------------------------------------------------
/doc/ss07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/doc/ss07.png
--------------------------------------------------------------------------------
/src/alice/html/alice/AIRSKY.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/src/alice/html/alice/AIRSKY.png
--------------------------------------------------------------------------------
/src/alice/html/alice/MELON.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/src/alice/html/alice/MELON.png
--------------------------------------------------------------------------------
/src/dave/html/dave/coffee.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/src/dave/html/dave/coffee.jpg
--------------------------------------------------------------------------------
/src/alice/html/alice/MONECRE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/src/alice/html/alice/MONECRE.png
--------------------------------------------------------------------------------
/src/alice/html/alice/productlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/src/alice/html/alice/productlogo.png
--------------------------------------------------------------------------------
/src/alice/html/alice/qr_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElementsProject/confidential-assets-demo/HEAD/src/alice/html/alice/qr_button.png
--------------------------------------------------------------------------------
/src/dave/html/dave/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ");
12 | let item = $("| ").text(order.Item);
13 | let addr = $(" | ").attr("title", order.Addr).text(shortAddr(order.Addr));
14 | let price = $(" | ").text(order.Price);
15 | let asset = $(" | ").text(order.Asset);
16 | let status = $(" | ").text(getStatus(order.Status));
17 | let to = $(" | ").text(formatDate(order.Timeout));
18 | let lm = $(" | ").text(formatDate(order.LastModify));
19 | tr.append(item).append(addr).append(price).append(asset).append(status).append(to).append(lm);
20 | $("#list").prepend(tr);
21 | }
22 | $("#lm").text(formatDate(Math.floor((new Date()).getTime() / 1000)));
23 | }
24 | });
25 | setTimeout(list, 3000);
26 | }
27 |
28 | function shortAddr(addr) {
29 | let ret = addr;
30 | if (ret.length > 20) {
31 | ret = ret.slice(0, 10) + " ... " + ret.slice(ret.length - 10);
32 | }
33 | return ret;
34 | }
35 |
36 | function getStatus(status) {
37 | let msg = "Unknown";
38 | if (status == 1) {
39 | msg = "Paid";
40 | } else if (status == 0) {
41 | msg = "Waiting";
42 | } else if (status == -1) {
43 | msg = "Timeout";
44 | }
45 | return msg;
46 | }
47 |
48 | function formatDate(unixTimestamp) {
49 | let date = new Date(unixTimestamp * 1000);
50 | return ""
51 | // + date.getFullYear() + "/"
52 | // + ('0' + (date.getMonth() + 1)).slice(-2) + "/"
53 | // + ('0' + date.getDate()).slice(-2) + " "
54 | + ('0' + date.getHours()).slice(-2) + ":"
55 | + ('0' + date.getMinutes()).slice(-2) + ":"
56 | + ('0' + date.getSeconds()).slice(-2);
57 | }
58 |
59 | $(init);
--------------------------------------------------------------------------------
/src/dave/html/dave/list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | List
10 |
47 |
48 |
49 |
50 |
51 | LastModify:
52 |
53 |
54 |
55 | | Item |
56 | Addr |
57 | Price |
58 | Asset |
59 | Status |
60 | Timeout |
61 | LastModified |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/lib/cyclic.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 DG Lab
2 | // Distributed under the MIT software license, see the accompanying
3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 |
5 | /*
6 | Package lib (cyclic.go) provides a very simple cyclic process management.
7 |
8 | usage:
9 | 1) define a function for cyclic process. (no input/no output)
10 | ex) func loop() { fmt.Println("called."); return }
11 | 2) start that function
12 | a) wait for the processes will done.
13 | ex) _, err := lib.StartCyclic(loop, 3 , true)
14 | b) to do other task and wait SIGINT signal to stop.
15 | ex) wg, err := lib.StartCyclic(loop, 3, false)
16 | // do something
17 | wg.Wait()
18 | c) to do other task and stop the cyclic process immediately.
19 | ex) wg, err := lib.StartCyclic(loop, 3, false)
20 | // do something
21 | lib.StopCyclicProc(wg)
22 | */
23 | package lib
24 |
25 | import (
26 | "fmt"
27 | "os"
28 | "os/signal"
29 | "sync"
30 | "syscall"
31 | "time"
32 | )
33 |
34 | // StartCyclic calls each function with each interval.
35 | func StartCyclic(callback func(), period int64, wait bool) (*sync.WaitGroup, error) {
36 | if period <= 0 {
37 | return nil, fmt.Errorf("period must be a plus value: %d", period)
38 | }
39 |
40 | wg := &sync.WaitGroup{}
41 | wg.Add(1)
42 | go func() {
43 | logger.Println("period", period, "s")
44 | sig := make(chan os.Signal, 1)
45 | signal.Notify(sig, syscall.SIGINT)
46 | ticker := time.NewTicker(time.Duration(period) * time.Second)
47 |
48 | defer func() {
49 | ticker.Stop()
50 | close(sig)
51 | wg.Done()
52 | }()
53 |
54 | for {
55 | select {
56 | case <-ticker.C:
57 | callback()
58 | case rcv := <-sig:
59 | logger.Println("signal:", rcv)
60 | return
61 | }
62 | }
63 | }()
64 |
65 | if wait {
66 | wg.Wait()
67 | }
68 |
69 | return wg, nil
70 | }
71 |
72 | // StopCyclicProc sends interrupt signal to myself.
73 | func StopCyclicProc(wg *sync.WaitGroup) {
74 | pid := os.Getpid()
75 | p, err := os.FindProcess(pid)
76 | if err == nil {
77 | _ = p.Signal(os.Interrupt)
78 | }
79 | if wg != nil {
80 | wg.Wait()
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/dave/html/dave/order.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Store
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Frontstar Coffee
16 | Frontstar Coffee Shibuya Station
17 |
18 |
19 | 
20 |
21 |
22 |
23 | Product |
24 | Caramel Macchiato Coffee |
25 |
26 |
27 | Price |
28 | MELON 200 pt |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | Scan This QR Code (Click to put into Clipboard!)
37 |
38 |
39 |
40 |
41 |
42 |
45 |
46 | Data in clipboard; paste this into the customer interface
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/democonf/democonf.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 DG Lab
2 | // Distributed under the MIT software license, see the accompanying
3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 |
5 | // Package democonf externalize setting.
6 | package democonf
7 |
8 | import (
9 | "encoding/json"
10 | "log"
11 | "os"
12 | "path/filepath"
13 | )
14 |
15 | // DemoConf represents externalized setting.
16 | type DemoConf struct {
17 | Data map[string]interface{}
18 | logger *log.Logger
19 | }
20 |
21 | // NewDemoConf creates DemoConf with specified section.
22 | func NewDemoConf(section string) *DemoConf {
23 | conf := new(DemoConf)
24 | conf.logger = log.New(os.Stdout, "DemoConf:", log.LstdFlags)
25 | dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
26 | file, err := os.Open(dir + "/democonf.json")
27 | if err != nil {
28 | conf.logger.Println("os#Open error", err)
29 | return conf
30 | }
31 | dec := json.NewDecoder(file)
32 | var j map[string]map[string]interface{}
33 | err = dec.Decode(&j)
34 | if err != nil {
35 | conf.logger.Println("decode error", err)
36 | return conf
37 | }
38 | val, ok := j[section]
39 | if !ok {
40 | conf.logger.Println("section not found", section)
41 | return conf
42 | }
43 | conf.Data = val
44 | return conf
45 | }
46 |
47 | // GetString returns config value by string.
48 | func (conf *DemoConf) GetString(key string, defaultValue string) string {
49 | val, ok := conf.Data[key]
50 | if !ok {
51 | conf.logger.Println("key not found. Key:", key)
52 | return defaultValue
53 | }
54 | str, ok := val.(string)
55 | if !ok {
56 | conf.logger.Printf("type is not a string. Type: %T, Value: %+v\n", val, val)
57 | return defaultValue
58 | }
59 | return str
60 | }
61 |
62 | // GetNumber returns config value by number(float64).
63 | func (conf *DemoConf) GetNumber(key string, defaultValue float64) float64 {
64 | val, ok := conf.Data[key]
65 | if !ok {
66 | conf.logger.Println("key not found. Key:", key)
67 | return defaultValue
68 | }
69 | num, ok := val.(float64)
70 | if !ok {
71 | conf.logger.Printf("type is not a number. Type: %T, Value: %+v\n", val, val)
72 | return defaultValue
73 | }
74 | return num
75 | }
76 |
77 | // GetBool returns config value by bool.
78 | func (conf *DemoConf) GetBool(key string, defaultValue bool) bool {
79 | val, ok := conf.Data[key]
80 | if !ok {
81 | conf.logger.Println("key not found. Key:", key)
82 | return defaultValue
83 | }
84 | b, ok := val.(bool)
85 | if !ok {
86 | conf.logger.Printf("type is not a bool. Type: %T, Value: %+v\n", val, val)
87 | return defaultValue
88 | }
89 | return b
90 | }
91 |
92 | // GetInterface returns config value by interface{}
93 | func (conf *DemoConf) GetInterface(key string, result interface{}) {
94 | val, ok := conf.Data[key]
95 | if !ok {
96 | conf.logger.Println("key not found. Key:", key)
97 | return
98 | }
99 | var bs []byte
100 | m, ok := val.(map[string]interface{})
101 | if !ok {
102 | a, ok := val.([]interface{})
103 | if !ok {
104 | conf.logger.Printf("type is neither map[string]interface{} nor []interface{}. Type: %T, Value: %+v\n", val, val)
105 | return
106 | }
107 | bs, _ = json.Marshal(a)
108 | } else {
109 | bs, _ = json.Marshal(m)
110 | }
111 | err := json.Unmarshal(bs, result)
112 | if err != nil {
113 | conf.logger.Printf("json#Unmarshal error. %+v", err)
114 | return
115 | }
116 | return
117 | }
118 |
--------------------------------------------------------------------------------
/src/bob/main.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 DG Lab
2 | // Distributed under the MIT software license, see the accompanying
3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 |
5 | // bob project main.go
6 | package main
7 |
8 | import (
9 | "fmt"
10 | "log"
11 | "os"
12 |
13 | "democonf"
14 | "lib"
15 | "rpc"
16 | )
17 |
18 | // Block with Transactions
19 | type Block struct {
20 | Tx []string `json:"tx,"`
21 | }
22 |
23 | var rpcurl = "http://127.0.0.1:10010"
24 | var rpcuser = "user"
25 | var rpcpass = "pass"
26 |
27 | var rpcClient *rpc.Rpc
28 |
29 | var assets = make(map[string]string)
30 |
31 | var logger *log.Logger
32 |
33 | var blockcount = -1
34 |
35 | func getblockcount() (int, error) {
36 | blockcount, res, err := rpcClient.RequestAndCastNumber("getblockcount")
37 | if err != nil {
38 | logger.Printf("Rpc#RequestAndCastNumber error:%v res:%+v", err, res)
39 | return -1, err
40 | }
41 | return int(blockcount), nil
42 | }
43 |
44 | func viewBlock(height int) error {
45 | blockhash, res, err := rpcClient.RequestAndCastString("getblockhash", height)
46 | if err != nil {
47 | logger.Printf("Rpc#RequestAndCastString error:%v res:%+v", err, res)
48 | return err
49 | }
50 | var block Block
51 | res, err = rpcClient.RequestAndUnmarshalResult(&block, "getblock", blockhash)
52 | if err != nil {
53 | logger.Printf("Rpc#RequestAndUnmarshalResult error:%v res:%+v", err, res)
54 | return err
55 | }
56 | for _, tx := range block.Tx {
57 | printtxouts(fmt.Sprintf("%v", tx))
58 | }
59 | return nil
60 | }
61 |
62 | func printtxouts(txid string) error {
63 | fmt.Println("TXID:", txid)
64 | var tx rpc.RawTransaction
65 | res, err := rpcClient.RequestAndUnmarshalResult(&tx, "getrawtransaction", txid, 1)
66 | if err != nil {
67 | logger.Printf("Rpc#RequestAndUnmarshalResult error:%v res:%+v", err, res)
68 | return err
69 | }
70 | format := "[%d] Value: %3v Asset: %7v -> %v\n"
71 | for _, out := range tx.Vout {
72 | if out.Asset == "" {
73 | fmt.Printf(format, out.N, "???", "???????", out.ScriptPubKey.Addresses)
74 | } else {
75 | if out.ScriptPubKey.Type == "fee" {
76 | fmt.Printf(format, out.N, out.Value, assets[out.Asset], "fee")
77 | } else {
78 | fmt.Printf(format, out.N, out.Value, assets[out.Asset], out.ScriptPubKey.Addresses)
79 | }
80 | }
81 | }
82 | return nil
83 | }
84 |
85 | func getassetlabels() error {
86 | var labels map[string]string
87 | res, err := rpcClient.RequestAndUnmarshalResult(&labels, "dumpassetlabels")
88 | if err != nil {
89 | logger.Printf("Rpc#RequestAndUnmarshalResult error:%v res:%+v", err, res)
90 | return err
91 | }
92 | for k, v := range labels {
93 | assets[fmt.Sprintf("%v", v)] = k
94 | }
95 | return nil
96 | }
97 |
98 | func callback() {
99 | getassetlabels()
100 | blockheight, err := getblockcount()
101 | if err != nil {
102 | logger.Println("getblockcount error:", err)
103 | return
104 | }
105 | if blockcount < 0 {
106 | blockcount = blockheight
107 | fmt.Println("Start block", blockcount)
108 | } else if blockcount < blockheight {
109 | for blockcount < blockheight {
110 | blockcount++
111 | fmt.Println("Find block", blockcount)
112 | viewBlock(blockcount)
113 | }
114 | }
115 | }
116 |
117 | func loadConf() {
118 | conf := democonf.NewDemoConf("bob")
119 | rpcurl = conf.GetString("rpcurl", rpcurl)
120 | rpcuser = conf.GetString("rpcuser", rpcuser)
121 | rpcpass = conf.GetString("rpcpass", rpcpass)
122 | }
123 |
124 | func main() {
125 | logger = log.New(os.Stdout, "Bob:", log.LstdFlags+log.Lshortfile)
126 | fmt.Println("Bob starting")
127 |
128 | loadConf()
129 | rpcClient = rpc.NewRpc(rpcurl, rpcuser, rpcpass)
130 |
131 | lib.SetLogger(logger)
132 | lib.StartCyclic(callback, 3, true)
133 |
134 | fmt.Println("Bob stopping")
135 | }
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Confidential Assets Demo
2 |
3 | ## Introduction
4 |
5 | The Confidential Assets feature of Elements blockchain platform allows competing parties to interact
6 | within the same blockchain without revealing crucial information to each other, such as the asset
7 | identities and quantities transferred in a given period.
8 |
9 | This is a simple demonstration showing a scenario where a coffee shop (Dave the merchant) charges
10 | a customer (Alice) for coffee using a given type of asset, which the customer does not presently hold.
11 | To facilitate the purchase, the customer makes use of a point exchange system (Charlie) to convert one
12 | of their other assets into the one Dave accepts.
13 |
14 | Bob is a competitor trying to gather info about Dave's sales. Due to the CT/CA feature of Elements,
15 | the idea is that he will not see anything useful at all by processing transactions on the blockchain.
16 |
17 | Fred is completely uninteresting but necessary as he makes blocks on the blockchain when transactions
18 | enter his mempool.
19 |
20 | ## Prerequisites
21 |
22 | The demo uses the following libraries/tools:
23 |
24 | * [Elements blockchain platform](https://github.com/ElementsProject/elements)
25 | * [Go](https://golang.org/)
26 | * [jq](https://stedolan.github.io/jq/)
27 |
28 | Installation (Go and jq):
29 | * (linux) using apt as `golang-1.7` and `jq`
30 | * (macOS) using brew as `golang` and `jq`
31 |
32 | ## Installation and set up
33 |
34 | The demo is written in Go with some HTML/JS components for UI related stuff.
35 |
36 | The demo must be built. This can be done using the `build.sh` script.
37 |
38 | There are five nodes, one for each party mentioned above, as well as several assets that must be
39 | generated and given to the appropriate party before the demo will function. This can be automated using
40 | the `start_demo.sh` script. For this to work, you must have `elementsd`, `elements-tx`, and `elements-cli`
41 | in the path. E.g. by doing `export PATH=$PATH:/home/me/workspace/elements/src` or alternatively by doing
42 | `make install` from `elements/src` beforehand.
43 |
44 | `start_demo.sh` essentially does the following:
45 |
46 | 1. Sets up 5 Elements blockchain platform nodes and connects them to each other.
47 | 2. Generates the appropriate assets.
48 | 3. Sends assets to the appropriate parties.
49 | 4. Starts up the appropriate demo-specific daemons.
50 |
51 | After this, open two pages in a web browser:
52 | - http://127.0.0.1:8000/ (the customer Alice's UI)
53 | - http://127.0.0.1:8030/order.html (the merchant Dave's order page)
54 | - http://127.0.0.1:8030/list.html (the merchant Dave's admin page)
55 |
56 | The idea is that Dave presents Alice with his UI, and Alice uses her UI (some app) to perform the
57 | exchange.
58 |
59 | ## Screenshots
60 |
61 | 
62 |
63 | 
64 |
65 | 
66 |
67 | 
68 |
69 | 
70 |
71 | 
72 |
73 | 
74 |
75 | ## License and Disclaimer
76 |
77 | ```
78 | The MIT License (MIT)
79 |
80 | Copyright (c) 2017 DG Lab
81 |
82 | Permission is hereby granted, free of charge, to any person obtaining a copy
83 | of this software and associated documentation files (the "Software"), to deal
84 | in the Software without restriction, including without limitation the rights
85 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
86 | copies of the Software, and to permit persons to whom the Software is
87 | furnished to do so, subject to the following conditions:
88 |
89 | The above copyright notice and this permission notice shall be included in
90 | all copies or substantial portions of the Software.
91 |
92 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
93 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
94 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
95 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
96 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
97 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
98 | THE SOFTWARE.
99 | ```
100 |
--------------------------------------------------------------------------------
/src/dave/html/dave/order.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0px;
3 | padding: 0px;
4 | color: #595757;
5 | font-family: fmonospace;
6 | font-size: 12pt;
7 | }
8 |
9 | body {
10 | background-color: #EFEFEF;
11 | }
12 |
13 | #page {
14 | width: 500px;
15 | margin-left: auto;
16 | margin-right: auto;
17 | }
18 |
19 | #head {
20 | text-align: center;
21 | padding: 20px 0px;
22 | }
23 |
24 | .store {
25 | font-size: 20pt;
26 | font-weight: bold;
27 | transform: scale(1, 1.4);
28 | }
29 |
30 | .office {
31 | font-size: 10pt;
32 | font-weight: bold;
33 | padding-top: 5px;
34 | }
35 |
36 | #before {
37 | width: 500px;
38 | height: 560px;
39 | background-color: #FFFFFF;
40 | text-align: center;
41 | border-radius: 10px;
42 | }
43 |
44 | #before img {
45 | width: 430px;
46 | padding-top: 30px;
47 | }
48 |
49 | #pinfo {
50 | position: relative;
51 | width: 430px;
52 | margin: 0px auto;
53 | overflow: hidden;
54 | margin-top: 10px;
55 | }
56 |
57 | #pinfo::before {
58 | position: absolute;
59 | content: '';
60 | width: 0px;
61 | height: 0px;
62 | border: 5px solid #FFFFFF;
63 | border-radius: 5px;
64 | top: 46px;
65 | left: -5px;
66 | }
67 |
68 | #pinfo::after {
69 | position: absolute;
70 | content: '';
71 | width: 0px;
72 | height: 0px;
73 | border: 5px solid #FFFFFF;
74 | border-radius: 5px;
75 | top: 46px;
76 | right: -5px;
77 | }
78 |
79 | #before table {
80 | background-color: #00E6C8;
81 | width: 100%;
82 | border-radius: 5px;
83 | }
84 |
85 | #before tr {
86 | height: 50px;
87 | }
88 |
89 | #before tr.sep td {
90 | border-bottom: 1px dashed #FFFFFF;
91 | }
92 |
93 | #before div.key {
94 | font-size: 10pt;
95 | }
96 |
97 | #before td.key {
98 | text-align: left;
99 | padding-left: 20px;
100 | }
101 |
102 | #before td.val {
103 | text-align: right;
104 | padding-right: 20px;
105 | }
106 |
107 | #before .pname {
108 | font-size: 12pt;
109 | font-weight: bold;
110 | }
111 |
112 | #before .passet {
113 | font-size: 12pt;
114 | display: inline-block;
115 | }
116 |
117 | #before .pprice {
118 | font-size: 20pt;
119 | font-weight: bold;
120 | transform: scale(1, 1.4);
121 | display: inline-block;
122 | padding: 0px 10px;
123 | }
124 |
125 | #before .punit {
126 | font-size: 14pt;
127 | font-weight: bold;
128 | display: inline-block;
129 | }
130 |
131 | #order {
132 | margin-top: 40px;
133 | background-color: #595757;
134 | color: #FFFFFF;
135 | width: 225px;
136 | height: 44px;
137 | border: none;
138 | border-radius: 5px;
139 | font-size: 12pt;
140 | font-weight: bold;
141 | letter-spacing: 0.1em;
142 | cursor: pointer;
143 | }
144 |
145 | #after {
146 | display: none;
147 | width: 500px;
148 | height: 560px;
149 | background-color: #FFFFFF;
150 | text-align: center;
151 | border-radius: 10px;
152 | padding-top: 30px;
153 | }
154 |
155 | #qrbase {
156 | width: 440px;
157 | border: 2px solid #595757;
158 | margin: 0px auto;
159 | border-radius: 10px;
160 | }
161 |
162 | #qrbase .qrtext {
163 | font-size: 12pt;
164 | font-weight: bold;
165 | padding-top: 15px;
166 | }
167 |
168 | #qrcode {
169 | padding-top: 0px;
170 | padding-bottom: 30px;
171 | cursor: pointer;
172 | }
173 |
174 | #addr {
175 | max-width: 25em;
176 | font-size: 12pt;
177 | font-weight: bold;
178 | letter-spacing: 0.1em;
179 | font-family: monospace;
180 | word-break: break-all;
181 | margin: 0px auto;
182 | }
183 |
184 | .qrbar {
185 | width: 400px;
186 | margin: 0px auto;
187 | border-bottom: 1px solid #595757;
188 | padding-top: 8px;
189 | margin-bottom: 30px;
190 | }
191 |
192 | .qrval {
193 | margin-bottom: 30px;
194 | }
195 |
196 | #name {
197 | font-size: 16pt;
198 | font-weight: bold;
199 | }
200 |
201 | #asset {
202 | font-size: 16pt;
203 | font-weight: bold;
204 | display: inline-block;
205 | }
206 |
207 | #price {
208 | font-size: 16pt;
209 | font-weight: bold;
210 | display: inline-block;
211 | padding: 0px 10px;
212 | }
213 |
214 | .unit {
215 | font-size: 16pt;
216 | font-weight: bold;
217 | display: inline-block;
218 | }
219 |
220 | #uri {
221 | width: 1px;
222 | height: 20px;
223 | border: none;
224 | }
225 |
226 | #message {
227 | position: fixed;
228 | top: 300px;
229 | left: 50%;
230 | width: 500px;
231 | margin-left: -250px;
232 | height: 2em;
233 | line-height: 2em;
234 | border-radius: 5px;
235 | background-color: gray;
236 | color: white;
237 | font-weight: bold;
238 | display: none;
239 | }
240 |
--------------------------------------------------------------------------------
/src/alice/html/alice/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Wallet
13 |
14 |
15 |
16 |
17 |
22 |
23 |
24 |
25 | Purchase using Points
26 |
27 |
28 |
29 |
30 |
31 |
32 | ADDRESS
33 |
34 |
37 |
38 | ▲ QR Scanner not available in this demo
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | Purchase Info
48 |
49 |
50 |
51 |
52 |
53 | Product
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 |
62 |
63 | Price
64 |
65 |
66 | -
67 |
68 |
69 | -
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | Point Type
79 |
80 |
81 | Balance
82 |
83 |
84 | Cost (Remainder)
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | ×
98 |
99 |
100 |
101 | ↑
102 | →
103 |
104 | Fee:
105 |
106 | Total cost:
107 |
108 | Is this OK?
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | Thank you!
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/start_demo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | shopt -s expand_aliases
3 |
4 | start_daemon () {
5 | if [ $# -le 1 ]; then
6 | echo " no nodes started."
7 | return 1
8 | fi
9 |
10 | for i in ${@:2:$#-1} ; do
11 | ${ELDAE} -datadir=${DEMOD}/data/${i} &
12 | if [ ""${1} == "1" ]; then
13 | echo "${i}_dae=$!" >> ./demo.tmp
14 | fi
15 | done
16 |
17 | LDW=1
18 | while [ "${LDW}" = "1" ]
19 | do
20 | LDW=0
21 | for i in ${@:2:$#-1} ; do
22 | ${ELCLI} -datadir=${DEMOD}/data/${i} getwalletinfo > /dev/null 2>&1 || LDW=1
23 | done
24 | if [ "${LDW}" = "1" ]; then
25 | echo -n -e "."
26 | sleep 1
27 | fi
28 | done
29 | echo " nodes started."
30 | return 0
31 | }
32 |
33 | # prepare
34 | ## be sure at elements-next folder
35 | cd "$(dirname "${BASH_SOURCE[0]}")"
36 | for i in elementsd elements-cli elements-tx ; do
37 | which $i > /dev/null
38 | if [ ""$? != "0" ];then
39 | echo "cannot find [" $i "]"
40 | exit 1
41 | fi
42 | done
43 |
44 | DEMOD=$PWD/demo
45 | ELDAE=elementsd
46 | ELCLI=elements-cli
47 | ELTX=elements-tx
48 |
49 | ## cleanup previous data
50 | if [ -e ./demo.tmp ]; then
51 | ./stop_demo.sh
52 | fi
53 | echo "ELCLI=$ELCLI" >> ./demo.tmp
54 |
55 | ## cleanup previous data
56 | rm -rf ${DEMOD}/data
57 |
58 | echo "initial setup - asset generation"
59 |
60 | ## setup nodes
61 | PORT=0
62 | for i in alice bob charlie dave fred; do
63 | mkdir -p ${DEMOD}/data/$i
64 | cat < ${DEMOD}/data/$i/elements.conf
65 | rpcuser=user
66 | rpcpassword=pass
67 | rpcport=$((10000 + $PORT))
68 | port=$((10001 + $PORT))
69 |
70 | connect=localhost:$((10001 + $(($PORT + 10)) % 50))
71 | regtest=1
72 | daemon=0
73 | listen=1
74 | txindex=1
75 | keypool=10
76 | initialfreecoins=2100000000000000
77 | EOF
78 | let PORT=PORT+10
79 | alias ${i}-dae="${ELDAE} -datadir=${DEMOD}/data/$i"
80 | alias ${i}-tx="${ELTX}"
81 | alias ${i}="${ELCLI} -datadir=${DEMOD}/data/$i"
82 | echo "${i}_dir=\"-datadir=${DEMOD}/data/$i\"" >> ./demo.tmp
83 | done
84 |
85 | start_daemon 0 fred
86 |
87 | echo "- generating initial blocks to reach maturity"
88 |
89 | ## generate assets
90 | fred generate 100 >/dev/null
91 |
92 | echo -n -e "- generating AIRSKY asset"
93 | AIRSKY=$(fred issueasset 1000000 500 | jq -r ".asset")
94 | echo -n -e ": $AIRSKY\n- generating MELON asset"
95 | MELON=$(fred issueasset 2000000 500 | jq -r ".asset")
96 | echo -n -e ": $MELON\n- generating MONECRE asset"
97 | MONECRE=$(fred issueasset 2000000 500 | jq -r ".asset")
98 | echo ": $MONECRE"
99 |
100 | echo -n -e "final setup - starting daemons"
101 |
102 | fred stop
103 | sleep 1
104 |
105 | ## setup nodes phase 2
106 | for i in alice bob charlie dave fred; do
107 | cat <> ${DEMOD}/data/$i/elements.conf
108 | assetdir=$AIRSKY:AIRSKY
109 | assetdir=$MELON:MELON
110 | assetdir=$MONECRE:MONECRE
111 | EOF
112 | done
113 |
114 | start_daemon 0 alice bob charlie dave fred
115 |
116 | ## generate assets
117 | fred getwalletinfo
118 |
119 | ## preset asset
120 | echo -n -e "AIRSKY"
121 | # fred sendtoaddress $(alice validateaddress $(alice getnewaddress) | jq -r ".unconfidential") 500 "" "" false "AIRSKY" >/dev/null
122 | fred sendtoaddress $(alice getnewaddress) 500 "" "" false "AIRSKY" >/dev/null
123 | sleep 1
124 | echo -n -e "\nMELON"
125 | # fred sendtoaddress $(alice validateaddress $(alice getnewaddress) | jq -r ".unconfidential") 100 "" "" false "MELON" >/dev/null
126 | fred sendtoaddress $(alice getnewaddress) 100 "" "" false "MELON" >/dev/null
127 | sleep 1
128 | echo -n -e "\nMONECRE"
129 | # fred sendtoaddress $(alice validateaddress $(alice getnewaddress) | jq -r ".unconfidential") 150 "" "" false "MONECRE" >/dev/null
130 | fred sendtoaddress $(alice getnewaddress) 150 "" "" false "MONECRE" >/dev/null
131 | echo -n -e "\n"
132 | fred generate 1 >/dev/null
133 | sleep 1 # wait for sync
134 | echo "Alice wallet:"
135 | alice getwalletinfo
136 |
137 | echo -n -e "Sending to Charlie [ ]\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"
138 | for i in 100 200 300 400 500; do
139 | for j in AIRSKY MELON MONECRE; do
140 | # fred sendtoaddress $(charlie validateaddress $(charlie getnewaddress) | jq -r ".unconfidential") $i "" "" false "$j" >/dev/null
141 | fred sendtoaddress $(charlie getnewaddress) $i "" "" false "$j" >/dev/null
142 | echo -n -e "."
143 | done
144 | done
145 | echo ""
146 | fred generate 1
147 | sleep 1 # wait for sync
148 | echo "Charlie wallet:"
149 | charlie getwalletinfo
150 |
151 | ## setup nodes phase 3
152 | alice stop
153 | bob stop
154 | charlie stop
155 | dave stop
156 | fred stop
157 | sleep 3
158 |
159 | for i in alice bob charlie dave fred; do
160 | cat <> ${DEMOD}/data/$i/elements.conf
161 | feeasset=$AIRSKY
162 | EOF
163 | done
164 |
165 | start_daemon 1 alice bob charlie dave fred
166 |
167 | cd ${DEMOD}
168 | for i in alice bob charlie dave fred; do
169 | ./$i &
170 | echo "${i}_pid=$!" >> ../demo.tmp
171 | done
172 |
173 | cd "$(dirname "${BASH_SOURCE[0]}")"
174 | sleep 2
175 |
176 | echo "Setup complete. Use these URLs to test it out:"
177 | echo "Alice -> http://127.0.0.1:8000/"
178 | echo "Dave -> http://127.0.0.1:8030/order.html"
179 | echo "Dave -> http://127.0.0.1:8030/list.html"
180 | echo "When finished, run stop_demo.sh"
181 |
--------------------------------------------------------------------------------
/src/alice/html/alice/alice.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: #707172;
3 | }
4 |
5 | #walletbody {
6 | background-color: #EEEFF0;
7 | width: 640px;
8 | }
9 |
10 | #headerrow {
11 | background-color: white;
12 | color: rgb(89,87,87);
13 | padding-top: 30px;
14 | padding-bottom: 30px;
15 | background-color: #FEFFFF;
16 | }
17 |
18 | #titletext {
19 | color: white;
20 | font-size: 1.2em;
21 | font-weight: bold;
22 | background-color: rgb(89,87,87);
23 | margin-top: 30px;
24 | padding-top: 10px;
25 | margin-bottom: 20px;
26 | padding-bottom: 10px;
27 | margin-right: 10px;
28 | border-radius: 20px;
29 | }
30 |
31 | .addressdetail {
32 | background-color: #EEEFF0;
33 | padding-left: 40px;
34 | padding-right: 40px;
35 | padding-top: 30px;
36 | padding-bottom: 30px;
37 | }
38 |
39 | #addressinput {
40 | height: 50px;
41 | width: 80%;
42 | font-size: 1.2em;
43 | font-weight: bold;
44 | }
45 |
46 | /* Modal */
47 |
48 | .modal {
49 | color: white;
50 | display: none;
51 | position: fixed;
52 | z-index: 1;
53 | width: 100%;
54 | height: 100%;
55 | background-color: #000000;
56 | background-color: rgba(0,0,0,0.8);
57 | }
58 |
59 | .modal-content {
60 | background-color: #000000;
61 | top: 20%;
62 | padding: 10px;
63 | border: 1px solid #FFFFFF;
64 | width: 50%;
65 | }
66 |
67 | #addressline {
68 | height: 3px;
69 | }
70 |
71 | #qr_sorry {
72 | color: #EEEFF0;
73 | }
74 |
75 | button.cancel {
76 | width: 100%;
77 | background-color: white;
78 | color: rgb(50,50,50);
79 | }
80 |
81 | button.ok {
82 | width: 100%;
83 | background-color: rgb(3,230,200);
84 | color: rgb(50,50,50);
85 | }
86 |
87 | /* Point containers */
88 |
89 | .point {
90 | background-color: rgb(3,230,200);
91 | margin-left: 5%;
92 | width: 90%;
93 | padding-top: 30px;
94 | padding-bottom: 30px;
95 | }
96 |
97 | .pointseparator {
98 | margin-left: 7%;
99 | width: 86%;
100 | background-color: rgb(3,230,200);
101 | border-top-style: dashed;
102 | border-top-color: white;
103 | border-top-width: 2px;
104 | }
105 |
106 | .toppoint {
107 | border-top-left-radius: 10px;
108 | border-top-right-radius: 10px;
109 | background:
110 | linear-gradient(315deg, transparent 10px, rgb(3,230,200) 0) bottom right,
111 | linear-gradient(45deg, transparent 10px, rgb(3,230,200) 0) bottom left;
112 | background-size: 51% 100%;
113 | background-repeat: no-repeat;
114 | background-image:
115 | radial-gradient(circle at 100% 100%, rgba(3,230,200,0) 14px, rgb(3,230,200) 15px),
116 | radial-gradient(circle at 0 100%, rgba(3,230,200,0) 14px, rgb(3,230,200) 15px);
117 | }
118 |
119 | .middlepoint {
120 | background:
121 | linear-gradient(135deg, transparent 10px, rgb(3,230,200) 0) top left,
122 | linear-gradient(225deg, transparent 10px, rgb(3,230,200) 0) top right,
123 | linear-gradient(315deg, transparent 10px, rgb(3,230,200) 0) bottom right,
124 | linear-gradient(45deg, transparent 10px, rgb(3,230,200) 0) bottom left;
125 | background-size: 51% 51%;
126 | background-repeat: no-repeat;
127 | background-image:
128 | radial-gradient(circle at 0 0, rgba(3,230,200,0) 14px, rgb(3,230,200) 15px),
129 | radial-gradient(circle at 100% 0, rgba(3,230,200,0) 14px, rgb(3,230,200) 15px),
130 | radial-gradient(circle at 100% 100%, rgba(3,230,200,0) 14px, rgb(3,230,200) 15px),
131 | radial-gradient(circle at 0 100%, rgba(3,230,200,0) 14px, rgb(3,230,200) 15px);
132 | }
133 |
134 | .bottompoint {
135 | border-bottom-left-radius: 10px;
136 | border-bottom-right-radius: 10px;
137 | background:
138 | linear-gradient(135deg, transparent 10px, rgb(3,230,200) 0) top left,
139 | linear-gradient(225deg, transparent 10px, rgb(3,230,200) 0) top right;
140 | background-size: 51% 100%;
141 | background-repeat: no-repeat;
142 | background-image:
143 | radial-gradient(circle at 0 0, rgba(3,230,200,0) 14px, rgb(3,230,200) 15px),
144 | radial-gradient(circle at 100% 0, rgba(3,230,200,0) 14px, rgb(3,230,200) 15px);
145 | margin-bottom: 50px;
146 | }
147 |
148 | /* Item detail */
149 | #item-title {
150 | font-size: 1.2em;
151 | }
152 |
153 | #item-detail {
154 | font-size: 1.2em;
155 | font-weight: bold;
156 | }
157 |
158 | #item-pricetitle {
159 | top: 22px;
160 | font-size: 1.2em;
161 | }
162 |
163 | #item-pointtype {
164 | top: 15px;
165 | font-size: 2em;
166 | }
167 |
168 | #item-price {
169 | font-size: 3.1em;
170 | font-weight: bold;
171 | }
172 |
173 | /* Points detail */
174 | button.btn {
175 | font-size: 1.5em;
176 | overflow: hidden;
177 | }
178 |
179 | button.btn-point {
180 | width: 100%;
181 | }
182 |
183 | button.btn-payable {
184 | color: white;
185 | background-color: rgb(89,87,87);
186 | box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
187 | }
188 |
189 | button.btn-payable:hover {
190 | color: white;
191 | background-color: rgb(50,50,50);
192 | }
193 |
194 | button.btn-unpayable:disabled {
195 | color: rgb(127,243,228);
196 | background-color: rgb(3,230,200);
197 | box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
198 | }
199 |
200 | .points-values {
201 | top: 20px;
202 | font-size: 1.5em;
203 | font-weight: bold;
204 | }
205 |
206 | .points-negative {
207 | color: red;
208 | }
209 |
210 | .paypointer {
211 | display: none;
212 | top: 22px;
213 | }
214 |
215 | #modal-thank {
216 | display: none;
217 | }
218 |
219 | #dest_address {
220 | word-wrap: break-word;
221 | }
222 |
--------------------------------------------------------------------------------
/src/dave/main.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 DG Lab
2 | // Distributed under the MIT software license, see the accompanying
3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 |
5 | // dave project main.go
6 | package main
7 |
8 | import (
9 | "encoding/json"
10 | "fmt"
11 | "log"
12 | "net"
13 | "net/http"
14 | "net/url"
15 | "os"
16 | "path/filepath"
17 | "time"
18 |
19 | "democonf"
20 | "lib"
21 | "rpc"
22 | )
23 |
24 | // URL for accessing RPC
25 | var rpcurl = "http://127.0.0.1:10030"
26 |
27 | // ID for accessing RPC
28 | var rpcuser = "user"
29 |
30 | // Password for accessing RPC
31 | var rpcpass = "pass"
32 |
33 | // Listen addr for RPC Proxy
34 | var laddr = ":8030"
35 |
36 | // getNewAddress use confidential
37 | var confidential = false
38 |
39 | var rpcClient *rpc.Rpc
40 |
41 | // Item details
42 | type Item struct {
43 | Price float64
44 | Asset string
45 | Timeout int64
46 | }
47 |
48 | var items = map[string]Item{
49 | "Caramel Macchiato Coffee": Item{Price: float64(200), Asset: "MELON", Timeout: int64(60 * 60)},
50 | }
51 |
52 | // Order details
53 | type Order struct {
54 | Item string
55 | Addr string
56 | Status int
57 | Asset string
58 | Price float64
59 | Timeout int64
60 | LastModify int64
61 | }
62 |
63 | var list = []*Order{}
64 |
65 | var logger *log.Logger
66 |
67 | func callback() {
68 | newlist := []*Order{}
69 | now := time.Now()
70 | old := now.AddDate(0, 0, -1)
71 | for _, order := range list {
72 | if order.LastModify > old.Unix() {
73 | newlist = append(newlist, order)
74 | }
75 | if order.Status != 0 {
76 | continue
77 | }
78 | if order.Timeout <= now.Unix() {
79 | fmt.Println("Timeout!", order.Addr)
80 | order.Status = -1
81 | order.LastModify = now.Unix()
82 | continue
83 | }
84 | amount, res, err := rpcClient.RequestAndCastNumber("getreceivedbyaddress", order.Addr, 1, order.Asset)
85 | if err != nil {
86 | logger.Printf("Rpc#RequestAndCastNumber error:%v res:%+v", err, res)
87 | continue
88 | }
89 | if amount >= order.Price {
90 | fmt.Println("Paid!", order.Addr)
91 | order.Status = 1
92 | order.LastModify = now.Unix()
93 | }
94 | }
95 | list = newlist
96 | }
97 |
98 | func orderhandler(w http.ResponseWriter, r *http.Request) {
99 | w.Header().Add("Access-Control-Allow-Origin", "*")
100 | w.Header().Add("Access-Control-Allow-Methods", "POST,GET")
101 | w.Header().Add("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
102 | w.Header().Add("Access-Control-Max-Age", "-1")
103 |
104 | item := r.FormValue("item")
105 | result := make(map[string]interface{})
106 | result["result"] = false
107 | for key, val := range items {
108 | if key == item {
109 | addr, err := rpcClient.GetNewAddr(confidential)
110 | if err != nil {
111 | w.WriteHeader(http.StatusInternalServerError)
112 | logger.Println("getNewAddress error", err)
113 | return
114 | }
115 | result["result"] = true
116 | vals := url.Values{}
117 | result["name"] = key
118 | vals["name"] = []string{key}
119 | result["addr"] = addr
120 | vals["addr"] = []string{addr}
121 | result["price"] = val.Price
122 | vals["price"] = []string{fmt.Sprintf("%v", val.Price)}
123 | result["asset"] = val.Asset
124 | vals["asset"] = []string{val.Asset}
125 | uri := fmt.Sprint("px:invoice?", vals.Encode())
126 | fmt.Println(uri)
127 | result["uri"] = uri
128 | now := time.Now().Unix()
129 | order := &Order{Item: key, Addr: addr, Status: 0, Timeout: now + val.Timeout, Price: val.Price, Asset: val.Asset, LastModify: now}
130 | list = append(list, order)
131 | bs, _ := json.Marshal(order)
132 | fmt.Println("Order", string(bs))
133 | break
134 | }
135 | }
136 | if !result["result"].(bool) {
137 | w.WriteHeader(http.StatusNotFound)
138 | result["error"] = fmt.Sprint("Not Found item. ", item)
139 | }
140 | bs, _ := json.Marshal(result)
141 | w.Write(bs)
142 | }
143 |
144 | func listhandler(w http.ResponseWriter, r *http.Request) {
145 | w.Header().Add("Access-Control-Allow-Origin", "*")
146 | w.Header().Add("Access-Control-Allow-Methods", "POST,GET")
147 | w.Header().Add("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
148 | w.Header().Add("Access-Control-Max-Age", "-1")
149 |
150 | res := make(map[string]interface{})
151 | res["result"] = list
152 | bs, _ := json.Marshal(res)
153 | w.Write(bs)
154 | }
155 |
156 | func loadConf() {
157 | conf := democonf.NewDemoConf("dave")
158 | rpcurl = conf.GetString("rpcurl", rpcurl)
159 | rpcuser = conf.GetString("rpcuser", rpcuser)
160 | rpcpass = conf.GetString("rpcpass", rpcpass)
161 | laddr = conf.GetString("laddr", laddr)
162 | confidential = conf.GetBool("confidential", confidential)
163 | }
164 |
165 | func main() {
166 | logger = log.New(os.Stdout, "Dave:", log.LstdFlags+log.Lshortfile)
167 | fmt.Println("Dave starting")
168 |
169 | loadConf()
170 | rpcClient = rpc.NewRpc(rpcurl, rpcuser, rpcpass)
171 |
172 | listener, err := net.Listen("tcp", laddr)
173 | if err != nil {
174 | logger.Println("net#Listen error:", err)
175 | return
176 | }
177 | defer listener.Close()
178 |
179 | mux := http.NewServeMux()
180 | mux.HandleFunc("/order", orderhandler)
181 | mux.HandleFunc("/list", listhandler)
182 | dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
183 | fmt.Println("html path:", http.Dir(dir+"/html/dave"))
184 | mux.Handle("/", http.FileServer(http.Dir(dir+"/html/dave")))
185 | fmt.Println("start listening...", listener.Addr().Network(), listener.Addr())
186 | go http.Serve(listener, mux)
187 |
188 | lib.SetLogger(logger)
189 | lib.StartCyclic(callback, 3, true)
190 |
191 | fmt.Println("Dave stopping")
192 | }
193 |
--------------------------------------------------------------------------------
/src/rpc/helper.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 DG Lab
2 | // Distributed under the MIT software license, see the accompanying
3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 |
5 | // Package rpc Helper functions for the RPC utility
6 | package rpc
7 |
8 | import (
9 | "fmt"
10 | "sort"
11 | "time"
12 | )
13 |
14 | // LockList to keep referenced utxos (potentially to-be-spent)
15 | type LockList map[string]time.Time
16 |
17 | var utxoLockDuration time.Duration
18 |
19 | // SetUtxoLockDuration set utxoLockDuration
20 | func SetUtxoLockDuration(utxoLockDurationIn time.Duration) {
21 | utxoLockDuration = utxoLockDurationIn
22 | }
23 |
24 | func (ul UnspentList) Len() int {
25 | return len(ul)
26 | }
27 |
28 | func (ul UnspentList) Swap(i, j int) {
29 | ul[i], ul[j] = ul[j], ul[i]
30 | }
31 |
32 | func (ul UnspentList) Less(i, j int) bool {
33 | if (*ul[i]).Amount < (*ul[j]).Amount {
34 | return true
35 | }
36 | if (*ul[i]).Amount > (*ul[j]).Amount {
37 | return false
38 | }
39 | return (*ul[i]).Confirmations < (*ul[j]).Confirmations
40 | }
41 |
42 | // GetAmount get total amount.
43 | func (ul UnspentList) GetAmount() int64 {
44 | var totalAmount = int64(0)
45 |
46 | for _, u := range ul {
47 | totalAmount += u.Amount
48 | }
49 |
50 | return totalAmount
51 | }
52 |
53 | func getLockingKey(txid string, vout int64) string {
54 | return fmt.Sprintf("%s:%d", txid, vout)
55 | }
56 |
57 | // Lock lock utxo.
58 | func (ll LockList) Lock(txid string, vout int64) bool {
59 | key := getLockingKey(txid, vout)
60 | now := time.Now()
61 | to := now.Add(utxoLockDuration)
62 |
63 | old, ok := ll[key]
64 | if !ok {
65 | // new lock.
66 | ll[key] = to
67 | return true
68 | }
69 | if old.Sub(now) < 0 {
70 | // exists but no longer locked. lock again.
71 | ll[key] = to
72 | return true
73 | }
74 |
75 | // already locked.
76 | return false
77 | }
78 |
79 | // Unlock unlock utxo.
80 | func (ll LockList) Unlock(txid string, vout int64) {
81 | key := getLockingKey(txid, vout)
82 | delete(ll, key)
83 |
84 | return
85 | }
86 |
87 | // Sweep delete timeout
88 | func (ll LockList) Sweep() {
89 | now := time.Now()
90 | for k, v := range ll {
91 | if v.Sub(now) < 0 {
92 | delete(ll, k)
93 | }
94 | }
95 | }
96 |
97 | // UnlockUnspentList unlock utxos.
98 | func (ll LockList) UnlockUnspentList(ul UnspentList) {
99 | for _, u := range ul {
100 | ll.Unlock(u.Txid, u.Vout)
101 | }
102 | }
103 |
104 | // GetNewAddr get new address, confidential or normal.
105 | func (rpc *Rpc) GetNewAddr(confidential bool) (string, error) {
106 | var validAddr ValidatedAddress
107 |
108 | adr, _, err := rpc.RequestAndCastString("getnewaddress")
109 | if err != nil {
110 | return "", err
111 | }
112 |
113 | if confidential {
114 | return adr, nil
115 | }
116 |
117 | _, err = rpc.RequestAndUnmarshalResult(&validAddr, "validateaddress", adr)
118 | if err != nil {
119 | return "", err
120 | }
121 |
122 | if validAddr.Unconfidential == "" {
123 | return "", fmt.Errorf("unconfidential is empty")
124 | }
125 |
126 | return validAddr.Unconfidential, nil
127 | }
128 |
129 | // GetCommitments : Extract the commitments from a list of UTXOs and return these
130 | // as an array of hex strings.
131 | func (rpc *Rpc) GetCommitments(utxos UnspentList) ([]string, error) {
132 | var commitments = make([]string, len(utxos))
133 |
134 | for i, u := range utxos {
135 | commitments[i] = u.AssetCommitment
136 | }
137 | return commitments, nil
138 | }
139 |
140 | // SearchUnspent search unspent utxo.
141 | func (rpc *Rpc) SearchUnspent(lockList LockList, requestAsset string, requestAmount int64, blinding bool) (UnspentList, error) {
142 | var totalAmount = int64(0)
143 | var ul UnspentList
144 | var utxos = make(UnspentList, 0)
145 |
146 | _, err := rpc.RequestAndUnmarshalResult(&ul, "listunspent", 1, 9999999, []string{}, false, requestAsset)
147 | if err != nil {
148 | return utxos, err
149 | }
150 | sort.Sort(sort.Reverse(ul))
151 |
152 | for _, u := range ul {
153 | if requestAmount < totalAmount {
154 | break
155 | }
156 | if blinding && (u.AssetCommitment == "") {
157 | continue
158 | }
159 | if !blinding && (u.AssetCommitment != "") {
160 | continue
161 | }
162 | if !(u.Spendable || u.Solvable) {
163 | continue
164 | }
165 | if !lockList.Lock(u.Txid, u.Vout) {
166 | continue
167 | }
168 | totalAmount += u.Amount
169 | utxos = append(utxos, u)
170 | }
171 |
172 | if requestAmount >= totalAmount {
173 | lockList.UnlockUnspentList(utxos)
174 | err = fmt.Errorf("no sufficient utxo")
175 | return utxos, err
176 | }
177 |
178 | return utxos, nil
179 | }
180 |
181 | // SearchMinimalUnspent search unspent minimal utxo.
182 | func (rpc *Rpc) SearchMinimalUnspent(lockList LockList, requestAsset string, blinding bool) (UnspentList, error) {
183 | var ul UnspentList
184 | var utxos UnspentList
185 |
186 | _, err := rpc.RequestAndUnmarshalResult(&ul, "listunspent", 1, 9999999, []string{}, false, requestAsset)
187 | if err != nil {
188 | return utxos, err
189 | }
190 |
191 | if ul.Len() == 0 {
192 | err := fmt.Errorf("no utxo [%s]", requestAsset)
193 | return utxos, err
194 | }
195 |
196 | sort.Sort(ul)
197 | var start = 0
198 | var found = false
199 | for i, u := range ul {
200 | if blinding && (u.AssetCommitment == "") {
201 | continue
202 | }
203 | if !blinding && (u.AssetCommitment != "") {
204 | continue
205 | }
206 | if !(u.Spendable || u.Solvable) {
207 | continue
208 | }
209 | if !lockList.Lock(u.Txid, u.Vout) {
210 | continue
211 | }
212 |
213 | start = i
214 | found = true
215 | break
216 | }
217 | if !found {
218 | err := fmt.Errorf("no utxo [%s]", requestAsset)
219 | return utxos, err
220 | }
221 |
222 | minUnspent := ul[start]
223 | if ul.Len() == start+1 {
224 | utxos = append(utxos, minUnspent)
225 | return utxos, nil
226 | }
227 |
228 | for _, u := range ul[start+1:] {
229 | if u.Amount != minUnspent.Amount {
230 | break
231 | }
232 | if blinding && (u.AssetCommitment == "") {
233 | continue
234 | }
235 | if !blinding && (u.AssetCommitment != "") {
236 | continue
237 | }
238 | if !(u.Spendable || u.Solvable) {
239 | continue
240 | }
241 | if !lockList.Lock(u.Txid, u.Vout) {
242 | continue
243 | }
244 |
245 | lockList.Unlock(minUnspent.Txid, minUnspent.Vout)
246 | minUnspent = u
247 | }
248 |
249 | utxos = append(utxos, minUnspent)
250 | return utxos, nil
251 | }
252 |
--------------------------------------------------------------------------------
/src/alice/html/alice/alice.js:
--------------------------------------------------------------------------------
1 |
2 | var wallets = {};
3 |
4 | var payinfo = {};
5 |
6 | function init() {
7 | $("#obtn").click(okpay);
8 | $("#cbtn").click(cancelpay);
9 | $("#modal-thank").click(cancelpay);
10 | $("#addressinput").change(getPayInfo);
11 | $("#qr_scanner").hover(function(){$("#qr_sorry").css('color', '#707172');},function(){$("#qr_sorry").css('color', '#EEEFF0');});
12 | reset();
13 | }
14 |
15 | function reset() {
16 | $("#wallet").hide();
17 | $("#purchaseinfo").hide();
18 | $("#pay").hide();
19 | wallets = {};
20 | $("#addressinput").val("");
21 | payinfo = {};
22 | $("#item-detail").empty();
23 | $("#item-detail").text("-");
24 | $("#item-pointtype").empty();
25 | $("#item-pointtype").text("-");
26 | $("#item-price").empty();
27 | $("#item-price").text("-");
28 | $("#exinfo").empty();
29 | $("[data-key=\"mc\"]").empty();
30 | getWalletInfo();
31 | }
32 |
33 | function getWalletInfo() {
34 | $.getJSON("walletinfo")
35 | .done(function (walletinfo) {
36 | for (let key in walletinfo.balance) {
37 | if (wallets[key]) {
38 | wallets[key].point = walletinfo.balance[key];
39 | } else {
40 | var wallet = {
41 | sname: key,
42 | lname: key + "pt",
43 | point: walletinfo.balance[key]
44 | }
45 | wallets[key] = wallet;
46 | }
47 | }
48 | setWalletInfo();
49 | })
50 | .fail(function (jqXHR, textStatus, errorThrown) {
51 | if (confirm("walletinfo fail\n" + JSON.stringify(jqXHR) + "\n" + textStatus + "\n"
52 | + errorThrown + "\nCannot retrieve wallet info. Do you want to retry?")) {
53 | reset();
54 | }
55 | });
56 | }
57 |
58 | function setWalletInfo() {
59 | $("#walletpoints").empty();
60 | var i = 0;
61 | var j = Object.keys(wallets).length - 1;
62 | for (let key in wallets) {
63 | var additionalClass = "";
64 | switch (i) {
65 | case 0:
66 | additionalClass = "toppoint";
67 | break;
68 | case j:
69 | additionalClass = "bottompoint";
70 | break;
71 | default:
72 | additionalClass = "middlepoint";
73 | }
74 | $("#walletpoints").append(
75 | $("")
76 | .addClass(additionalClass + " row point")
77 | .attr("id", wallets[key].sname)
78 | .append($("")
79 | .addClass("col-md-4 col-md-offset-1 text-center")
80 | .append($("")
81 | .addClass("btn btn-unpayable btn-point")
82 | .prop("disabled", true)
83 | .attr("data-key", wallets[key].sname)
84 | .attr("id", wallets[key].sname+"-btn")
85 | .append($(' '))))
86 | .append($("")
87 | .addClass("col-md-2 text-center points-values")
88 | .attr("id", wallets[key].sname+"-balance")
89 | .text(wallets[key].point))
90 | .append($("")
91 | .addClass("col-md-1 text-center points-values")
92 | .text("→"))
93 | .append($("")
94 | .addClass("col-md-3 text-center points-values")
95 | .attr("id", wallets[key].sname+"-remainder")
96 | .text("-"))
97 | .append($("/")
98 | .addClass("col-md-1 text-center paypointer")
99 | .attr("id", wallets[key].sname+"-pointer")
100 | .text("▶︎"))
101 | );
102 | if (i!=j) {
103 | $("#walletpoints").append($(" ").addClass("row pointseparator"));
104 | }
105 | i++;
106 | }
107 | }
108 |
109 | function getPayInfo() {
110 | //px:invoice?addr=2dcyt9LFshsNYNzPzXAtpzTkCo4kKJKjgG2&asset=ASSET&name=PRODUCTNAME&price=PRICE
111 | let uri = $("#addressinput").val();
112 | let q = uri.split("?");
113 | let errFlg = true;
114 | if (q[0] == "px:invoice") {
115 | payinfo = {};
116 | let as = q[1].split("&");
117 | for (let a of as) {
118 | let kv = a.split("=");
119 | if (kv.length == 2) {
120 | payinfo[kv[0]] = decodeURIComponent(kv[1].replace(/\+/ig,"%20"));
121 | }
122 | }
123 | if (payinfo["name"] && payinfo["addr"] && payinfo["price"] && payinfo["asset"]) {
124 | errFlg = false;
125 | setOrderInfo(payinfo["name"], payinfo["price"], payinfo["asset"]);
126 | $("#info").show();
127 | getExchangeRate(payinfo["asset"], payinfo["price"]);
128 | }
129 | }
130 | if (errFlg) {
131 | payinfo = {};
132 | alert("Incorrect payment info format.\n" + uri);
133 | }
134 | $("#purchaseinfo").fadeIn("slow");
135 | }
136 |
137 | function setOrderInfo(name, price, asset) {
138 | $("#item-detail").empty();
139 | $("#item-detail").text(name);
140 | $("#item-pointtype").empty();
141 | $("#item-pointtype").text(asset);
142 | $("#item-price").empty();
143 | $("#item-price").text(price + " pt");
144 | }
145 |
146 | function getExchangeRate(asset, cost) {
147 | $.getJSON("offer", { asset: "" + asset, cost: "" + cost })
148 | .done(function (offer) {
149 | payinfo["offer"] = offer;
150 | setExchangeRate();
151 | })
152 | .fail(function (jqXHR, textStatus, errorThrown) {
153 | alert("offer fail\n" + JSON.stringify(jqXHR) + "\n" + textStatus + "\n" + errorThrown + "\n");
154 | reset();
155 | });
156 | }
157 |
158 | function setExchangeRate() {
159 | let offer = payinfo["offer"];
160 | if (offer) {
161 | for (var key in offer) {
162 | var total_cost = offer[key].cost + offer[key].fee;
163 | var balance = wallets[key].point;
164 | $("#"+key+"-remainder").empty();
165 | var remainder = balance-total_cost;
166 | $("#"+key+"-remainder").text(total_cost+" ("+remainder+")");
167 | if (total_cost <= balance) {
168 | $("#"+key+"-btn")
169 | .removeClass("btn-unpayable")
170 | .addClass("btn-payable")
171 | .prop("disabled", false)
172 | .click(confirmExchange);
173 | $("#"+key+"-pointer").show();
174 | }
175 | else {
176 | $("#"+key+"-remainder").addClass("points-negative");
177 | }
178 | }
179 | }
180 | }
181 |
182 | function confirmExchange() {
183 | payinfo["exasset"] = $(this).attr("data-key");
184 | $("[data-key=\"mc\"]").empty();
185 | let offer = payinfo["offer"][payinfo["exasset"]];
186 | if (offer) {
187 | $("#dest_address").text(payinfo["addr"]);
188 | $("#bpoint").text(offer["cost"]);
189 | $("#basset").text(payinfo["exasset"]);
190 | $("#apoint").text(payinfo["price"]);
191 | $("#aasset").text(payinfo["asset"]);
192 | $("#fpoint").text(offer["fee"]);
193 | $("#fasset").text(payinfo["exasset"]);
194 | $("#tpoint").text(offer["cost"] + offer["fee"]);
195 | $("#tasset").text(payinfo["exasset"]);
196 | $("#modal-confirm").show();
197 | $("#modal-overlay").fadeIn('slow');
198 | }
199 | }
200 |
201 | function cancelpay() {
202 | $("#modal-overlay").fadeOut('slow');
203 | reset();
204 | }
205 |
206 | function okpay() {
207 | $("#modal-confirm").hide();
208 | let id = payinfo["offer"][payinfo["exasset"]]["id"];
209 | let addr = payinfo["addr"];
210 | if (id && addr) {
211 | $.getJSON("send", { id: "" + id, addr: "" + addr })
212 | .done(function (offer) {
213 | $("#modal-thank").show();
214 | })
215 | .fail(function (jqXHR, textStatus, errorThrown) {
216 | alert("Offer failed\n" + JSON.stringify(jqXHR) + "\n" + textStatus + "\n" + errorThrown + "\n");
217 | reset();
218 | });
219 | } else {
220 | alert("No payinfo:" + id + "," + addr);
221 | reset();
222 | }
223 | }
224 |
225 | $(init)
226 |
--------------------------------------------------------------------------------
/src/lib/http.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 DG Lab
2 | // Distributed under the MIT software license, see the accompanying
3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 |
5 | /*
6 | Package lib provides HTTP related functions.
7 |
8 | There is a very simple http-form/json framework.
9 | And it is also provides cyclic process management.
10 | */
11 | package lib
12 |
13 | import (
14 | "crypto/sha256"
15 | "encoding/binary"
16 | "encoding/json"
17 | "fmt"
18 | "io/ioutil"
19 | "log"
20 | "net"
21 | "net/http"
22 | "net/url"
23 | "reflect"
24 | "runtime"
25 | "strconv"
26 | "strings"
27 | "time"
28 | )
29 |
30 | // ExchangeRateRequest is a structure that represents the JSON-API request.
31 | type ExchangeRateRequest struct {
32 | Request map[string]int64 `json:"request"`
33 | Offer string `json:"offer"`
34 | }
35 |
36 | // ExchangeRateResponse is a structure that represents the JSON-API response.
37 | type ExchangeRateResponse struct {
38 | Fee int64 `json:"fee"`
39 | AssetLabel string `json:"assetid"`
40 | Cost int64 `json:"cost"`
41 | }
42 |
43 | // GetID returns ID of ExchangeRateResponse instance.
44 | func (u *ExchangeRateResponse) GetID() string {
45 | return generateID(fmt.Sprintf("%d:%s:%d", u.Fee, u.AssetLabel, u.Cost))
46 | }
47 |
48 | func generateID(key string) string {
49 | now := time.Now().Unix()
50 | nowba := make([]byte, binary.MaxVarintLen64)
51 | binary.PutVarint(nowba, now)
52 | target := append([]byte(key), nowba...)
53 | hash := sha256.Sum256(target)
54 | id := fmt.Sprintf("%x", hash)
55 | return id
56 | }
57 |
58 | // ExchangeOfferRequest is a structure that represents the JSON-API request.
59 | type ExchangeOfferRequest struct {
60 | Request map[string]int64 `json:"request"`
61 | Offer string `json:"offer"`
62 | }
63 |
64 | // ExchangeOfferResponse is a structure that represents the JSON-API response.
65 | type ExchangeOfferResponse struct {
66 | Fee int64 `json:"fee"`
67 | AssetLabel string `json:"assetid"`
68 | Cost int64 `json:"cost"`
69 | Transaction string `json:"tx"`
70 | }
71 |
72 | // GetID returns ID of ExchangeOfferResponse instance.
73 | func (u *ExchangeOfferResponse) GetID() string {
74 | return generateID(u.Transaction)
75 | }
76 |
77 | // ExchangeOfferWBRequest is a structure that represents the JSON-API request.
78 | type ExchangeOfferWBRequest struct {
79 | Request map[string]int64 `json:"request"`
80 | Offer string `json:"offer"`
81 | Commitments []string `json:"commitments"`
82 | }
83 |
84 | // ExchangeOfferWBResponse is a structure that represents the JSON-API response.
85 | type ExchangeOfferWBResponse struct {
86 | Fee int64 `json:"fee"`
87 | AssetLabel string `json:"assetid"`
88 | Cost int64 `json:"cost"`
89 | Transaction string `json:"tx"`
90 | Commitments []string `json:"commitments"`
91 | }
92 |
93 | // GetID returns ID of ExchangeOfferWBResponse instance.
94 | func (u *ExchangeOfferWBResponse) GetID() string {
95 | return generateID(u.Transaction)
96 | }
97 |
98 | // SubmitExchangeRequest is a structure that represents the JSON-API request.
99 | type SubmitExchangeRequest struct {
100 | Transaction string `json:"tx"`
101 | }
102 |
103 | // SubmitExchangeResponse is a structure that represents the JSON-API response.
104 | type SubmitExchangeResponse struct {
105 | TransactionID string `json:"txid"`
106 | }
107 |
108 | // ErrorResponse is a structure that represents the JSON-API response.
109 | type ErrorResponse struct {
110 | Result bool `json:"result"`
111 | Message string `json:"message"`
112 | }
113 |
114 | var logger *log.Logger
115 |
116 | // SetLogger sets logger.
117 | func SetLogger(loggerIn *log.Logger) {
118 | logger = loggerIn
119 | }
120 |
121 | func createErrorByteArray(e error) []byte {
122 | if e == nil {
123 | e = fmt.Errorf("error occured (fake)")
124 | }
125 | res := ErrorResponse{
126 | Result: false,
127 | Message: fmt.Sprintf("%s", e),
128 | }
129 | b, err := json.Marshal(res)
130 | if err != nil {
131 | logger.Println("error:", err)
132 | }
133 | return b
134 | }
135 |
136 | func handler(w http.ResponseWriter, r *http.Request, fi interface{}, n string) {
137 |
138 | w.Header().Add("Access-Control-Allow-Origin", "*")
139 | w.Header().Add("Access-Control-Allow-Methods", "GET")
140 | w.Header().Add("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
141 | w.Header().Add("Access-Control-Max-Age", "-1")
142 |
143 | status := http.StatusOK
144 | createParam := formToFlatStruct
145 | var res interface{}
146 | var err error
147 | var fp0e reflect.Value
148 |
149 | defer func() {
150 | e := r.Body.Close()
151 | if e != nil {
152 | logger.Println("error:", e)
153 | }
154 | }()
155 |
156 | switch r.Method {
157 | case "GET":
158 | case "POST":
159 | ct := strings.Split(""+r.Header.Get("Content-Type"), ";")[0]
160 | switch ct {
161 | case "application/x-www-form-urlencoded":
162 | case "application/json", "text/plain":
163 | createParam = jsonToStruct
164 | default:
165 | status = http.StatusBadRequest
166 | err = fmt.Errorf("content-type not allowed:%s", ct)
167 | logger.Println("error:", err)
168 | handleTermninate(w, res, status, err)
169 | return
170 | }
171 | default:
172 | status = http.StatusMethodNotAllowed
173 | err = fmt.Errorf("method not allowed:%s", r.Method)
174 | logger.Println("error:", err)
175 | handleTermninate(w, res, status, err)
176 | return
177 | }
178 |
179 | fp0e, err = createParam(r, fi)
180 | if err != nil {
181 | status = http.StatusInternalServerError
182 | logger.Println("error:", err)
183 | handleTermninate(w, res, status, err)
184 | return
185 | }
186 |
187 | fv := reflect.ValueOf(fi)
188 | logger.Println("start:", n)
189 | result := fv.Call([]reflect.Value{fp0e})
190 | logger.Println("end:", n)
191 |
192 | if err, ok := result[1].Interface().(error); ok {
193 | status = http.StatusInternalServerError
194 | logger.Println("error:", err)
195 | handleTermninate(w, res, status, err)
196 | }
197 |
198 | handleTermninate(w, result[0].Interface(), status, nil)
199 |
200 | return
201 | }
202 |
203 | func formToFlatStruct(r *http.Request, fi interface{}) (reflect.Value, error) {
204 | var formValue reflect.Value
205 |
206 | err := r.ParseForm()
207 | if err != nil {
208 | logger.Println("http.Request#ParseForm error:", err)
209 | return formValue, err
210 | }
211 |
212 | fv := reflect.ValueOf(fi)
213 | fp0t := fv.Type().In(0)
214 | fp0v := reflect.New(fp0t)
215 | formValue = fp0v.Elem()
216 |
217 | for i := 0; i < fp0t.NumField(); i++ {
218 | sfv := formValue.Field(i)
219 |
220 | if !sfv.CanSet() {
221 | continue
222 | }
223 |
224 | paramv := getParamByNameIgnoreCase(r.Form, fp0t.Field(i).Name)
225 | switch sfv.Kind() {
226 | case reflect.String:
227 | sfv.SetString(paramv)
228 | case reflect.Int64:
229 | val, err := strconv.ParseInt(paramv, 10, 64)
230 | if err != nil {
231 | break
232 | }
233 | sfv.SetInt(val)
234 | default:
235 | }
236 | }
237 |
238 | return formValue, nil
239 | }
240 |
241 | func getParamByNameIgnoreCase(form url.Values, key string) string {
242 | lkey := strings.ToLower(key)
243 | var value string
244 | for k, v := range form {
245 | if strings.ToLower(k) == lkey {
246 | value = v[0]
247 | }
248 | }
249 | return value
250 | }
251 |
252 | func jsonToStruct(r *http.Request, fi interface{}) (reflect.Value, error) {
253 | var fp0e reflect.Value
254 |
255 | reqBody, err := ioutil.ReadAll(r.Body)
256 | if err != nil {
257 | logger.Println("ioutil#ReadAll error:", err)
258 | return fp0e, err
259 | }
260 |
261 | fv := reflect.ValueOf(fi)
262 | fp0v := reflect.New(fv.Type().In(0))
263 | fp0i := fp0v.Interface()
264 |
265 | err = json.Unmarshal(reqBody, fp0i)
266 | if err != nil {
267 | logger.Println("json#Unmarshal error:", err)
268 | return fp0e, err
269 | }
270 |
271 | return fp0v.Elem(), nil
272 | }
273 |
274 | func handleTermninate(w http.ResponseWriter, resif interface{}, status int, err error) {
275 | if resif == nil && err != nil {
276 | resif = err
277 | }
278 |
279 | res, err := json.Marshal(resif)
280 | if err != nil {
281 | status = http.StatusInternalServerError
282 | logger.Println("json#Marshal error:", err)
283 | res = createErrorByteArray(err)
284 | }
285 |
286 | w.WriteHeader(status)
287 | _, err = w.Write(res)
288 | if err != nil {
289 | logger.Println("w#Write Error:", err)
290 | return
291 | }
292 | }
293 |
294 | func generateMuxHandler(h interface{}) func(http.ResponseWriter, *http.Request) {
295 | fv := reflect.ValueOf(h)
296 | n := runtime.FuncForPC(fv.Pointer()).Name()
297 | return func(w http.ResponseWriter, r *http.Request) {
298 | handler(w, r, h, n)
299 | return
300 | }
301 | }
302 |
303 | // StartHTTPServer binds specific URL and handler function. And it starts http server.
304 | func StartHTTPServer(laddr string, handlers map[string]interface{}, filepath string) (net.Listener, error) {
305 | listener, err := net.Listen("tcp", laddr)
306 | if err != nil {
307 | return listener, err
308 | }
309 |
310 | mux := http.NewServeMux()
311 | for p, h := range handlers {
312 | hv := reflect.ValueOf(h)
313 | if !hv.IsValid() {
314 | return listener, fmt.Errorf("handler is invalid")
315 | }
316 | if hv.Kind() != reflect.Func {
317 | return listener, fmt.Errorf("each handler must be a function")
318 | }
319 | funcname := runtime.FuncForPC(hv.Pointer()).Name()
320 | ht := hv.Type()
321 | if ht.NumIn() != 1 {
322 | return listener, fmt.Errorf("[%s] must have one input. but it has %d", funcname, ht.NumIn())
323 | }
324 | hi0t := ht.In(0)
325 | if hi0t.Kind() != reflect.Struct {
326 | return listener, fmt.Errorf("[%s] input must be a struct", funcname)
327 | }
328 | if ht.NumOut() != 2 {
329 | return listener, fmt.Errorf("[%s] must have two output. but it has %d", funcname, ht.NumOut())
330 | }
331 | ho0t := ht.Out(0)
332 | if (ho0t.Kind() != reflect.Struct) && (ho0t.Kind() != reflect.Map) {
333 | return listener, fmt.Errorf("[%s] 1st output must be a struct or a map", funcname)
334 | }
335 | ho1t := ht.Out(1)
336 | if ho1t.Kind() != reflect.Interface {
337 | return listener, fmt.Errorf("[%s] 2nd output must be a interface", funcname)
338 | }
339 | errorType := reflect.TypeOf((*error)(nil)).Elem()
340 | if !ho1t.Implements(errorType) {
341 | return listener, fmt.Errorf("[%s] 2nd output must implements error", funcname)
342 | }
343 |
344 | f := generateMuxHandler(h)
345 | mux.HandleFunc(p, f)
346 | }
347 |
348 | mux.Handle("/", http.FileServer(http.Dir(filepath)))
349 | go func() {
350 | e := http.Serve(listener, mux)
351 | if e != nil {
352 | logger.Println("error:", e)
353 | }
354 | }()
355 |
356 | return listener, err
357 | }
358 |
--------------------------------------------------------------------------------
/src/rpc/rpc.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 DG Lab
2 | // Distributed under the MIT software license, see the accompanying
3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 |
5 | // Package rpc is a simple client
6 | package rpc
7 |
8 | import (
9 | "bytes"
10 | "encoding/json"
11 | "fmt"
12 | "io/ioutil"
13 | "net/http"
14 | "time"
15 | )
16 |
17 | // ValidatedAddress contains address details.
18 | type ValidatedAddress struct {
19 | IsValid bool `json:"isvalid"` // : true|false, (boolean) If the address is valid or not. If not, this is the only property returned.
20 | Address string `json:"address"` // : "bitcoinaddress", (string) The bitcoin address validated
21 | ScriptPubKey string `json:"scriptPubKey"` // : "hex", (string) The hex encoded scriptPubKey generated by the address
22 | IsMine bool `json:"ismine"` // : true|false, (boolean) If the address is yours or not
23 | IsWatchonly bool `json:"iswatchonly"` // : true|false, (boolean) If the address is watchonly
24 | IsScript bool `json:"isscript"` // : true|false, (boolean) If the key is a script
25 | PubKey string `json:"pubkey"` // : "publickeyhex", (string) The hex value of the raw public key
26 | IsCompressed bool `json:"iscompressed"` // : true|false, (boolean) If the address is compressed
27 | Account string `json:"account"` // : "account" (string) DEPRECATED. The account associated with the address, "" is the default account
28 | ConfidentialKey string `json:"confidential_key"` // : "pubkey" (string) The confidentiality key associated with the address, or "" if none
29 | Unconfidential string `json:"unconfidential"` // : "address" (string) The address without confidentiality key
30 | Confidential string `json:"confidential"` // : "address" (string) Confidential version of the address, only if it is yours and unconfidential
31 | Hdkeypath string `json:"hdkeypath"` // : "keypath" (string, optional) The HD keypath if the key is HD and available
32 | Hdmasterkeyid string `json:"hdmasterkeyid"` // : " " (string, optional) The Hash160 of the HD master pubkey
33 | }
34 |
35 | // Unspent contains unspent details.
36 | type Unspent struct {
37 | Txid string `json:"txid"` // "txid" : "txid", (string) the transaction id
38 | Vout int64 `json:"vout"` // "vout" : n, (numeric) the vout value
39 | Address string `json:"address"` // "address" : "address", (string) the bitcoin address
40 | Account string `json:"account"` // "account" : "account", (string) DEPRECATED. The associated account, or "" for the default account
41 | ScriptPubKey string `json:"scriptPubKey"` // "scriptPubKey" : "key", (string) the script key
42 | Amount int64 `json:"amount"` // "amount" : x.xxx, (numeric) the transaction amount in BTC
43 | Asset string `json:"asset"` // "asset" : "hex" (string) the asset id for this output
44 | AssetCommitment string `json:"assetcommitment"` // "assetcommitment" : "hex" (string) the asset commitment for this output
45 | Confirmations int64 `json:"confirmations"` // "confirmations" : n, (numeric) The number of confirmations
46 | SerValue string `json:"serValue"` // "serValue" : "hex", (string) the output's value commitment
47 | Blinder string `json:"blinder"` // "blinder" : "blind" (string) The blinding factor used for a confidential output (or "")
48 | RedeemScript string `json:"redeemScript"` // "redeemScript" : n (string) The redeemScript if scriptPubKey is P2SH
49 | Spendable bool `json:"spendable"` // "spendable" : xxx, (bool) Whether we have the private keys to spend this output
50 | Solvable bool `json:"solvable"` // "solvable" : xxx (bool) Whether we know how to spend this output, ignoring the lack of keys
51 | }
52 |
53 | // UnspentList is an array of unspent outputs.
54 | type UnspentList []*Unspent
55 |
56 | // BalanceMap is a map where the key is an assetid and the value is a balance.
57 | type BalanceMap map[string]float64
58 |
59 | // Wallet is wallet details.
60 | type Wallet struct {
61 | WalletVersion int64 `json:"walletversion"` // : xxxxx, (numeric) the wallet version
62 | Balance BalanceMap `json:"balance"` // : xxxxxxx, (numeric) the total confirmed balance of the wallet in BTC
63 | UnconfirmedBalance BalanceMap `json:"unconfirmed_balance"` // : xxx, (numeric) the total unconfirmed balance of the wallet in BTC
64 | ImmatureBalance BalanceMap `json:"immature_balance"` // : xxxxxx, (numeric) the total immature balance of the wallet in BTC
65 | TxCount int64 `json:"txcount"` // : xxxxxxx, (numeric) the total number of transactions in the wallet
66 | KeypoolOldest float64 `json:"keypoololdest"` // : xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool
67 | KeypoolSize int64 `json:"keypoolsize"` // : xxxx, (numeric) how many new keys are pre-generated
68 | UnlockedUntil int64 `json:"unlocked_until"` // : ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked
69 | PayTxFee int64 `json:"paytxfee"` // : x.xxxx, (numeric) the transaction fee configuration, set in BTC/kB
70 | HDMasterKeyId string `json:"hdmasterkeyid"` // : "", (string) the Hash160 of the HD master pubkey
71 | }
72 |
73 | // ScriptSig is script details.
74 | type ScriptSig struct {
75 | Asm string `json:"asm"`
76 | Hex string `json:"hex"`
77 | }
78 |
79 | // ScriptPubKey is pubkey details.
80 | type ScriptPubKey struct {
81 | Asm string `json:"asm"`
82 | Hex string `json:"hex"`
83 | ReqSigs int64 `json:"reqSigs"`
84 | Type string `json:"type"`
85 | Addresses []string `json:"addresses"`
86 | }
87 |
88 | // Vin is input details.
89 | type Vin struct {
90 | Txid string `json:"txid"`
91 | Vout int64 `json:"vout"`
92 | ScriptSig ScriptSig `json:"scriptSig"`
93 | txinwitness string `json:"txinwitness"`
94 | sequence int64 `json:"sequence"`
95 | }
96 |
97 | // Vout is output details.
98 | type Vout struct {
99 | Value float64 `json:"value"`
100 | N int64 `json:"n"`
101 | Asset string `json:"asset"`
102 | Assettag string `json:"assettag"`
103 | ScriptPubKey ScriptPubKey `json:"scriptPubKey"`
104 | }
105 |
106 | // RawTransaction is transaction details.
107 | type RawTransaction struct {
108 | Txid string `json:"txid"`
109 | Hash string `json:"hash"`
110 | Size int64 `json:"size"`
111 | Vsize int64 `json:"vsize"`
112 | Version int64 `json:"version"`
113 | LockTime int64 `json:"locktime"`
114 | Fee float64 `json:"fee"`
115 | Vin []Vin `json:"vin"`
116 | Vout []Vout `json:"vout"`
117 | }
118 |
119 | // SignedTransaction is transaction details.
120 | type SignedTransaction struct {
121 | Hex string `json:"hex"`
122 | Complete bool `json:"complete"`
123 | }
124 |
125 | // Rpc is request info.
126 | type Rpc struct {
127 | Url string
128 | User string
129 | Pass string
130 | View bool
131 | }
132 |
133 | // RpcRequest is request parameters.
134 | type RpcRequest struct {
135 | Jsonrpc string `json:"jsonrpc,"`
136 | Id string `json:"id,"`
137 | Method string `json:"method,"`
138 | Params []interface{} `json:"params,"`
139 | }
140 |
141 | // RpcResponse is response details.
142 | type RpcResponse struct {
143 | Result interface{} `json:"result,"`
144 | Error interface{} `json:"error,"`
145 | Id string `json:"id,"`
146 | }
147 |
148 | // RpcError is error details.
149 | type RpcError struct {
150 | Code int `json:"code"`
151 | Message string `json:"message"`
152 | }
153 |
154 | // UnmarshalError converts the error response into an RpcError type
155 | func (res *RpcResponse) UnmarshalError() (RpcError, error) {
156 | var rerr RpcError
157 | if res.Error == nil {
158 | return rerr, fmt.Errorf("RpcResponse Error is nil.")
159 | }
160 | data, ok := res.Error.(map[string]interface{})
161 | if !ok {
162 | return rerr, fmt.Errorf("RpcResponse Error is not map[string]interface{}")
163 | }
164 | bs, _ := json.Marshal(data)
165 | json.Unmarshal(bs, &rerr)
166 | return rerr, nil
167 | }
168 |
169 | // UnmarshalResult converts the response into an result type
170 | func (res *RpcResponse) UnmarshalResult(result interface{}) error {
171 | if res.Result == nil {
172 | return fmt.Errorf("RpcResponse Result is nil.")
173 | }
174 | var bs []byte
175 | m, ok := res.Result.(map[string]interface{})
176 | if !ok {
177 | arr, ok := res.Result.([]interface{})
178 | if !ok {
179 | return fmt.Errorf("RpcResponse Result is neither map[string]interface{} nor []interface{}")
180 | }
181 | bs, _ = json.Marshal(arr)
182 | } else {
183 | bs, _ = json.Marshal(m)
184 | }
185 | err := json.Unmarshal(bs, result)
186 | if err != nil {
187 | return err
188 | }
189 | return nil
190 | }
191 |
192 | // NewRpc return new Rpc
193 | func NewRpc(url, user, pass string) *Rpc {
194 | rpc := new(Rpc)
195 | rpc.Url = url
196 | rpc.User = user
197 | rpc.Pass = pass
198 | return rpc
199 | }
200 |
201 | // Request request server
202 | func (rpc *Rpc) Request(method string, params ...interface{}) (RpcResponse, error) {
203 | var res RpcResponse
204 | if len(params) == 0 {
205 | params = []interface{}{}
206 | }
207 | id := fmt.Sprintf("%d", time.Now().Unix())
208 | req := &RpcRequest{"1.0", id, method, params}
209 | bs, _ := json.Marshal(req)
210 | if rpc.View {
211 | fmt.Printf("%s\n", bs)
212 | }
213 | client := &http.Client{}
214 | hreq, _ := http.NewRequest("POST", rpc.Url, bytes.NewBuffer(bs))
215 | hreq.SetBasicAuth(rpc.User, rpc.Pass)
216 | hres, err := client.Do(hreq)
217 | if err != nil {
218 | return res, err
219 | }
220 | defer hres.Body.Close()
221 | body, _ := ioutil.ReadAll(hres.Body)
222 | if rpc.View {
223 | fmt.Printf("%d, %s\n", hres.StatusCode, body)
224 | }
225 | err = json.Unmarshal(body, &res)
226 | if err != nil || hres.StatusCode != http.StatusOK || res.Id != id {
227 | return res, fmt.Errorf("status:%v, error:%v, body:%s reqid:%v, resid:%v", hres.Status, err, body, id, res.Id)
228 | }
229 | return res, nil
230 | }
231 |
232 | // RequestAndUnmarshalResult do Request and UnmarshalResult
233 | func (rpc *Rpc) RequestAndUnmarshalResult(result interface{}, method string, params ...interface{}) (RpcResponse, error) {
234 | res, err := rpc.Request(method, params...)
235 | if err != nil {
236 | return res, err
237 | }
238 | err = res.UnmarshalResult(result)
239 | if err != nil {
240 | return res, err
241 | }
242 | return res, nil
243 | }
244 |
245 | // RequestAndCastNumber do Request and cast float64
246 | func (rpc *Rpc) RequestAndCastNumber(method string, params ...interface{}) (float64, RpcResponse, error) {
247 | var num float64
248 | res, err := rpc.Request(method, params...)
249 | if err != nil {
250 | return num, res, err
251 | }
252 | num, ok := res.Result.(float64)
253 | if !ok {
254 | return num, res, fmt.Errorf("RpcResponse Result cast error:%+v", res.Result)
255 | }
256 | return num, res, nil
257 | }
258 |
259 | // RequestAndCastString do Request and cast string
260 | func (rpc *Rpc) RequestAndCastString(method string, params ...interface{}) (string, RpcResponse, error) {
261 | var str string
262 | res, err := rpc.Request(method, params...)
263 | if err != nil {
264 | return str, res, err
265 | }
266 | str, ok := res.Result.(string)
267 | if !ok {
268 | return str, res, fmt.Errorf("RpcResponse Result cast error:%+v", res.Result)
269 | }
270 | return str, res, nil
271 | }
272 |
273 | // RequestAndCastBool do Request and cast bool
274 | func (rpc *Rpc) RequestAndCastBool(method string, params ...interface{}) (bool, RpcResponse, error) {
275 | var b bool
276 | res, err := rpc.Request(method, params...)
277 | if err != nil {
278 | return b, res, err
279 | }
280 | b, ok := res.Result.(bool)
281 | if !ok {
282 | return b, res, fmt.Errorf("RpcResponse Result cast error:%+v", res.Result)
283 | }
284 | return b, res, nil
285 | }
286 |
--------------------------------------------------------------------------------
/src/charlie/main.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 DG Lab
2 | // Distributed under the MIT software license, see the accompanying
3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 |
5 | package main
6 |
7 | import (
8 | "democonf"
9 | "fmt"
10 | "lib"
11 | "log"
12 | "os"
13 | "os/exec"
14 | "rpc"
15 | "strconv"
16 | "strings"
17 | "time"
18 | )
19 |
20 | type exchangeRateTuple struct {
21 | Rate float64 `json:"rate"`
22 | Min int64 `json:"min"`
23 | Max int64 `json:"max"`
24 | Unit int64 `json:"unit"`
25 | Fee int64 `json:"fee,"`
26 | }
27 |
28 | const (
29 | myActorName = "charlie"
30 | defaultRateFrom = "AKISKY"
31 | defaultRateTo = "MELON"
32 | defaultRPCURL = "http://127.0.0.1:10020"
33 | defaultRPCUser = "user"
34 | defaultRPCPass = "pass"
35 | defaultLocalAddr = ":8020"
36 | defaultTxPath = "elements-tx"
37 | defaultTxOption = ""
38 | defaultTimeout = 600
39 | )
40 |
41 | var logger = log.New(os.Stdout, myActorName+":", log.LstdFlags+log.Lshortfile)
42 | var conf = democonf.NewDemoConf(myActorName)
43 | var assetIDMap = make(map[string]string)
44 | var lockList = make(rpc.LockList)
45 | var rpcClient *rpc.Rpc
46 | var elementsTxCommand string
47 | var elementsTxOption string
48 | var localAddr string
49 | var defaultRateTuple = exchangeRateTuple{Rate: 0.5, Min: 100, Max: 200000, Unit: 20, Fee: 15}
50 | var fixedRateTable = make(map[string](map[string]exchangeRateTuple))
51 |
52 | var handlerList = map[string]interface{}{
53 | "/getexchangerate/": doGetRate,
54 | "/getexchangeofferwb/": doOfferWithBlinding,
55 | "/getexchangeoffer/": doOffer,
56 | "/submitexchange/": doSubmit,
57 | }
58 |
59 | func doGetRate(rateRequest lib.ExchangeRateRequest) (lib.ExchangeRateResponse, error) {
60 | var rateRes lib.ExchangeRateResponse
61 | var requestAsset string
62 | var requestAmount int64
63 | var err error
64 |
65 | request := rateRequest.Request
66 | if len(request) != 1 {
67 | err = fmt.Errorf("request must be a single record but has:%d", len(request))
68 | logger.Println("error:", err)
69 | return rateRes, err
70 | }
71 | for k, v := range request {
72 | requestAsset = k
73 | requestAmount = v
74 | }
75 |
76 | // 1. lookup config
77 | rateRes, err = lookupRate(requestAsset, requestAmount, rateRequest.Offer)
78 | if err != nil {
79 | logger.Println("error:", err)
80 | }
81 |
82 | return rateRes, err
83 | }
84 |
85 | func doOfferWithBlinding(offerRequest lib.ExchangeOfferWBRequest) (lib.ExchangeOfferWBResponse, error) {
86 | var offerWBRes lib.ExchangeOfferWBResponse
87 | var requestAsset string
88 | var requestAmount int64
89 | var err error
90 |
91 | request := offerRequest.Request
92 | if len(request) != 1 {
93 | err = fmt.Errorf("request must be a single record but has:%d", len(request))
94 | logger.Println("error:", err)
95 | return offerWBRes, err
96 | }
97 | for k, v := range request {
98 | requestAsset = k
99 | requestAmount = v
100 | }
101 |
102 | offer := offerRequest.Offer
103 | commitments := offerRequest.Commitments
104 |
105 | // 1. lookup rate
106 | tmp, err := lookupRate(requestAsset, requestAmount, offer)
107 | if err != nil {
108 | logger.Println("error:", err)
109 | return offerWBRes, err
110 | }
111 |
112 | offerWBRes.Fee = tmp.Fee
113 | offerWBRes.AssetLabel = tmp.AssetLabel
114 | offerWBRes.Cost = tmp.Cost
115 |
116 | // 2. lookup unspent
117 | utxos, err := rpcClient.SearchUnspent(lockList, requestAsset, requestAmount, true)
118 | if err != nil {
119 | logger.Println("error:", err)
120 | return offerWBRes, err
121 | }
122 | rautxos, err := rpcClient.SearchMinimalUnspent(lockList, offer, true)
123 | if err != nil {
124 | logger.Println("error:", err)
125 | return offerWBRes, err
126 | }
127 |
128 | // 3. creat tx
129 | tx, err := createTransactionTemplateWB(requestAsset, requestAmount, offer, offerWBRes, utxos, rautxos)
130 | if err != nil {
131 | logger.Println("error:", err)
132 | return offerWBRes, err
133 | }
134 |
135 | // 4. blinding
136 | cmutxos := append(utxos, rautxos...)
137 | resCommitments, err := rpcClient.GetCommitments(cmutxos)
138 | if err != nil {
139 | logger.Println("error:", err)
140 | return offerWBRes, err
141 | }
142 | commitments = append(resCommitments, commitments...)
143 |
144 | blindtx, _, err := rpcClient.RequestAndCastString("blindrawtransaction", tx, true, commitments)
145 | if err != nil {
146 | logger.Println("RPC/blindrawtransaction error:", err, tx)
147 | return offerWBRes, err
148 | }
149 |
150 | offerWBRes.Transaction = blindtx
151 | offerWBRes.Commitments = resCommitments
152 |
153 | return offerWBRes, nil
154 | }
155 |
156 | func doOffer(offerRequest lib.ExchangeOfferRequest) (lib.ExchangeOfferResponse, error) {
157 | var offerRes lib.ExchangeOfferResponse
158 | var requestAsset string
159 | var requestAmount int64
160 | var err error
161 |
162 | request := offerRequest.Request
163 | if len(request) != 1 {
164 | err = fmt.Errorf("request must be a single record but has:%d", len(request))
165 | logger.Println("error:", err)
166 | return offerRes, err
167 | }
168 | for k, v := range request {
169 | requestAsset = k
170 | requestAmount = v
171 | }
172 |
173 | offer := offerRequest.Offer
174 |
175 | // 1. lookup config
176 | tmp, err := lookupRate(requestAsset, requestAmount, offer)
177 | if err != nil {
178 | logger.Println("error:", err)
179 | return offerRes, err
180 | }
181 |
182 | offerRes.Fee = tmp.Fee
183 | offerRes.AssetLabel = tmp.AssetLabel
184 | offerRes.Cost = tmp.Cost
185 |
186 | // 2. lookup unspent
187 | utxos, err := rpcClient.SearchUnspent(lockList, requestAsset, requestAmount, false)
188 | if err != nil {
189 | logger.Println("error:", err)
190 | return offerRes, err
191 | }
192 |
193 | // 3. creat tx
194 | offerRes.Transaction, err = createTransactionTemplate(requestAsset, requestAmount, offer, offerRes.Cost, utxos)
195 | if err != nil {
196 | logger.Println("error:", err)
197 | }
198 |
199 | return offerRes, err
200 | }
201 |
202 | func createTransactionTemplate(requestAsset string, requestAmount int64, offer string, cost int64, utxos rpc.UnspentList) (string, error) {
203 | var addrOffer string
204 | var addrChange string
205 | var err error
206 |
207 | change := utxos.GetAmount() - requestAmount
208 |
209 | addrOffer, err = rpcClient.GetNewAddr(false)
210 | if err != nil {
211 | return "", err
212 | }
213 |
214 | params := []string{}
215 |
216 | if elementsTxOption != "" {
217 | params = append(params, elementsTxOption)
218 | }
219 | params = append(params, "-create")
220 |
221 | for _, u := range utxos {
222 | txin := "in=" + u.Txid + ":" + strconv.FormatInt(u.Vout, 10)
223 | params = append(params, txin)
224 | }
225 |
226 | outAddrOffer := "outaddr=" + strconv.FormatInt(cost, 10) + ":" + addrOffer + ":" + assetIDMap[offer]
227 | params = append(params, outAddrOffer)
228 |
229 | if 0 < change {
230 | addrChange, err = rpcClient.GetNewAddr(false)
231 | if err != nil {
232 | return "", err
233 | }
234 | outAddrChange := "outaddr=" + strconv.FormatInt(change, 10) + ":" + addrChange + ":" + assetIDMap[requestAsset]
235 | params = append(params, outAddrChange)
236 | }
237 |
238 | out, err := exec.Command(elementsTxCommand, params...).Output()
239 |
240 | if err != nil {
241 | logger.Println("elements-tx error:", err, params, out)
242 | return "", err
243 | }
244 |
245 | txTemplate := strings.TrimRight(string(out), "\n")
246 | return txTemplate, nil
247 | }
248 |
249 | func createTransactionTemplateWB(requestAsset string, requestAmount int64, offer string, offerRes lib.ExchangeOfferWBResponse, utxos rpc.UnspentList, loopbackUtxos rpc.UnspentList) (string, error) {
250 | var addrOffer string
251 | var addrChange string
252 | var err error
253 |
254 | change := utxos.GetAmount() - requestAmount
255 | lbChange := loopbackUtxos.GetAmount() + offerRes.Cost
256 |
257 | addrOffer, err = rpcClient.GetNewAddr(true)
258 | if err != nil {
259 | return "", err
260 | }
261 |
262 | params := []string{}
263 |
264 | if elementsTxOption != "" {
265 | params = append(params, elementsTxOption)
266 | }
267 | params = append(params, "-create")
268 |
269 | for _, u := range utxos {
270 | txin := "in=" + u.Txid + ":" + strconv.FormatInt(u.Vout, 10)
271 | params = append(params, txin)
272 | }
273 | for _, u := range loopbackUtxos {
274 | txin := "in=" + u.Txid + ":" + strconv.FormatInt(u.Vout, 10)
275 | params = append(params, txin)
276 | }
277 |
278 | outAddrOffer := "outaddr=" + strconv.FormatInt(lbChange, 10) + ":" + addrOffer + ":" + assetIDMap[offer]
279 | params = append(params, outAddrOffer)
280 |
281 | if 0 < change {
282 | addrChange, err = rpcClient.GetNewAddr(true)
283 | if err != nil {
284 | return "", err
285 | }
286 | outAddrChange := "outaddr=" + strconv.FormatInt(change, 10) + ":" + addrChange + ":" + assetIDMap[requestAsset]
287 | params = append(params, outAddrChange)
288 | }
289 | outAddrFee := "outscript=" + strconv.FormatInt(offerRes.Fee, 10) + "::" + assetIDMap[offer]
290 | params = append(params, outAddrFee)
291 |
292 | out, err := exec.Command(elementsTxCommand, params...).Output()
293 |
294 | if err != nil {
295 | logger.Println("elements-tx error:", err, params, out)
296 | return "", err
297 | }
298 |
299 | txTemplate := strings.TrimRight(string(out), "\n")
300 | return txTemplate, nil
301 | }
302 |
303 | func doSubmit(submitRequest lib.SubmitExchangeRequest) (lib.SubmitExchangeResponse, error) {
304 | var submitRes lib.SubmitExchangeResponse
305 | var rawTx rpc.RawTransaction
306 | var signedtx rpc.SignedTransaction
307 | var err error
308 |
309 | rcvtx := submitRequest.Transaction
310 |
311 | _, err = rpcClient.RequestAndUnmarshalResult(&rawTx, "decoderawtransaction", rcvtx)
312 | if err != nil {
313 | logger.Println("RPC/decoderawtransaction error:", err, rcvtx)
314 | return submitRes, err
315 | }
316 |
317 | // TODO check rawTx (consistency with offer etc...)
318 |
319 | _, err = rpcClient.RequestAndUnmarshalResult(&signedtx, "signrawtransaction", rcvtx)
320 | if err != nil {
321 | logger.Println("RPC/signrawtransaction error:", err, rcvtx)
322 | return submitRes, err
323 | }
324 |
325 | txid, _, err := rpcClient.RequestAndCastString("sendrawtransaction", signedtx.Hex, true)
326 | if err != nil {
327 | logger.Println("RPC/sendrawtransaction error:", err, signedtx.Hex)
328 | return submitRes, err
329 | }
330 |
331 | submitRes.TransactionID = txid
332 |
333 | for _, v := range rawTx.Vin {
334 | lockList.Unlock(v.Txid, v.Vout)
335 | }
336 |
337 | return submitRes, nil
338 | }
339 |
340 | func lookupRate(requestAsset string, requestAmount int64, offer string) (lib.ExchangeRateResponse, error) {
341 | var rateRes lib.ExchangeRateResponse
342 |
343 | rateMap, ok := fixedRateTable[offer]
344 | if !ok {
345 | err := fmt.Errorf("no exchange source:%s", offer)
346 | logger.Println("error:", err)
347 | return rateRes, err
348 | }
349 |
350 | rate, ok := rateMap[requestAsset]
351 | if !ok {
352 | err := fmt.Errorf("cannot exchange to:%s", requestAsset)
353 | logger.Println("error:", err)
354 | return rateRes, err
355 | }
356 |
357 | cost := int64(float64(requestAmount) / rate.Rate)
358 | if cost < rate.Min {
359 | err := fmt.Errorf("cost lower than min value:%d", cost)
360 | logger.Println("error:", err)
361 | return rateRes, err
362 | }
363 | if rate.Max < cost {
364 | err := fmt.Errorf("cost higher than max value:%d", cost)
365 | logger.Println("error:", err)
366 | return rateRes, err
367 | }
368 |
369 | rateRes = lib.ExchangeRateResponse{
370 | Fee: rate.Fee,
371 | AssetLabel: offer,
372 | Cost: cost,
373 | }
374 |
375 | return rateRes, nil
376 | }
377 |
378 | func initialize() {
379 | logger = log.New(os.Stdout, myActorName+":", log.LstdFlags+log.Lshortfile)
380 | lib.SetLogger(logger)
381 |
382 | rpcClient = rpc.NewRpc(
383 | conf.GetString("rpcurl", defaultRPCURL),
384 | conf.GetString("rpcuser", defaultRPCUser),
385 | conf.GetString("rpcpass", defaultRPCPass))
386 | _, err := rpcClient.RequestAndUnmarshalResult(&assetIDMap, "dumpassetlabels")
387 | if err != nil {
388 | logger.Println("RPC/dumpassetlabels error:", err)
389 | }
390 | delete(assetIDMap, "bitcoin")
391 |
392 | localAddr = conf.GetString("laddr", defaultLocalAddr)
393 | elementsTxCommand = conf.GetString("txpath", defaultTxPath)
394 | elementsTxOption = conf.GetString("txoption", defaultTxOption)
395 | rpc.SetUtxoLockDuration(time.Duration(int64(conf.GetNumber("timeout", defaultTimeout))) * time.Second)
396 | fixedRateTable[defaultRateFrom] = map[string]exchangeRateTuple{defaultRateTo: defaultRateTuple}
397 | conf.GetInterface("fixrate", &fixedRateTable)
398 | }
399 |
400 | func main() {
401 | initialize()
402 |
403 | dir, err := os.Getwd()
404 | if err != nil {
405 | logger.Println("error:", err)
406 | return
407 | }
408 | listener, err := lib.StartHTTPServer(localAddr, handlerList, dir+"/html/"+myActorName)
409 | if err != nil {
410 | logger.Println("error:", err)
411 | return
412 | }
413 | defer func() {
414 | e := listener.Close()
415 | if e != nil {
416 | logger.Println("error:", e)
417 | }
418 | }()
419 |
420 | _, err = lib.StartCyclic(lockList.Sweep, 3, true)
421 | if err != nil {
422 | logger.Println("error:", err)
423 | return
424 | }
425 |
426 | logger.Println(myActorName + " stop")
427 | }
428 |
--------------------------------------------------------------------------------
/src/dave/html/dave/jquery.qrcode.min.js:
--------------------------------------------------------------------------------
1 | (function(r){r.fn.qrcode=function(h){var s;function u(a){this.mode=s;this.data=a}function o(a,c){this.typeNumber=a;this.errorCorrectLevel=c;this.modules=null;this.moduleCount=0;this.dataCache=null;this.dataList=[]}function q(a,c){if(void 0==a.length)throw Error(a.length+"/"+c);for(var d=0;da||this.moduleCount<=a||0>c||this.moduleCount<=c)throw Error(a+","+c);return this.modules[a][c]},getModuleCount:function(){return this.moduleCount},make:function(){if(1>this.typeNumber){for(var a=1,a=1;40>a;a++){for(var c=p.getRSBlocks(a,this.errorCorrectLevel),d=new t,b=0,e=0;e=d;d++)if(!(-1>=a+d||this.moduleCount<=a+d))for(var b=-1;7>=b;b++)-1>=c+b||this.moduleCount<=c+b||(this.modules[a+d][c+b]=
5 | 0<=d&&6>=d&&(0==b||6==b)||0<=b&&6>=b&&(0==d||6==d)||2<=d&&4>=d&&2<=b&&4>=b?!0:!1)},getBestMaskPattern:function(){for(var a=0,c=0,d=0;8>d;d++){this.makeImpl(!0,d);var b=j.getLostPoint(this);if(0==d||a>b)a=b,c=d}return c},createMovieClip:function(a,c,d){a=a.createEmptyMovieClip(c,d);this.make();for(c=0;c=f;f++)for(var i=-2;2>=i;i++)this.modules[b+f][e+i]=-2==f||2==f||-2==i||2==i||0==f&&0==i?!0:!1}},setupTypeNumber:function(a){for(var c=
7 | j.getBCHTypeNumber(this.typeNumber),d=0;18>d;d++){var b=!a&&1==(c>>d&1);this.modules[Math.floor(d/3)][d%3+this.moduleCount-8-3]=b}for(d=0;18>d;d++)b=!a&&1==(c>>d&1),this.modules[d%3+this.moduleCount-8-3][Math.floor(d/3)]=b},setupTypeInfo:function(a,c){for(var d=j.getBCHTypeInfo(this.errorCorrectLevel<<3|c),b=0;15>b;b++){var e=!a&&1==(d>>b&1);6>b?this.modules[b][8]=e:8>b?this.modules[b+1][8]=e:this.modules[this.moduleCount-15+b][8]=e}for(b=0;15>b;b++)e=!a&&1==(d>>b&1),8>b?this.modules[8][this.moduleCount-
8 | b-1]=e:9>b?this.modules[8][15-b-1+1]=e:this.modules[8][15-b-1]=e;this.modules[this.moduleCount-8][8]=!a},mapData:function(a,c){for(var d=-1,b=this.moduleCount-1,e=7,f=0,i=this.moduleCount-1;0g;g++)if(null==this.modules[b][i-g]){var n=!1;f>>e&1));j.getMask(c,b,i-g)&&(n=!n);this.modules[b][i-g]=n;e--; -1==e&&(f++,e=7)}b+=d;if(0>b||this.moduleCount<=b){b-=d;d=-d;break}}}};o.PAD0=236;o.PAD1=17;o.createData=function(a,c,d){for(var c=p.getRSBlocks(a,
9 | c),b=new t,e=0;e8*a)throw Error("code length overflow. ("+b.getLengthInBits()+">"+8*a+")");for(b.getLengthInBits()+4<=8*a&&b.put(0,4);0!=b.getLengthInBits()%8;)b.putBit(!1);for(;!(b.getLengthInBits()>=8*a);){b.put(o.PAD0,8);if(b.getLengthInBits()>=8*a)break;b.put(o.PAD1,8)}return o.createBytes(b,c)};o.createBytes=function(a,c){for(var d=
10 | 0,b=0,e=0,f=Array(c.length),i=Array(c.length),g=0;g>>=1;return c},getPatternPosition:function(a){return j.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,c,d){switch(a){case 0:return 0==(c+d)%2;case 1:return 0==c%2;case 2:return 0==d%3;case 3:return 0==(c+d)%3;case 4:return 0==(Math.floor(c/2)+Math.floor(d/3))%2;case 5:return 0==c*d%2+c*d%3;case 6:return 0==(c*d%2+c*d%3)%2;case 7:return 0==(c*d%3+(c+d)%2)%2;default:throw Error("bad maskPattern:"+
14 | a);}},getErrorCorrectPolynomial:function(a){for(var c=new q([1],0),d=0;dc)switch(a){case 1:return 10;case 2:return 9;case s:return 8;case 8:return 8;default:throw Error("mode:"+a);}else if(27>c)switch(a){case 1:return 12;case 2:return 11;case s:return 16;case 8:return 10;default:throw Error("mode:"+a);}else if(41>c)switch(a){case 1:return 14;case 2:return 13;case s:return 16;case 8:return 12;default:throw Error("mode:"+
15 | a);}else throw Error("type:"+c);},getLostPoint:function(a){for(var c=a.getModuleCount(),d=0,b=0;b=g;g++)if(!(0>b+g||c<=b+g))for(var h=-1;1>=h;h++)0>e+h||c<=e+h||0==g&&0==h||i==a.isDark(b+g,e+h)&&f++;5a)throw Error("glog("+a+")");return l.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;256<=a;)a-=255;return l.EXP_TABLE[a]},EXP_TABLE:Array(256),
17 | LOG_TABLE:Array(256)},m=0;8>m;m++)l.EXP_TABLE[m]=1<m;m++)l.EXP_TABLE[m]=l.EXP_TABLE[m-4]^l.EXP_TABLE[m-5]^l.EXP_TABLE[m-6]^l.EXP_TABLE[m-8];for(m=0;255>m;m++)l.LOG_TABLE[l.EXP_TABLE[m]]=m;q.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var c=Array(this.getLength()+a.getLength()-1),d=0;d
18 | this.getLength()-a.getLength())return this;for(var c=l.glog(this.get(0))-l.glog(a.get(0)),d=Array(this.getLength()),b=0;b>>7-a%8&1)},put:function(a,c){for(var d=0;d>>c-d-1&1))},getLengthInBits:function(){return this.length},putBit:function(a){var c=Math.floor(this.length/8);this.buffer.length<=c&&this.buffer.push(0);a&&(this.buffer[c]|=128>>>this.length%8);this.length++}};"string"===typeof h&&(h={text:h});h=r.extend({},{render:"canvas",width:256,height:256,typeNumber:-1,
26 | correctLevel:2,background:"#ffffff",foreground:"#000000"},h);return this.each(function(){var a;if("canvas"==h.render){a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();var c=document.createElement("canvas");c.width=h.width;c.height=h.height;for(var d=c.getContext("2d"),b=h.width/a.getModuleCount(),e=h.height/a.getModuleCount(),f=0;f").css("width",h.width+"px").css("height",h.height+"px").css("border","0px").css("border-collapse","collapse").css("background-color",h.background);d=h.width/a.getModuleCount();b=h.height/a.getModuleCount();for(e=0;e |
").css("height",b+"px").appendTo(c);for(i=0;i