repos / hub.go.git


commit
96111eb
parent
11fcf63
author
Evgenii Akentev
date
2024-09-02 13:23:39 +0400 +04
Add example of the chain listener
10 files changed,  +418, -225
M go.mod
M go.sum
M hub.go
A chain-listener.go
+52, -0
 1@@ -0,0 +1,52 @@
 2+package main
 3+
 4+import (
 5+  "context"
 6+  "log"
 7+
 8+	"github.com/xssnick/tonutils-go/liteclient"
 9+	"github.com/xssnick/tonutils-go/ton"
10+
11+  "main/contracts"
12+)
13+
14+func listenChain() {
15+  log.Println("Start liteclient")
16+  client := liteclient.NewConnectionPool()
17+
18+  var liteServers string
19+  switch contracts.GetContractEnvironment() {
20+  case "MAINNET":
21+    liteServers = "https://ton.org/global.config.json"
22+  default:
23+    liteServers = "https://ton.org/testnet-global.config.json"
24+  }
25+
26+	// connect to mainnet lite servers
27+	err := client.AddConnectionsFromConfigUrl(context.Background(), liteServers) 
28+	if err != nil {
29+		log.Fatalln("connection err: ", err.Error())
30+		return
31+	} 
32+
33+  api := ton.NewAPIClient(client, ton.ProofCheckPolicyFast).WithRetry()
34+	ctx := client.StickyContext(context.Background())
35+
36+  for {
37+    log.Println("get masterchain info")
38+    b, err := api.CurrentMasterchainInfo(ctx)
39+	  if err != nil {
40+	  	log.Fatalln("get block err:", err.Error())
41+	  	return
42+	  }
43+  
44+    log.Println("read declarations")
45+    declarations := readDeclarations(api, ctx, b, nil)
46+    
47+    for _, decl := range declarations {
48+      log.Println("%V", decl)
49+    }
50+
51+
52+  }
53+}
M client.go
+64, -107
  1@@ -1,134 +1,91 @@
  2 package main
  3 
  4 import (
  5-	"bytes"
  6-	"log"
  7 	"net/http"
  8-	"time"
  9-
 10+  "log"
 11 	"github.com/gorilla/websocket"
 12 )
 13 
 14-const (
 15-	// Time allowed to write a message to the peer.
 16-	writeWait = 10 * time.Second
 17-
 18-	// Time allowed to read the next pong message from the peer.
 19-	pongWait = 60 * time.Second
 20-
 21-	// Send pings to peer with this period. Must be less than pongWait.
 22-	pingPeriod = (pongWait * 9) / 10
 23-
 24-	// Maximum message size allowed from peer.
 25-	maxMessageSize = 512
 26-)
 27-
 28-var (
 29-	newline = []byte{'\n'}
 30-	space   = []byte{' '}
 31-)
 32-
 33 var upgrader = websocket.Upgrader{
 34 	ReadBufferSize:  1024,
 35 	WriteBufferSize: 1024,
 36   CheckOrigin: func(r *http.Request) bool{ return true },
 37 }
 38 
 39-// Client is a middleman between the websocket connection and the hub.
 40 type Client struct {
 41 	hub *Hub
 42-
 43-	// The websocket connection.
 44 	conn *websocket.Conn
 45-
 46-	// Buffered channel of outbound messages.
 47-	send chan []byte
 48+  hubResponse chan HubResponse
 49 }
 50 
 51-// readPump pumps messages from the websocket connection to the hub.
 52-//
 53-// The application runs readPump in a per-connection goroutine. The application
 54-// ensures that there is at most one reader on a connection by executing all
 55-// reads from this goroutine.
 56-func (c *Client) readPump() {
 57-	defer func() {
 58-		c.hub.unregister <- c
 59-		c.conn.Close()
 60-	}()
 61-	c.conn.SetReadLimit(maxMessageSize)
 62-	c.conn.SetReadDeadline(time.Now().Add(pongWait))
 63-	c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
 64-	for {
 65-		_, message, err := c.conn.ReadMessage()
 66-		if err != nil {
 67-			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
 68-				log.Printf("error: %v", err)
 69-			}
 70-			break
 71-		}
 72-		message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
 73-		c.hub.broadcast <- message
 74-	}
 75+
 76+func (c *Client) run() {
 77+  go c.handleChannels()
 78+  go c.listenConnection()
 79 }
 80 
 81-// writePump pumps messages from the hub to the websocket connection.
 82-//
 83-// A goroutine running writePump is started for each connection. The
 84-// application ensures that there is at most one writer to a connection by
 85-// executing all writes from this goroutine.
 86-func (c *Client) writePump() {
 87-	ticker := time.NewTicker(pingPeriod)
 88-	defer func() {
 89-		ticker.Stop()
 90-		c.conn.Close()
 91-	}()
 92-	for {
 93-		select {
 94-		case message, ok := <-c.send:
 95-			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
 96-			if !ok {
 97-				// The hub closed the channel.
 98-				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
 99-				return
100-			}
101+func (c *Client) handleChannels() {
102+  defer func() {
103+    close(c.hubResponse)
104+  }()
105+
106+  for {
107+    select {
108+    case response := <- c.hubResponse:
109+      switch r := response.(type) {
110+        case CopyrightsHubResponse:
111+          c.conn.WriteJSON(CopyrightsResponse{"copyrights", r})
112+        case CopyrightStatesHubResponse:
113+          c.conn.WriteJSON(CopyrightStatesResponse{"new-states", r})
114+      }
115+    }
116+  }
117+}
118 
119-			w, err := c.conn.NextWriter(websocket.TextMessage)
120-			if err != nil {
121-				return
122-			}
123-			w.Write(message)
124+func (c *Client) listenConnection() {
125+  defer func() {
126+    c.hub.unregister <- c
127+    c.conn.Close()
128+    close(c.hubResponse)
129+  }()
130 
131-			// Add queued chat messages to the current websocket message.
132-			n := len(c.send)
133-			for i := 0; i < n; i++ {
134-				w.Write(newline)
135-				w.Write(<-c.send)
136-			}
137+  for {
138+    var m Message 
139+    err := c.conn.ReadJSON(&m)
140 
141-			if err := w.Close(); err != nil {
142-				return
143-			}
144-		case <-ticker.C:
145-			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
146-			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
147-				return
148+  	if err != nil {
149+			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
150+				log.Printf("error: %v", err)
151 			}
152+      log.Printf("error: %v", err)
153+			break
154 		}
155-	}
156-}
157-
158-func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
159-	conn, err := upgrader.Upgrade(w, r, nil)
160-	if err != nil {
161-		log.Println(err)
162-		return
163-	}
164-	client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
165-	client.hub.register <- client
166 
167-	// Allow collection of memory referenced by the caller by doing all work in
168-	// new goroutines.
169-	go client.writePump()
170-	go client.readPump()
171+    switch m.Event {
172+    case "subscribe-copyrights":
173+      for _, addr := range m.Addrs {
174+        c.hub.subscribe <- SubMessage{Contract, addr, c}
175+      }
176+    case "unsubscribe-copyrights":
177+      for _, addr := range m.Addrs {
178+        c.hub.unsubscribe <- SubMessage{Contract, addr, c}
179+      }
180+    case "subscribe-wallets":
181+      for _, addr := range m.Addrs {
182+        c.hub.subscribe <- SubMessage{Wallet, addr, c}
183+      }
184+    case "unsubscribe-wallets":
185+      for _, addr := range m.Addrs {
186+        c.hub.unsubscribe <- SubMessage{Wallet, addr, c}
187+      }
188+    case "get-copyrights":
189+      c.hub.clientQuery <- ClientQuery{c, GetCopyrightsQuery(m.Addrs)}
190+    case "get-copyrights-by-wallets":
191+      c.hub.clientQuery <- ClientQuery{c, GetCopyrightsByWalletsQuery(m.Addrs)}
192+    case "get-states":
193+      c.hub.clientQuery <- ClientQuery{c, GetCopyrightStatesQuery(m.Addrs)}
194+    default:
195+      log.Printf("error: Unsupported event: %v", m)
196+    }
197+  }
198 }
199-
R entities.go => contract-entities.go
+28, -13
 1@@ -1,15 +1,5 @@
 2 package main
 3 
 4-
 5-import (
 6-//  "encoding/json"
 7-)
 8-
 9-type Message struct {
10-  Event string `json:"event"`
11-  Addrs []string `json:"addresses"`
12-}
13-
14 type PersonDetails struct {
15   name string
16   address string
17@@ -27,12 +17,13 @@ const (
18 )
19 
20 type ExclusiveRightsClaim struct {
21+  author Address
22 }
23 
24 type Copyright struct {
25-  address string
26-  author string
27-  owner string
28+  address Address
29+  author Address
30+  owner Address
31   ownerDetails PersonDetails
32   royalty *string
33 
34@@ -42,4 +33,28 @@ type Copyright struct {
35   price *int
36   assignmentHash *string
37   claim *ExclusiveRightsClaim
38+
39+  initTxLt int
40+}
41+
42+type CopyrightState struct {
43+  owner Address
44+  ownerDetails PersonDetails
45+  claim *ExclusiveRightsClaim
46+  assignmentHash *string
47+  price *int
48+  balance int
49+  status DeclarationStatus
50+}
51+
52+func (c Copyright) mkState() *CopyrightState {
53+  return &CopyrightState{
54+    owner: c.owner,
55+    ownerDetails: c.ownerDetails,
56+    claim: c.claim,
57+    assignmentHash: c.assignmentHash,
58+    price: c.price,
59+    balance: c.balance,
60+    status: c.status,
61+  }
62 }
A declarations.go
+63, -0
 1@@ -0,0 +1,63 @@
 2+package main
 3+
 4+import (
 5+  "log"
 6+  "context"
 7+
 8+	"github.com/xssnick/tonutils-go/ton"
 9+
10+  "main/contracts"
11+)
12+
13+type DeclareDocument struct {
14+}
15+
16+type DocumentDeclaration struct {
17+  authorAddress Address
18+  declarationAddress Address
19+  txLt int
20+  txHash string
21+  declaration DeclareDocument
22+}
23+
24+func readDeclarations(client ton.APIClientWrapped, ctx context.Context, b *ton.BlockIDExt, prevLt *uint64) []DocumentDeclaration {
25+  res, err := client.WaitForBlock(b.SeqNo).GetAccount(ctx, b, contracts.GetContractAddress())
26+  if err != nil {
27+		log.Fatalln("get account err:", err.Error())
28+		return nil
29+	}
30+
31+  var declarations []DocumentDeclaration
32+
33+  var lastLt = res.LastTxLT
34+  var lastHash = res.LastTxHash
35+
36+  for {
37+    if lastLt == 0 { break }
38+
39+    list, err := client.ListTransactions(ctx, contracts.GetContractAddress(), 15, lastLt, lastHash)
40+		if err != nil {
41+			log.Printf("send err: %s", err.Error())
42+			return nil
43+		}
44+
45+    for _, tx := range list {
46+
47+      if prevLt != nil && tx.LT <= *prevLt {
48+        return declarations
49+      }
50+
51+//      var declaration DocumentDeclaration
52+
53+      outMessage := tx.IO.Out.List
54+      log.Println("%V", outMessage)
55+
56+//      append(declarations, declaration)
57+    }
58+
59+    lastLt = list[0].PrevTxLT
60+    lastHash = list[0].PrevTxHash
61+  }
62+
63+  return declarations
64+}
M go.mod
+11, -1
 1@@ -2,4 +2,14 @@ module main
 2 
 3 go 1.22.5
 4 
 5-require github.com/gorilla/websocket v1.5.3
 6+require (
 7+	github.com/gorilla/websocket v1.5.3
 8+	github.com/xssnick/tonutils-go v1.9.9
 9+)
10+
11+require (
12+	github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect
13+	github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect
14+	golang.org/x/crypto v0.17.0 // indirect
15+	golang.org/x/sys v0.15.0 // indirect
16+)
M go.sum
+10, -0
 1@@ -1,2 +1,12 @@
 2 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
 3 github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 4+github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae h1:7smdlrfdcZic4VfsGKD2ulWL804a4GVphr4s7WZxGiY=
 5+github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s=
 6+github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs=
 7+github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
 8+github.com/xssnick/tonutils-go v1.9.9 h1:J0hVJI4LNEFHqgRHzpWTjFuv/Ga89OqLRUc9gxmjCoc=
 9+github.com/xssnick/tonutils-go v1.9.9/go.mod h1:p1l1Bxdv9sz6x2jfbuGQUGJn6g5cqg7xsTp8rBHFoJY=
10+golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
11+golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
12+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
13+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
M hub.go
+56, -21
  1@@ -1,48 +1,83 @@
  2 package main
  3 
  4-// Hub maintains the set of active clients and broadcasts messages to the
  5-// clients.
  6+import (
  7+  "slices"
  8+  "cmp"
  9+  "log"
 10+)
 11+
 12 type Hub struct {
 13-	// Registered clients.
 14 	clients map[*Client]bool
 15+  
 16+  copyrights map[Address]*Copyright
 17+  clientQuery chan ClientQuery   
 18 
 19-	// Inbound messages from the clients.
 20-	broadcast chan []byte
 21+  subs *Subscriptions
 22+  subscribe chan SubMessage
 23+  unsubscribe chan SubMessage 
 24 
 25-	// Register requests from the clients.
 26+  messages chan string
 27 	register chan *Client
 28-
 29-	// Unregister requests from clients.
 30 	unregister chan *Client
 31 }
 32 
 33 func newHub() *Hub {
 34 	return &Hub{
 35-		broadcast:  make(chan []byte),
 36+  	clients:    make(map[*Client]bool),
 37+    copyrights: make(map[Address]*Copyright),
 38+    clientQuery: make(chan ClientQuery),
 39+
 40+    subs:    newSubscriptions(),
 41+    subscribe: make(chan SubMessage),
 42+    unsubscribe: make(chan SubMessage),
 43+
 44 		register:   make(chan *Client),
 45-		unregister: make(chan *Client),
 46-		clients:    make(map[*Client]bool),
 47+		unregister: make(chan *Client),	
 48 	}
 49 }
 50 
 51 func (h *Hub) run() {
 52 	for {
 53 		select {
 54+    case cq := <-h.clientQuery:
 55+      switch q := cq.query.(type) {
 56+      case GetCopyrightsQuery:
 57+        var copyrights []*Copyright
 58+        for _, addr := range q {
 59+          if c, ok := h.copyrights[addr]; ok {
 60+            copyrights = append(copyrights, c)
 61+          }
 62+        }
 63+        cq.client.hubResponse <- CopyrightsHubResponse(copyrights)
 64+      case GetCopyrightsByWalletsQuery: 
 65+        var copyrights []*Copyright
 66+        for _, cp := range h.copyrights {
 67+          if (slices.Contains(q, cp.author) || slices.Contains(q, cp.owner) || slices.Contains(q, cp.claim.author)) {
 68+           copyrights = append(copyrights, cp)
 69+          }
 70+        }
 71+        slices.SortFunc(copyrights, func(a, b *Copyright) int { return cmp.Compare(a.initTxLt, b.initTxLt)})
 72+        cq.client.hubResponse <- CopyrightsHubResponse(copyrights)
 73+    case GetCopyrightStatesQuery:
 74+      var copyrightStates = make(map[Address]*CopyrightState)
 75+      for _, addr := range q {
 76+        if cp, ok := h.copyrights[addr]; ok {
 77+          copyrightStates[addr] = cp.mkState()
 78+        }
 79+      }
 80+      cq.client.hubResponse <- CopyrightStatesHubResponse(copyrightStates)
 81+    }
 82+    case s := <-h.subscribe:
 83+      log.Println("%V", h.subs)
 84+      h.subs.subscribe(s.addressType, s.address, s.client)
 85+      log.Println("%V", h.subs)
 86+    case s := <-h.unsubscribe:
 87+      h.subs.unsubscribe(s.addressType, s.address, s.client)
 88 		case client := <-h.register:
 89 			h.clients[client] = true
 90 		case client := <-h.unregister:
 91 			if _, ok := h.clients[client]; ok {
 92 				delete(h.clients, client)
 93-				close(client.send)
 94-			}
 95-		case message := <-h.broadcast:
 96-			for client := range h.clients {
 97-				select {
 98-				case client.send <- message:
 99-				default:
100-					close(client.send)
101-					delete(h.clients, client)
102-				}
103 			}
104 		}
105 	}
M main.go
+20, -83
  1@@ -4,96 +4,20 @@ import (
  2 	"flag"
  3 	"log"
  4 	"net/http"
  5-
  6-  "github.com/gorilla/websocket"
  7+  
  8 )
  9 
 10-type Subscribers = map[string]([](*websocket.Conn))
 11-
 12-type State struct {
 13-  copyrights map[string]Copyright
 14-  copyrightSubs map[string]([]*websocket.Conn)
 15-  walletSubs map[string]([]*websocket.Conn)
 16-}
 17-
 18-func newState() *State {
 19-  return &State {
 20-    copyrights: make(map[string]Copyright),
 21-    copyrightSubs: make(map[string]([]*websocket.Conn)),
 22-    walletSubs: make(map[string]([]*websocket.Conn)),
 23-  }
 24-}
 25-
 26-func subscribe(m Subscribers, k string, c *websocket.Conn)  {
 27-  var newSubs [](*websocket.Conn)
 28-  if subs, ok := m[k]; ok {
 29-    newSubs = append(subs, c)
 30-    m[k] = newSubs
 31-  } else {
 32-    newSubs = make([](*websocket.Conn), 1)
 33-    newSubs[0] = c
 34-    m[k] = newSubs
 35-  }
 36-}
 37-
 38-func unsubscribe(m Subscribers, k string, c *websocket.Conn)  {
 39-  var newSubs [](*websocket.Conn)
 40-
 41-  if subs, ok := m[k]; ok {
 42-    for _, sub := range subs {
 43-      if (sub != c) {
 44-        newSubs = append(newSubs, sub)
 45-      }
 46-    }
 47-    m[k] = newSubs
 48-  }
 49-}
 50-
 51-func listenConnection(state *State, c *websocket.Conn) {
 52-  defer func() {
 53-    c.Close()
 54-  }()
 55-
 56-  for {
 57-    var m Message 
 58-    err := c.ReadJSON(&m)
 59-
 60-  	if err != nil {
 61-			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
 62-				log.Printf("error: %v", err)
 63-			}
 64-      log.Printf("error: %v", err)
 65-			break
 66-		}
 67-
 68-    switch m.Event {
 69-    case "subscribe-wallets":
 70-      for _, addr := range m.Addrs {
 71-        subscribe((*state).walletSubs, addr, c)
 72-      }
 73-    default:
 74-      log.Printf("error: Unsupported event: %v", m)
 75-    }
 76-  }
 77-}
 78-
 79-func serve(state *State, w http.ResponseWriter, r *http.Request) {
 80-  conn, err := upgrader.Upgrade(w, r, nil)
 81-  if err != nil {
 82-    log.Println(err)
 83-    return
 84-  }
 85-
 86-  go listenConnection(state, conn)
 87-}
 88-
 89 func main() {
 90 	flag.Parse()
 91+  
 92+  hub := newHub()
 93+  
 94+  go hub.run()
 95 
 96-  state := newState()
 97+  go listenChain()
 98 
 99   http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
100-		serve(state, w, r)
101+		serve(hub, w, r)
102 	})
103   
104   log.Println("Started websocket server at :3000")
105@@ -104,3 +28,16 @@ func main() {
106 	}
107 }
108 
109+func serve(hub *Hub, w http.ResponseWriter, r *http.Request) {
110+  conn, err := upgrader.Upgrade(w, r, nil)
111+  if err != nil {
112+    log.Println(err)
113+    return
114+  }
115+
116+  client := Client{hub: hub, conn: conn}
117+
118+  hub.register <- &client
119+
120+  go client.run()
121+}
A server-entities.go
+63, -0
 1@@ -0,0 +1,63 @@
 2+package main
 3+
 4+import (
 5+//  "encoding/json"
 6+)
 7+
 8+type Address string
 9+type AddressType int
10+
11+const (
12+  Wallet = iota
13+  Contract
14+)
15+
16+type SubMessage struct {
17+  addressType AddressType
18+  address Address
19+  client *Client
20+}
21+
22+type Query interface { isQuery() } 
23+
24+type GetCopyrightsQuery []Address
25+func (gc GetCopyrightsQuery) isQuery() {}
26+
27+type GetCopyrightsByWalletsQuery []Address
28+func (gc GetCopyrightsByWalletsQuery) isQuery() {}
29+
30+type GetCopyrightStatesQuery []Address
31+func (gc GetCopyrightStatesQuery) isQuery() {}
32+
33+type ClientQuery struct {
34+  client *Client
35+  query Query
36+}
37+
38+type HubResponse interface { isHubResponse() }
39+
40+type CopyrightsHubResponse []*Copyright
41+func (cr CopyrightsHubResponse) isHubResponse() {}
42+
43+type CopyrightStatesHubResponse map[Address]*CopyrightState
44+func (cr CopyrightStatesHubResponse) isHubResponse() {}
45+
46+type Message struct {
47+  Event string `json:"event"`
48+  Addrs []Address `json:"addresses"`
49+}
50+
51+type CopyrightsResponse struct {
52+  Event string `json:"event"`
53+  Data any `json:"data"`
54+}
55+
56+type CopyrightStatesResponse struct {
57+  Event string `json:"event"`
58+  States map[Address]*CopyrightState `json:"states"`
59+}
60+
61+type Response struct {
62+  copyrightsResponse *CopyrightsResponse
63+  copyrightStatesResponse *CopyrightStatesResponse
64+}
A subscriptions.go
+51, -0
 1@@ -0,0 +1,51 @@
 2+package main
 3+
 4+type Subscription map[Address](map[*Client]struct{})
 5+
 6+type Subscriptions struct {
 7+  wallets Subscription
 8+  copyrights Subscription
 9+}
10+
11+func newSubscriptions() *Subscriptions {
12+  return &Subscriptions{
13+    wallets: make(map[Address](map[*Client]struct{})), 
14+    copyrights: make(map[Address](map[*Client]struct{})), 
15+  }
16+}
17+
18+func (s *Subscriptions) removeClient(c *Client) {
19+  for _, v := range s.wallets {
20+    delete(v, c)
21+  }
22+}
23+
24+func (s *Subscriptions) getSub(at AddressType) Subscription {
25+  switch at {
26+  case Wallet:
27+   return s.wallets
28+  case Contract:
29+   return s.copyrights
30+  }
31+  return nil
32+}
33+
34+func (s *Subscriptions) subscribe(at AddressType, addr Address, c *Client) {
35+  var m = s.getSub(at)
36+  if subs, ok := m[addr]; ok {
37+    subs[c] = struct{}{}
38+    //TODO: not sure if it works, maybe need m[addr] ...
39+  } else {
40+    newsubs := make(map[*Client]struct{})
41+    newsubs[c] = struct{}{}
42+    m[addr] = newsubs
43+  }
44+}
45+
46+func (s *Subscriptions) unsubscribe(at AddressType, addr Address, c *Client)  {
47+  var m = s.getSub(at)
48+  if subs, ok := m[addr]; ok {
49+    // TODO: not sure as well
50+    delete(subs, c)
51+  }
52+}