├── 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 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/lib/doc.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 | Miscellaneous shared functionality 7 | */ 8 | package lib 9 | -------------------------------------------------------------------------------- /src/democonf/doc.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 | The democonf.json file contains all the configuration parameters required to 7 | run the demo. 8 | */ 9 | package democonf 10 | -------------------------------------------------------------------------------- /src/rpc/doc.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 | A simple RPC library used by the parties in the demo to facilitate interactions 7 | with the Elements RPC. 8 | */ 9 | package rpc 10 | -------------------------------------------------------------------------------- /src/dave/doc.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 | Dave runs a coffee shop. His customers may either pay him in cash or they can use 7 | a specific asset that he supports. 8 | */ 9 | package main 10 | -------------------------------------------------------------------------------- /src/charlie/doc.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 | Charlie is a Point Exchange service. He is willing to exchange points between 7 | different asset types. He does this by looking at a conversion table, composing 8 | transactions in response to requests to convert points. 9 | */ 10 | package main 11 | -------------------------------------------------------------------------------- /src/alice/doc.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 | Alice is the customer in the demo. 7 | 8 | She wants to buy coffee from Dave, but since she doesn't have the asset he accepts 9 | she uses a Point Exchange service (Charlie) to convert her existing points to the 10 | ones Dave wants. 11 | */ 12 | package main 13 | -------------------------------------------------------------------------------- /src/fred/doc.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 | Fred is a "block signer". He is responsible for creating and putting blocks 7 | on the block chain at appropriate times (when the mempool has something in it). 8 | In reality, he would be one of several functionaries in the block chain. 9 | */ 10 | package main 11 | -------------------------------------------------------------------------------- /src/bob/doc.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 | Bob is a competitor of Dave's. 7 | 8 | He wants to find out as much as he can about what Dave is doing, so he can strategize 9 | how to take Dave out of business for good. He is actively snooping on the block chain 10 | and logs everything he can find about all transactions. 11 | */ 12 | package main 13 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 DG Lab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/democonf/democonf.json: -------------------------------------------------------------------------------- 1 | { 2 | "alice": { 3 | "rpcurl": "http://127.0.0.1:10000/", 4 | "rpcuser": "user", 5 | "rpcpass": "pass", 6 | "laddr": ":8000" 7 | }, 8 | "bob": { 9 | "rpcurl": "http://127.0.0.1:10010/", 10 | "rpcuser": "user", 11 | "rpcpass": "pass" 12 | }, 13 | "charlie": { 14 | "rpcurl": "http://127.0.0.1:10020/", 15 | "rpcuser": "user", 16 | "rpcpass": "pass", 17 | "laddr": ":8020", 18 | "fixrate": { 19 | "AIRSKY":{ 20 | "MELON":{"rate":0.5,"min":100,"max":200000,"unit":20,"fee":15}, 21 | "MONECRE":{"rate":0.5,"min":100,"max":200000,"unit":20,"fee":20} 22 | }, 23 | "MELON":{ 24 | "AIRSKY":{"rate":2.0,"min":100,"max":100000,"unit":10,"fee":5}, 25 | "MONECRE":{"rate":1.0,"min":100,"max":100000,"unit":10,"fee":5} 26 | }, 27 | "MONECRE":{ 28 | "AIRSKY":{"rate":2.0,"min":100,"max":100000,"unit":10,"fee":10}, 29 | "MELON":{"rate":1.0,"min":100,"max":100000,"unit":10,"fee":10} 30 | } 31 | } 32 | }, 33 | "dave": { 34 | "rpcurl": "http://127.0.0.1:10030/", 35 | "rpcuser": "user", 36 | "rpcpass": "pass", 37 | "laddr": ":8030", 38 | "confidential": true 39 | }, 40 | "fred": { 41 | "rpcurl": "http://127.0.0.1:10040/", 42 | "rpcuser": "user", 43 | "rpcpass": "pass" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /stop_demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | if [ -e ./demo.tmp ]; then 5 | 6 | source ./demo.tmp 7 | 8 | gopids=( $dave_pid $alice_pid $charlie_pid $fred_pid $bob_pid ) 9 | for pid in "${gopids[@]}"; do 10 | if ps -p $pid > /dev/null ; then 11 | echo "kill -SIGINT $pid" 12 | kill -SIGINT $pid 13 | fi 14 | done 15 | sleep 1 16 | for pid in "${gopids[@]}"; do 17 | if ps -p $pid > /dev/null ; then 18 | echo "kill -9 $pid" 19 | kill -9 $pid 20 | fi 21 | done 22 | 23 | dirs=( $dave_dir $alice_dir $charlie_dir $fred_dir $bob_dir ) 24 | for dir in "${dirs[@]}"; do 25 | echo "$ELCLI $dir stop" 26 | $ELCLI $dir stop 27 | done 28 | sleep 3 29 | pids=( $dave_dae $alice_dae $charlie_dae $fred_dae $bob_dae ) 30 | for pid in "${pids[@]}"; do 31 | if ps -p $pid > /dev/null ; then 32 | echo "kill -9 $pid" 33 | kill -9 $pid 34 | fi 35 | done 36 | 37 | rm -f ./demo.tmp 38 | 39 | else 40 | 41 | echo "kill processes" 42 | 43 | # Stop the demo. This script is definitely not the way to do this in a production environment. 44 | # Note that any running elementsd processes WILL be killed unless owned by a different user! 45 | 46 | pkill bob 47 | pkill dave 48 | pkill charlie 49 | pkill alice 50 | pkill fred 51 | pkill -9 elementsd 52 | 53 | fi 54 | -------------------------------------------------------------------------------- /src/dave/html/dave/order.js: -------------------------------------------------------------------------------- 1 | 2 | function init() { 3 | $("#order").click(order); 4 | $("#reset").click(reset); 5 | $("#qrcode").click(copyUri); 6 | } 7 | 8 | function order() { 9 | $("#qrcode").empty(); 10 | $("#addr").empty(); 11 | $("#price").empty(); 12 | $("#asset").empty(); 13 | $("#uri").val(""); 14 | var param = { 15 | item: "Caramel Macchiato Coffee" 16 | }; 17 | $.getJSON("order", param) 18 | .done(function (data) { 19 | if (data.result) { 20 | console.log(data.uri); 21 | let item = { 22 | text: data.uri 23 | }; 24 | $("#qrcode").qrcode(item); 25 | $("#name").text(data.name); 26 | $("#addr").text(data.addr); 27 | $("#asset").text(data.asset); 28 | $("#price").text("" + data.price); 29 | $("#uri").val(data.uri); 30 | } 31 | $("#before").fadeOut('slow', function () { $("#after").fadeIn('slow'); }); 32 | }) 33 | .fail(function (jqXHR, textStatus, errorThrown) { 34 | alert("order fail\n" + JSON.stringify(jqXHR) + "\n" + textStatus + "\n" + errorThrown + "\n"); 35 | }); 36 | } 37 | 38 | function reset() { 39 | if (confirm("Return to order page?")) { 40 | $("#after").fadeOut('slow', function () { $("#before").fadeIn('slow'); }); 41 | } 42 | } 43 | 44 | function copyUri() { 45 | var uri = document.getElementById("uri"); 46 | uri.select(); 47 | uri.selectionStart = 0; 48 | uri.selectEnd = uri.value.length; 49 | document.execCommand("copy"); 50 | $("#message").fadeIn('slow').delay(1000).fadeOut('slow'); 51 | } 52 | 53 | $(init); 54 | -------------------------------------------------------------------------------- /src/fred/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 | // fred 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 | // URL for accessing RPC 19 | var rpcurl = "http://127.0.0.1:10040" 20 | 21 | // ID for accessing RPC 22 | var rpcuser = "user" 23 | 24 | // Password for accessing RPC 25 | var rpcpass = "pass" 26 | 27 | var rpcClient *rpc.Rpc 28 | 29 | var logger *log.Logger 30 | 31 | func checkgenerate() error { 32 | var txs []string 33 | res, err := rpcClient.RequestAndUnmarshalResult(&txs, "getrawmempool") 34 | if err != nil { 35 | logger.Printf("Rpc#RequestAndUnmarshalResult error:%v res:%+v", err, res) 36 | return err 37 | } 38 | if len(txs) == 0 { 39 | return nil 40 | } 41 | rpcClient.View = true 42 | var hashs []string 43 | res, err = rpcClient.RequestAndUnmarshalResult(&hashs, "generate", 1) 44 | rpcClient.View = false 45 | if err != nil { 46 | logger.Printf("Rpc#RequestAndUnmarshalResult error:%v res:%+v", err, res) 47 | return err 48 | } 49 | return nil 50 | } 51 | 52 | func callback() { 53 | err := checkgenerate() 54 | if err != nil { 55 | logger.Println("checkgenarate error:", err) 56 | } 57 | } 58 | 59 | func loadConf() { 60 | conf := democonf.NewDemoConf("fred") 61 | rpcurl = conf.GetString("rpcurl", rpcurl) 62 | rpcuser = conf.GetString("rpcuser", rpcuser) 63 | rpcpass = conf.GetString("rpcpass", rpcpass) 64 | } 65 | 66 | func main() { 67 | logger = log.New(os.Stdout, "Fred:", log.LstdFlags+log.Lshortfile) 68 | fmt.Println("Fred start") 69 | 70 | loadConf() 71 | rpcClient = rpc.NewRpc(rpcurl, rpcuser, rpcpass) 72 | 73 | lib.SetLogger(logger) 74 | lib.StartCyclic(callback, 3, true) 75 | 76 | fmt.Println("Fred stop") 77 | } 78 | -------------------------------------------------------------------------------- /src/dave/html/dave/list.js: -------------------------------------------------------------------------------- 1 | 2 | function init() { 3 | list(); 4 | } 5 | 6 | function list() { 7 | $.getJSON("list", function (data) { 8 | if (data.result) { 9 | $("#list").empty(); 10 | for (order of data.result) { 11 | let tr = $(""); 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 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
ItemAddrPriceAssetStatusTimeoutLastModified
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 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
Product
Caramel Macchiato Coffee
Price
MELON
200
pt
31 |
32 | 33 |
34 |
35 |
36 |
Scan This QR Code (Click to put into Clipboard!)
37 | 38 |
39 |
40 |
41 |
42 |
43 |
pt
44 |
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 | ![SS01](doc/ss01.png) 62 | 63 | ![SS02](doc/ss02.png) 64 | 65 | ![SS03](doc/ss03.png) 66 | 67 | ![SS04](doc/ss04.png) 68 | 69 | ![SS05](doc/ss05.png) 70 | 71 | ![SS06](doc/ss06.png) 72 | 73 | ![SS07](doc/ss07.png) 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 |
18 |
19 | 20 |
21 |
22 | 23 |
24 |
25 | Purchase using Points 26 |
27 |
28 | 29 | 30 |
31 |
32 | ADDRESS 33 |
34 |
35 |   36 |
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 | 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($("