Add nurl and vainstr CLI tools (v0.39.0)
Some checks failed
Go / build-and-release (push) Has been cancelled
Some checks failed
Go / build-and-release (push) Has been cancelled
- Add nurl: NIP-98 authenticated HTTP client for testing owner APIs - Add vainstr: vanity npub generator using fast secp256k1 library - Update CLAUDE.md with documentation for both tools - Properly handle secp256k1 library loading via p8k.New() Files modified: - cmd/nurl/main.go: New NIP-98 HTTP client tool - cmd/vainstr/main.go: New vanity npub generator - CLAUDE.md: Added usage documentation for nurl and vainstr - go.mod/go.sum: Added go-arg dependency for vainstr - pkg/version/version: Bump to v0.39.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
11
CLAUDE.md
11
CLAUDE.md
@@ -21,6 +21,17 @@ go test -v -run TestName ./pkg/package
|
|||||||
# Web UI dev (hot reload)
|
# Web UI dev (hot reload)
|
||||||
ORLY_WEB_DISABLE=true ORLY_WEB_DEV_PROXY_URL=http://localhost:5173 ./orly &
|
ORLY_WEB_DISABLE=true ORLY_WEB_DEV_PROXY_URL=http://localhost:5173 ./orly &
|
||||||
cd app/web && bun run dev
|
cd app/web && bun run dev
|
||||||
|
|
||||||
|
# NIP-98 HTTP debugging (build: go build -o nurl ./cmd/nurl)
|
||||||
|
NOSTR_SECRET_KEY=nsec1... ./nurl https://relay.example.com/api/logs
|
||||||
|
NOSTR_SECRET_KEY=nsec1... ./nurl https://relay.example.com/api/logs/clear
|
||||||
|
./nurl help # Show usage
|
||||||
|
|
||||||
|
# Vanity npub generator (build: go build -o vainstr ./cmd/vainstr)
|
||||||
|
./vainstr mleku end # Find npub ending with "mleku"
|
||||||
|
./vainstr orly begin # Find npub starting with "orly" (after npub1)
|
||||||
|
./vainstr foo contain # Find npub containing "foo"
|
||||||
|
./vainstr --threads 4 xyz end # Use 4 threads
|
||||||
```
|
```
|
||||||
|
|
||||||
## Key Environment Variables
|
## Key Environment Variables
|
||||||
|
|||||||
201
cmd/nurl/main.go
Normal file
201
cmd/nurl/main.go
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
// Package main is a simple implementation of a cURL like tool that can do
|
||||||
|
// simple GET/POST operations on a HTTP server that understands NIP-98
|
||||||
|
// authentication, with the signing key found in an environment variable.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"lol.mleku.dev/chk"
|
||||||
|
"lol.mleku.dev/log"
|
||||||
|
|
||||||
|
"git.mleku.dev/mleku/nostr/encoders/bech32encoding"
|
||||||
|
"git.mleku.dev/mleku/nostr/encoders/hex"
|
||||||
|
"git.mleku.dev/mleku/nostr/httpauth"
|
||||||
|
"git.mleku.dev/mleku/nostr/interfaces/signer"
|
||||||
|
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
|
||||||
|
|
||||||
|
"next.orly.dev/pkg/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
const secEnv = "NOSTR_SECRET_KEY"
|
||||||
|
|
||||||
|
var userAgent = fmt.Sprintf("nurl/%s", strings.TrimSpace(version.V))
|
||||||
|
|
||||||
|
func fail(format string, a ...any) {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, format+"\n", a...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == "help" {
|
||||||
|
fmt.Printf(
|
||||||
|
`nurl help:
|
||||||
|
|
||||||
|
for nostr http using NIP-98 HTTP authentication:
|
||||||
|
|
||||||
|
nurl <url> [file]
|
||||||
|
|
||||||
|
if no file is given, the request will be processed as a HTTP GET.
|
||||||
|
|
||||||
|
* NIP-98 secret will be expected in the environment variable "%s"
|
||||||
|
- if absent, will not be added to the header.
|
||||||
|
- endpoint is assumed to not require it if absent.
|
||||||
|
- an error will be returned if it was needed.
|
||||||
|
|
||||||
|
output will be rendered to stdout
|
||||||
|
|
||||||
|
`, secEnv,
|
||||||
|
)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
fail(
|
||||||
|
`error: nurl requires minimum 1 arg: <url>
|
||||||
|
|
||||||
|
signing nsec (in bech32 format) is expected to be found in %s environment variable.
|
||||||
|
|
||||||
|
use "help" to get usage information
|
||||||
|
`, secEnv,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var sign signer.I
|
||||||
|
if sign, err = GetNIP98Signer(); err != nil {
|
||||||
|
log.W.Ln("no signer available:", err)
|
||||||
|
}
|
||||||
|
var ur *url.URL
|
||||||
|
if ur, err = url.Parse(os.Args[1]); chk.E(err) {
|
||||||
|
fail("invalid URL: `%s` error: `%s`", os.Args[1], err.Error())
|
||||||
|
}
|
||||||
|
log.T.S(ur)
|
||||||
|
if len(os.Args) == 2 {
|
||||||
|
if err = Get(ur, sign); chk.E(err) {
|
||||||
|
fail(err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = Post(os.Args[2], ur, sign); chk.E(err) {
|
||||||
|
fail(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNIP98Signer() (sign signer.I, err error) {
|
||||||
|
nsec := os.Getenv(secEnv)
|
||||||
|
var sk []byte
|
||||||
|
if len(nsec) == 0 {
|
||||||
|
err = fmt.Errorf("no bech32 secret key found in environment variable %s", secEnv)
|
||||||
|
return
|
||||||
|
} else if sk, err = bech32encoding.NsecToBytes([]byte(nsec)); chk.E(err) {
|
||||||
|
err = fmt.Errorf("failed to decode nsec: '%s'", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var s *p8k.Signer
|
||||||
|
if s, err = p8k.New(); chk.E(err) {
|
||||||
|
err = fmt.Errorf("failed to create signer: '%s'", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = s.InitSec(sk); chk.E(err) {
|
||||||
|
err = fmt.Errorf("failed to init signer: '%s'", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sign = s
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(ur *url.URL, sign signer.I) (err error) {
|
||||||
|
log.T.F("GET %s", ur.String())
|
||||||
|
var r *http.Request
|
||||||
|
if r, err = http.NewRequest("GET", ur.String(), nil); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.Header.Add("User-Agent", userAgent)
|
||||||
|
if sign != nil {
|
||||||
|
if err = httpauth.AddNIP98Header(
|
||||||
|
r, ur, "GET", "", sign, 0,
|
||||||
|
); chk.E(err) {
|
||||||
|
fail(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client := &http.Client{
|
||||||
|
CheckRedirect: func(
|
||||||
|
req *http.Request,
|
||||||
|
via []*http.Request,
|
||||||
|
) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var res *http.Response
|
||||||
|
if res, err = client.Do(r); chk.E(err) {
|
||||||
|
err = fmt.Errorf("request failed: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = io.Copy(os.Stdout, res.Body); chk.E(err) {
|
||||||
|
res.Body.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Post(f string, ur *url.URL, sign signer.I) (err error) {
|
||||||
|
log.T.F("POST %s", ur.String())
|
||||||
|
var contentLength int64
|
||||||
|
var payload io.ReadCloser
|
||||||
|
// get the file path parameters and optional hash
|
||||||
|
var fi os.FileInfo
|
||||||
|
if fi, err = os.Stat(f); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var b []byte
|
||||||
|
if b, err = os.ReadFile(f); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hb := sha256.Sum256(b)
|
||||||
|
h := hex.Enc(hb[:])
|
||||||
|
contentLength = fi.Size()
|
||||||
|
if payload, err = os.Open(f); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.T.F("opened file %s hash %s", f, h)
|
||||||
|
var r *http.Request
|
||||||
|
r = &http.Request{
|
||||||
|
Method: "POST",
|
||||||
|
URL: ur,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: make(http.Header),
|
||||||
|
Body: payload,
|
||||||
|
ContentLength: contentLength,
|
||||||
|
Host: ur.Host,
|
||||||
|
}
|
||||||
|
r.Header.Add("User-Agent", userAgent)
|
||||||
|
if sign != nil {
|
||||||
|
if err = httpauth.AddNIP98Header(
|
||||||
|
r, ur, "POST", string(h), sign, 0,
|
||||||
|
); chk.E(err) {
|
||||||
|
fail(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.GetBody = func() (rc io.ReadCloser, err error) {
|
||||||
|
rc = payload
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := &http.Client{}
|
||||||
|
var res *http.Response
|
||||||
|
if res, err = client.Do(r); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if _, err = io.Copy(os.Stdout, res.Body); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
235
cmd/vainstr/main.go
Normal file
235
cmd/vainstr/main.go
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
// Package main is a simple nostr key miner that uses the fast bitcoin secp256k1
|
||||||
|
// C library to derive npubs with specified prefix/infix/suffix strings present.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"lol.mleku.dev/chk"
|
||||||
|
"lol.mleku.dev/log"
|
||||||
|
|
||||||
|
"git.mleku.dev/mleku/nostr/crypto/ec/bech32"
|
||||||
|
"git.mleku.dev/mleku/nostr/encoders/bech32encoding"
|
||||||
|
"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
|
||||||
|
|
||||||
|
"github.com/alexflint/go-arg"
|
||||||
|
)
|
||||||
|
|
||||||
|
var prefix = append(bech32encoding.PubHRP, '1')
|
||||||
|
|
||||||
|
const (
|
||||||
|
PositionBeginning = iota
|
||||||
|
PositionContains
|
||||||
|
PositionEnding
|
||||||
|
)
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
sec []byte
|
||||||
|
npub []byte
|
||||||
|
pub []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var args struct {
|
||||||
|
String string `arg:"positional" help:"the string you want to appear in the npub"`
|
||||||
|
Position string `arg:"positional" default:"end" help:"[begin|contain|end] default: end"`
|
||||||
|
Threads int `help:"number of threads to mine with - defaults to using all CPU threads available"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
arg.MustParse(&args)
|
||||||
|
if args.String == "" {
|
||||||
|
_, _ = fmt.Fprintln(
|
||||||
|
os.Stderr,
|
||||||
|
`Usage: vainstr [--threads THREADS] [STRING [POSITION]]
|
||||||
|
|
||||||
|
Positional arguments:
|
||||||
|
STRING the string you want to appear in the npub
|
||||||
|
POSITION [begin|contain|end] default: end
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--threads THREADS number of threads to mine with - defaults to using all CPU threads available
|
||||||
|
--help, -h display this help and exit`,
|
||||||
|
)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
var where int
|
||||||
|
canonical := strings.ToLower(args.Position)
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(canonical, "begin"):
|
||||||
|
where = PositionBeginning
|
||||||
|
case strings.Contains(canonical, "contain"):
|
||||||
|
where = PositionContains
|
||||||
|
case strings.HasSuffix(canonical, "end"):
|
||||||
|
where = PositionEnding
|
||||||
|
}
|
||||||
|
if args.Threads == 0 {
|
||||||
|
args.Threads = runtime.NumCPU()
|
||||||
|
}
|
||||||
|
if err := Vanity(args.String, where, args.Threads); chk.E(err) {
|
||||||
|
log.F.F("error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Vanity(str string, where int, threads int) (err error) {
|
||||||
|
// check the string has valid bech32 ciphers
|
||||||
|
for i := range str {
|
||||||
|
wrong := true
|
||||||
|
for j := range bech32.Charset {
|
||||||
|
if str[i] == bech32.Charset[j] {
|
||||||
|
wrong = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if wrong {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"found invalid character '%c' only ones from '%s' allowed\n",
|
||||||
|
str[i], bech32.Charset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
started := time.Now()
|
||||||
|
quit := make(chan struct{})
|
||||||
|
resC := make(chan Result)
|
||||||
|
|
||||||
|
// Handle interrupt
|
||||||
|
go func() {
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
<-c
|
||||||
|
close(quit)
|
||||||
|
log.I.Ln("\rinterrupt signal received")
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var counter int64
|
||||||
|
for i := 0; i < threads; i++ {
|
||||||
|
log.D.F("starting up worker %d", i)
|
||||||
|
go mine(str, where, quit, resC, &wg, &counter)
|
||||||
|
}
|
||||||
|
tick := time.NewTicker(time.Second * 5)
|
||||||
|
var res Result
|
||||||
|
out:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tick.C:
|
||||||
|
workingFor := time.Now().Sub(started)
|
||||||
|
wm := workingFor % time.Second
|
||||||
|
workingFor -= wm
|
||||||
|
fmt.Printf(
|
||||||
|
" working for %v, attempts %d",
|
||||||
|
workingFor, atomic.LoadInt64(&counter),
|
||||||
|
)
|
||||||
|
case r := <-resC:
|
||||||
|
// one of the workers found the solution
|
||||||
|
res = r
|
||||||
|
// tell the others to stop
|
||||||
|
close(quit)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for all workers to stop
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"\r# generated in %d attempts using %d threads, taking %v ",
|
||||||
|
atomic.LoadInt64(&counter), args.Threads, time.Now().Sub(started),
|
||||||
|
)
|
||||||
|
fmt.Printf(
|
||||||
|
"\nHSEC = %s\nHPUB = %s\n",
|
||||||
|
hex.EncodeToString(res.sec),
|
||||||
|
hex.EncodeToString(res.pub),
|
||||||
|
)
|
||||||
|
nsec, _ := bech32encoding.BinToNsec(res.sec)
|
||||||
|
fmt.Printf("NSEC = %s\nNPUB = %s\n", nsec, res.npub)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mine(
|
||||||
|
str string, where int, quit <-chan struct{}, resC chan Result, wg *sync.WaitGroup,
|
||||||
|
counter *int64,
|
||||||
|
) {
|
||||||
|
wg.Add(1)
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
var r Result
|
||||||
|
var e error
|
||||||
|
found := false
|
||||||
|
out:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-quit:
|
||||||
|
if found {
|
||||||
|
// send back the result
|
||||||
|
log.D.Ln("sending back result")
|
||||||
|
resC <- r
|
||||||
|
log.D.Ln("sent")
|
||||||
|
} else {
|
||||||
|
log.D.Ln("other thread found it")
|
||||||
|
}
|
||||||
|
break out
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
atomic.AddInt64(counter, 1)
|
||||||
|
r.sec, r.pub, e = Gen()
|
||||||
|
if e != nil {
|
||||||
|
log.E.Ln("error generating key: '%v' worker stopping", e)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
if r.npub, e = bech32encoding.BinToNpub(r.pub); e != nil {
|
||||||
|
log.E.Ln("fatal error generating npub: %s", e)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
fmt.Printf("\rgenerating key: %s", r.npub)
|
||||||
|
switch where {
|
||||||
|
case PositionBeginning:
|
||||||
|
if bytes.HasPrefix(r.npub, append(prefix, []byte(str)...)) {
|
||||||
|
found = true
|
||||||
|
// Signal quit by sending result
|
||||||
|
select {
|
||||||
|
case resC <- r:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case PositionEnding:
|
||||||
|
if bytes.HasSuffix(r.npub, []byte(str)) {
|
||||||
|
found = true
|
||||||
|
select {
|
||||||
|
case resC <- r:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case PositionContains:
|
||||||
|
if bytes.Contains(r.npub, []byte(str)) {
|
||||||
|
found = true
|
||||||
|
select {
|
||||||
|
case resC <- r:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Gen() (skb, pkb []byte, err error) {
|
||||||
|
sign, err := p8k.New()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err = sign.Generate(); chk.E(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
skb, pkb = sign.Sec(), sign.Pub()
|
||||||
|
return
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
@@ -31,6 +31,8 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect
|
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect
|
||||||
|
github.com/alexflint/go-arg v1.6.1 // indirect
|
||||||
|
github.com/alexflint/go-scalar v1.2.0 // indirect
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
|
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
|
||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
||||||
github.com/bytedance/sonic v1.13.1 // indirect
|
github.com/bytedance/sonic v1.13.1 // indirect
|
||||||
|
|||||||
5
go.sum
5
go.sum
@@ -6,6 +6,10 @@ github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 h1:ClzzXMDDuUbWfNN
|
|||||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3/go.mod h1:we0YA5CsBbH5+/NUzC/AlMmxaDtWlXeNsqrwXjTzmzA=
|
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3/go.mod h1:we0YA5CsBbH5+/NUzC/AlMmxaDtWlXeNsqrwXjTzmzA=
|
||||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||||
|
github.com/alexflint/go-arg v1.6.1 h1:uZogJ6VDBjcuosydKgvYYRhh9sRCusjOvoOLZopBlnA=
|
||||||
|
github.com/alexflint/go-arg v1.6.1/go.mod h1:nQ0LFYftLJ6njcaee0sU+G0iS2+2XJQfA8I062D0LGc=
|
||||||
|
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
||||||
|
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||||
github.com/aperturerobotics/go-indexeddb v0.2.3 h1:DfquIk9YEZjWD/lJyBWZWGCtRga43/a96bx0Ulv9VhQ=
|
github.com/aperturerobotics/go-indexeddb v0.2.3 h1:DfquIk9YEZjWD/lJyBWZWGCtRga43/a96bx0Ulv9VhQ=
|
||||||
github.com/aperturerobotics/go-indexeddb v0.2.3/go.mod h1:JV1XngOCCui7zrMSyRz+Wvz00nUSfotRKZqJzWpl5fQ=
|
github.com/aperturerobotics/go-indexeddb v0.2.3/go.mod h1:JV1XngOCCui7zrMSyRz+Wvz00nUSfotRKZqJzWpl5fQ=
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
|
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
|
||||||
@@ -126,6 +130,7 @@ github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERA
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v0.38.1
|
v0.39.0
|
||||||
|
|||||||
Reference in New Issue
Block a user