Connect yourself to the lightning network with Go

An example with LND and lntop

Edouard Paris - https://edouard.paris/slides/2019-06-18-meetup-go-paris




import ( "io/ioutil" "net/url"

<span style="color:#e6db74">&#34;github.com/pkg/errors&#34;</span>
<span style="color:#e6db74">&#34;google.golang.org/grpc&#34;</span>
<span style="color:#e6db74">&#34;google.golang.org/grpc/credentials&#34;</span>
<span style="color:#a6e22e">macaroon</span> <span style="color:#e6db74">&#34;gopkg.in/macaroon.v2&#34;</span>

<span style="color:#e6db74">&#34;github.com/lightningnetwork/lnd/lncfg&#34;</span>
<span style="color:#e6db74">&#34;github.com/lightningnetwork/lnd/macaroons&#34;</span>

)

type Config struct { Address string toml:&#34;address&#34; Cert string toml:&#34;cert&#34; Macaroon string toml:&#34;macaroon&#34; MacaroonTimeOut int64 toml:&#34;macaroon_timeout&#34; MacaroonIP string toml:&#34;macaroon_ip&#34; MaxMsgRecvSize int toml:&#34;max_msg_recv_size&#34; ConnTimeout int toml:&#34;conn_timeout&#34; }

func NewConn(c Config) (grpc.ClientConn, error) { macaroonBytes, err := ioutil.ReadFile(c.Macaroon) if err != nil { return nil, err }

<span style="color:#a6e22e">mac</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">macaroon</span>.<span style="color:#a6e22e">Macaroon</span>{}
<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">mac</span>.<span style="color:#a6e22e">UnmarshalBinary</span>(<span style="color:#a6e22e">macaroonBytes</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">WithStack</span>(<span style="color:#a6e22e">err</span>)
}

<span style="color:#a6e22e">constrainedMac</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">macaroons</span>.<span style="color:#a6e22e">AddConstraints</span>(<span style="color:#a6e22e">mac</span>,
    <span style="color:#a6e22e">macaroons</span>.<span style="color:#a6e22e">TimeoutConstraint</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">MacaroonTimeOut</span>),
    <span style="color:#a6e22e">macaroons</span>.<span style="color:#a6e22e">IPLockConstraint</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">MacaroonIP</span>),
)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">WithStack</span>(<span style="color:#a6e22e">err</span>)
}

<span style="color:#a6e22e">cred</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">credentials</span>.<span style="color:#a6e22e">NewClientTLSFromFile</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Cert</span>, <span style="color:#e6db74">&#34;&#34;</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
}

<span style="color:#a6e22e">u</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">url</span>.<span style="color:#a6e22e">Parse</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Address</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
}

<span style="color:#a6e22e">conn</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">Dial</span>(<span style="color:#a6e22e">u</span>.<span style="color:#a6e22e">Hostname</span>(),
    <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">WithTransportCredentials</span>(<span style="color:#a6e22e">cred</span>),
    <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">WithPerRPCCredentials</span>(<span style="color:#a6e22e">macaroons</span>.<span style="color:#a6e22e">NewMacaroonCredential</span>(<span style="color:#a6e22e">constrainedMac</span>)),
    <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">WithDialer</span>(<span style="color:#a6e22e">lncfg</span>.<span style="color:#a6e22e">ClientAddressDialer</span>(<span style="color:#a6e22e">u</span>.<span style="color:#a6e22e">Port</span>())),
    <span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">WithDefaultCallOptions</span>(<span style="color:#a6e22e">grpc</span>.<span style="color:#a6e22e">MaxCallRecvMsgSize</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">MaxMsgRecvSize</span>)),
)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">WithStack</span>(<span style="color:#a6e22e">err</span>)
}

<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">conn</span>, <span style="color:#66d9ef">nil</span>

}


LNTOP

https://github.com/edouardparis/lntop lntop-v0.1.0


architecture


type Pool struct { conns chan Conn factory Factory mu sync.RWMutex timeout time.Duration }

func (p Pool) Get(ctx context.Context) (Conn, error) { p.mu.Lock() conns := p.conns p.mu.Unlock()

<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">conns</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">ErrClosed</span>
}

<span style="color:#a6e22e">conn</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">Conn</span>{
    <span style="color:#a6e22e">pool</span>: <span style="color:#a6e22e">p</span>,
}

<span style="color:#66d9ef">select</span> {
<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">conn</span> = <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">conns</span>:
<span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>():
    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">ErrTimeout</span>
}

<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">conn</span>.<span style="color:#a6e22e">ClientConn</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> <span style="color:#f92672">&amp;&amp;</span>
    <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">timeout</span> &gt; <span style="color:#ae81ff">0</span> <span style="color:#f92672">&amp;&amp;</span>
    <span style="color:#a6e22e">conn</span>.<span style="color:#a6e22e">usedAt</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">timeout</span>).<span style="color:#a6e22e">Before</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()) {
    <span style="color:#a6e22e">conn</span>.<span style="color:#a6e22e">ClientConn</span>.<span style="color:#a6e22e">Close</span>()
    <span style="color:#a6e22e">conn</span>.<span style="color:#a6e22e">ClientConn</span> = <span style="color:#66d9ef">nil</span>
}

<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">conn</span>.<span style="color:#a6e22e">ClientConn</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#a6e22e">conn</span>.<span style="color:#a6e22e">ClientConn</span>, <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">factory</span>()
    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
        <span style="color:#a6e22e">conns</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">Conn</span>{
            <span style="color:#a6e22e">pool</span>: <span style="color:#a6e22e">p</span>,
        }
    }
}

<span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">conn</span>, <span style="color:#a6e22e">err</span>

}

func (p *Pool) Close() { p.mu.Lock() conns := p.conns p.conns = nil p.mu.Unlock()

<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">conns</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#66d9ef">return</span>
}

close(<span style="color:#a6e22e">conns</span>)
<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">Capacity</span>(); <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
    <span style="color:#a6e22e">client</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">conns</span>
    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">ClientConn</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
        <span style="color:#66d9ef">continue</span>
    }
    <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">ClientConn</span>.<span style="color:#a6e22e">Close</span>()
}

}

import ( "time"

<span style="color:#e6db74">&#34;google.golang.org/grpc&#34;</span>

)

type Conn struct { grpc.ClientConn pool Pool usedAt time.Time }

func (c *Conn) Close() error { if c == nil { return nil } if c.ClientConn == nil { return ErrAlreadyClosed } if c.pool.IsClosed() { return ErrClosed }

<span style="color:#a6e22e">conn</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">Conn</span>{
    <span style="color:#a6e22e">pool</span>:       <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">pool</span>,
    <span style="color:#a6e22e">ClientConn</span>: <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">ClientConn</span>,
}
<span style="color:#66d9ef">select</span> {
<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">pool</span>.<span style="color:#a6e22e">conns</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">conn</span>:
<span style="color:#66d9ef">default</span>:
    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">ErrFullPool</span>
}
<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>

}


import ( "context" "sync"

<span style="color:#e6db74">&#34;github.com/edouardparis/lntop/events&#34;</span>
<span style="color:#e6db74">&#34;github.com/edouardparis/lntop/logging&#34;</span>
<span style="color:#e6db74">&#34;github.com/edouardparis/lntop/network&#34;</span>
<span style="color:#e6db74">&#34;github.com/edouardparis/lntop/network/models&#34;</span>

)

type PubSub struct { stop chan bool logger logging.Logger network network.Network wg sync.WaitGroup }

func New(logger logging.Logger, network network.Network) PubSub { return &PubSub{ logger: logger.With(logging.String("logger", "pubsub")), network: network, wg: &sync.WaitGroup{}, stop: make(chan bool), } }

<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">invoice</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">invoices</span> {
        <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Debug</span>(<span style="color:#e6db74">&#34;received invoice&#34;</span>, <span style="color:#a6e22e">logging</span>.<span style="color:#a6e22e">Object</span>(<span style="color:#e6db74">&#34;invoice&#34;</span>, <span style="color:#a6e22e">invoice</span>))
        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">invoice</span>.<span style="color:#a6e22e">Settled</span> {
            <span style="color:#a6e22e">sub</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">events</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">events</span>.<span style="color:#a6e22e">InvoiceSettled</span>)
        } <span style="color:#66d9ef">else</span> {
            <span style="color:#a6e22e">sub</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">events</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">events</span>.<span style="color:#a6e22e">InvoiceCreated</span>)
        }
    }
    <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
}()

<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
    <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">network</span>.<span style="color:#a6e22e">SubscribeInvoice</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">invoices</span>)
    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
        <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;SubscribeInvoice returned an error&#34;</span>, <span style="color:#a6e22e">logging</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">err</span>))
    }
    <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
}()

<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
    <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">stop</span>
    <span style="color:#a6e22e">cancel</span>()
    close(<span style="color:#a6e22e">invoices</span>)
    <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
}()

}

<span style="color:#a6e22e">cltInvoices</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">clt</span>.<span style="color:#a6e22e">SubscribeInvoices</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">lnrpc</span>.<span style="color:#a6e22e">InvoiceSubscription</span>{})
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
}

<span style="color:#66d9ef">for</span> {
    <span style="color:#66d9ef">select</span> {
    <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>():
        <span style="color:#66d9ef">break</span>
    <span style="color:#66d9ef">default</span>:
        <span style="color:#a6e22e">invoice</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cltInvoices</span>.<span style="color:#a6e22e">Recv</span>()
        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
            <span style="color:#a6e22e">st</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">status</span>.<span style="color:#a6e22e">FromError</span>(<span style="color:#a6e22e">err</span>)
            <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">ok</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">st</span>.<span style="color:#a6e22e">Code</span>() <span style="color:#f92672">==</span> <span style="color:#a6e22e">codes</span>.<span style="color:#a6e22e">Canceled</span> {
                <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Debug</span>(<span style="color:#e6db74">&#34;stopping subscribe invoice: context canceled&#34;</span>)
                <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
            }
            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
        }

        <span style="color:#a6e22e">channelInvoice</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">lookupInvoiceProtoToInvoice</span>(<span style="color:#a6e22e">invoice</span>)
    }
}

}


import ( "fmt" "log"

<span style="color:#e6db74">&#34;github.com/jroimartin/gocui&#34;</span>

)

func main() { g, err := gocui.NewGui(gocui.OutputNormal) if err != nil { log.Panicln(err) } defer g.Close()

<span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">SetManagerFunc</span>(<span style="color:#a6e22e">layout</span>)

<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">SetKeybinding</span>(<span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#a6e22e">gocui</span>.<span style="color:#a6e22e">KeyCtrlC</span>, <span style="color:#a6e22e">gocui</span>.<span style="color:#a6e22e">ModNone</span>, <span style="color:#a6e22e">quit</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Panicln</span>(<span style="color:#a6e22e">err</span>)
}

<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">MainLoop</span>(); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">gocui</span>.<span style="color:#a6e22e">ErrQuit</span> {
    <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Panicln</span>(<span style="color:#a6e22e">err</span>)
}

}

func layout(g *gocui.Gui) error { maxX, maxY := g.Size() if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil { if err != gocui.ErrUnknownView { return err } fmt.Fprintln(v, "Hello world!") } return nil }

func quit(g gocui.Gui, v gocui.View) error { return gocui.ErrQuit }


<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Debug</span>(<span style="color:#e6db74">&#34;Listening...&#34;</span>)
<span style="color:#a6e22e">refresh</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">fn</span> <span style="color:#f92672">...</span><span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span>) {
    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">fn</span> {
        <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fn</span>[<span style="color:#a6e22e">i</span>](<span style="color:#a6e22e">ctx</span>)
        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
            <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;failed&#34;</span>, <span style="color:#a6e22e">logging</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">err</span>))
        }
    }
    <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Update</span>(<span style="color:#66d9ef">func</span>(<span style="color:#f92672">*</span><span style="color:#a6e22e">gocui</span>.<span style="color:#a6e22e">Gui</span>) <span style="color:#66d9ef">error</span> { <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span> })
}

<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">event</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">sub</span> {
    <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">logger</span>.<span style="color:#a6e22e">Debug</span>(<span style="color:#e6db74">&#34;event received&#34;</span>,
        <span style="color:#a6e22e">logging</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;type&#34;</span>, <span style="color:#a6e22e">event</span>.<span style="color:#a6e22e">Type</span>))
    <span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">event</span>.<span style="color:#a6e22e">Type</span> {
    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">events</span>.<span style="color:#a6e22e">TransactionCreated</span>:
        <span style="color:#a6e22e">refresh</span>(
            <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">RefreshInfo</span>,
            <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">RefreshWalletBalance</span>,
            <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">RefreshTransactions</span>,
        )
    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">events</span>.<span style="color:#a6e22e">BlockReceived</span>:
        <span style="color:#a6e22e">refresh</span>(
            <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">RefreshInfo</span>,
            <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">RefreshTransactions</span>,

<span style="color:#f92672">...</span>

import ( "context" "sort" "sync"

<span style="color:#e6db74">&#34;github.com/edouardparis/lntop/network/models&#34;</span>

)

type TransactionsSort func(models.Transaction, models.Transaction) bool

type Transactions struct { current models.Transaction list []models.Transaction sort TransactionsSort mu sync.RWMutex }

func (t Transactions) Current() models.Transaction { return t.current }

func (t *Transactions) SetCurrent(index int) { t.current = t.Get(index) }

func (t Transactions) List() []models.Transaction { return t.list }

func (t *Transactions) Len() int { return len(t.list) }

func (t *Transactions) Swap(i, j int) { t.list[i], t.list[j] = t.list[j], t.list[i] }

func (t *Transactions) Less(i, j int) bool { return t.sort(t.list[i], t.list[j]) }

func (t *Transactions) Sort(s TransactionsSort) { if s == nil { return } t.sort = s sort.Sort(t) }

func (t Transactions) Get(index int) models.Transaction { if index < 0 || index > len(t.list)-1 { return nil }

<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">list</span>[<span style="color:#a6e22e">index</span>]

}

func (t Transactions) Contains(tx models.Transaction) bool { if tx == nil { return false } for i := range t.list { if t.list[i].TxHash == tx.TxHash { return true } } return false }

func (t Transactions) Add(tx models.Transaction) { t.mu.Lock() defer t.mu.Unlock() if t.Contains(tx) { return } t.list = append(t.list, tx) if t.sort != nil { sort.Sort(t) } }

func (t Transactions) Update(tx models.Transaction) { if tx == nil { return } if !t.Contains(tx) { t.Add(tx) return }

<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Lock</span>()
<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Unlock</span>()

<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">list</span> {
    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">list</span>[<span style="color:#a6e22e">i</span>].<span style="color:#a6e22e">TxHash</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">tx</span>.<span style="color:#a6e22e">TxHash</span> {
        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">list</span>[<span style="color:#a6e22e">i</span>].<span style="color:#a6e22e">NumConfirmations</span> = <span style="color:#a6e22e">tx</span>.<span style="color:#a6e22e">NumConfirmations</span>
        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">list</span>[<span style="color:#a6e22e">i</span>].<span style="color:#a6e22e">BlockHeight</span> = <span style="color:#a6e22e">tx</span>.<span style="color:#a6e22e">BlockHeight</span>
    }
}

<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">sort</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#a6e22e">sort</span>.<span style="color:#a6e22e">Sort</span>(<span style="color:#a6e22e">t</span>)
}

}

func (m *Models) RefreshTransactions(ctx context.Context) error { transactions, err := m.network.GetTransactions(ctx) if err != nil { return err }

<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">transactions</span> {
    <span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">Transactions</span>.<span style="color:#a6e22e">Update</span>(<span style="color:#a6e22e">transactions</span>[<span style="color:#a6e22e">i</span>])
}

<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>

}

import ( "fmt"

<span style="color:#e6db74">&#34;github.com/jroimartin/gocui&#34;</span>
<span style="color:#e6db74">&#34;golang.org/x/text/language&#34;</span>
<span style="color:#e6db74">&#34;golang.org/x/text/message&#34;</span>

<span style="color:#e6db74">&#34;github.com/edouardparis/lntop/ui/color&#34;</span>
<span style="color:#e6db74">&#34;github.com/edouardparis/lntop/ui/models&#34;</span>

)

const ( TRANSACTION = "transaction" TRANSACTION_HEADER = "transaction_header" TRANSACTION_FOOTER = "transaction_footer" )

type Transaction struct { view gocui.View transactions models.Transactions }

func (c Transaction) Name() string { return TRANSACTION }

func (c Transaction) Empty() bool { return c.transactions == nil }

func (c Transaction) Wrap(v gocui.View) View { c.view = v return c }

func (c Transaction) Origin() (int, int) { return c.view.Origin() }

func (c Transaction) Cursor() (int, int) { return c.view.Cursor() }

func (c Transaction) Speed() (int, int, int, int) { return 1, 1, 1, 1 }

func (c *Transaction) SetCursor(x, y int) error { return c.view.SetCursor(x, y) }

func (c *Transaction) SetOrigin(x, y int) error { return c.view.SetOrigin(x, y) }

func (c Transaction) Set(g gocui.Gui, x0, y0, x1, y1 int) error { header, err := g.SetView(TRANSACTION_HEADER, x0-1, y0, x1+2, y0+2) if err != nil { if err != gocui.ErrUnknownView { return err } } header.Frame = false header.BgColor = gocui.ColorGreen header.FgColor = gocui.ColorBlack | gocui.AttrBold header.Clear() fmt.Fprintln(header, "Transaction")

<span style="color:#a6e22e">v</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">SetView</span>(<span style="color:#a6e22e">TRANSACTION</span>, <span style="color:#a6e22e">x0</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">y0</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">x1</span><span style="color:#f92672">+</span><span style="color:#ae81ff">2</span>, <span style="color:#a6e22e">y1</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">gocui</span>.<span style="color:#a6e22e">ErrUnknownView</span> {
        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
    }
}
<span style="color:#a6e22e">v</span>.<span style="color:#a6e22e">Frame</span> = <span style="color:#66d9ef">false</span>
<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">view</span> = <span style="color:#a6e22e">v</span>
<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">display</span>()

<span style="color:#a6e22e">footer</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">SetView</span>(<span style="color:#a6e22e">TRANSACTION_FOOTER</span>, <span style="color:#a6e22e">x0</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">y1</span><span style="color:#f92672">-</span><span style="color:#ae81ff">2</span>, <span style="color:#a6e22e">x1</span>, <span style="color:#a6e22e">y1</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">gocui</span>.<span style="color:#a6e22e">ErrUnknownView</span> {
        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
    }
}
<span style="color:#a6e22e">footer</span>.<span style="color:#a6e22e">Frame</span> = <span style="color:#66d9ef">false</span>
<span style="color:#a6e22e">footer</span>.<span style="color:#a6e22e">BgColor</span> = <span style="color:#a6e22e">gocui</span>.<span style="color:#a6e22e">ColorCyan</span>
<span style="color:#a6e22e">footer</span>.<span style="color:#a6e22e">FgColor</span> = <span style="color:#a6e22e">gocui</span>.<span style="color:#a6e22e">ColorBlack</span>
<span style="color:#a6e22e">footer</span>.<span style="color:#a6e22e">Clear</span>()
<span style="color:#a6e22e">blackBg</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">color</span>.<span style="color:#a6e22e">Black</span>(<span style="color:#a6e22e">color</span>.<span style="color:#a6e22e">Background</span>)
<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintln</span>(<span style="color:#a6e22e">footer</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;%s%s %s%s %s%s %s%s&#34;</span>,
    <span style="color:#a6e22e">blackBg</span>(<span style="color:#e6db74">&#34;F1&#34;</span>), <span style="color:#e6db74">&#34;Help&#34;</span>,
    <span style="color:#a6e22e">blackBg</span>(<span style="color:#e6db74">&#34;F2&#34;</span>), <span style="color:#e6db74">&#34;Menu&#34;</span>,
    <span style="color:#a6e22e">blackBg</span>(<span style="color:#e6db74">&#34;Enter&#34;</span>), <span style="color:#e6db74">&#34;Transactions&#34;</span>,
    <span style="color:#a6e22e">blackBg</span>(<span style="color:#e6db74">&#34;F10&#34;</span>), <span style="color:#e6db74">&#34;Quit&#34;</span>,
))
<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>

}

func (c Transaction) Delete(g *gocui.Gui) error { err := g.DeleteView(TRANSACTION_HEADER) if err != nil { return err }

<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">DeleteView</span>(<span style="color:#a6e22e">TRANSACTION</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
}

<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">DeleteView</span>(<span style="color:#a6e22e">TRANSACTION_FOOTER</span>)

}

func (c *Transaction) display() { p := message.NewPrinter(language.English) v := c.view v.Clear() transaction := c.transactions.Current() green := color.Green() cyan := color.Cyan() fmt.Fprintln(v, green(" [ Transaction ]")) fmt.Fprintln(v, fmt.Sprintf("%s %s", cyan(" Date:"), transaction.Date.Format("15:04:05 Jan _2"))) fmt.Fprintln(v, p.Sprintf("%s %d", cyan(" Amount:"), transaction.Amount)) fmt.Fprintln(v, p.Sprintf("%s %d", cyan(" Fee:"), transaction.TotalFees)) fmt.Fprintln(v, p.Sprintf("%s %d", cyan(" BlockHeight:"), transaction.BlockHeight)) fmt.Fprintln(v, p.Sprintf("%s %d", cyan("NumConfirmations:"), transaction.NumConfirmations)) fmt.Fprintln(v, p.Sprintf("%s %s", cyan(" BlockHash:"), transaction.BlockHash)) fmt.Fprintln(v, fmt.Sprintf("%s %s", cyan(" TxHash:"), transaction.TxHash)) fmt.Fprintln(v, "") fmt.Fprintln(v, green("[ addresses ]")) for i := range transaction.DestAddresses { fmt.Fprintln(v, fmt.Sprintf("%s %s", cyan(" -"), transaction.DestAddresses[i])) }

}

func NewTransaction(transactions models.Transactions) Transaction { return &Transaction{transactions: transactions} }