mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 04:04:21 +01:00
* ark credits * rename "ecash" --> "ark credit" * rework note_test.go * NewFromString * create several notes * note repo: rename "push" to "add" * RegisterInputsForNextRoundRequest: move "notes" to field #3 * use uint64 as note ID * rename to voucher * add nostr notification * nostr notification test and fixes * bump badger to 4.3 * allow npub to be registered * rename poolTxID * add default relays * Update server/internal/config/config.go Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com> * fix RedeemVouchers test * notification = voucher * WASM wrappers * fix arkd voucher cmd * test_utils.go ignore gosec rule G101 * fix permissions * rename ALL to notes * add URI prefix * note.go : fix signature encoding * fix decode note.Data * Update server/internal/infrastructure/notifier/nostr/nostr.go Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> * Update pkg/client-sdk/wasm/browser/wrappers.go Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> * Update server/internal/infrastructure/notifier/nostr/nostr.go Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> * rework note and entity db + sqlite implementations * NOTIFICATION_PREFIX -> NOTE_URI_PREFIX * validate NOTE_URI_PREFIX * Update defaults to convenant-less mainnet (#2) * config: defaults to convenant-less tx builder * Drop env var for blockchain scanner --------- Co-authored-by: altafan <18440657+altafan@users.noreply.github.com> * add // before URI prefix * add URI prefix in admin CreateNote * Fixes * rework nonces encoding (#4) * rework nonces encoding * add a check in Musig2Nonce decode function * musig2_test: increase number of signers to 20 * musig2.json: add a test case with a 35 leaves tree * GetEventStream REST rework * fix round phases time intervals * [SDK] Use server-side streams in rest client * Fix history * make the URI optional * Updates * Fix settled txs in history * fix e2e test * go work sync in sdk unit test * fix signMessage in btc and liquid sdk wallets --------- Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com> Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com>
500 lines
10 KiB
Go
500 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
"gopkg.in/macaroon.v2"
|
|
)
|
|
|
|
// flags
|
|
var (
|
|
passwordFlag = &cli.StringFlag{
|
|
Name: "password",
|
|
Usage: "wallet password",
|
|
Required: true,
|
|
}
|
|
mnemonicFlag = &cli.StringFlag{
|
|
Name: "mnemonic",
|
|
Usage: "mnemonic from which restore the wallet",
|
|
}
|
|
gapLimitFlag = &cli.Uint64Flag{
|
|
Name: "addr-gap-limit",
|
|
Usage: "address gap limit for wallet restoration",
|
|
Value: 100,
|
|
}
|
|
amountFlag = &cli.UintFlag{
|
|
Name: "amount",
|
|
Usage: "amount of the note in satoshis",
|
|
Required: true,
|
|
}
|
|
quantityFlag = &cli.UintFlag{
|
|
Name: "quantity",
|
|
Usage: "quantity of notes to create",
|
|
Value: 1,
|
|
}
|
|
)
|
|
|
|
// commands
|
|
var (
|
|
walletCmd = &cli.Command{
|
|
Name: "wallet",
|
|
Usage: "Manage the Ark Server wallet",
|
|
Subcommands: append(
|
|
cli.Commands{},
|
|
walletStatusCmd,
|
|
walletCreateOrRestoreCmd,
|
|
walletUnlockCmd,
|
|
walletAddressCmd,
|
|
walletBalanceCmd,
|
|
createNoteCmd,
|
|
),
|
|
}
|
|
walletStatusCmd = &cli.Command{
|
|
Name: "status",
|
|
Usage: "Get info about the status of the wallet",
|
|
Action: walletStatusAction,
|
|
}
|
|
walletCreateOrRestoreCmd = &cli.Command{
|
|
Name: "create",
|
|
Usage: "Create or restore the wallet",
|
|
Action: walletCreateOrRestoreAction,
|
|
Flags: []cli.Flag{passwordFlag, mnemonicFlag, gapLimitFlag},
|
|
}
|
|
walletUnlockCmd = &cli.Command{
|
|
Name: "unlock",
|
|
Usage: "Unlock the wallet",
|
|
Action: walletUnlockAction,
|
|
Flags: []cli.Flag{passwordFlag},
|
|
}
|
|
walletAddressCmd = &cli.Command{
|
|
Name: "address",
|
|
Usage: "Generate a receiving address",
|
|
Action: walletAddressAction,
|
|
}
|
|
walletBalanceCmd = &cli.Command{
|
|
Name: "balance",
|
|
Usage: "Get the wallet balance",
|
|
Action: walletBalanceAction,
|
|
}
|
|
createNoteCmd = &cli.Command{
|
|
Name: "note",
|
|
Usage: "Create a credit note",
|
|
Action: createNoteAction,
|
|
Flags: []cli.Flag{amountFlag, quantityFlag},
|
|
}
|
|
)
|
|
|
|
var timeout = time.Minute
|
|
|
|
func walletStatusAction(ctx *cli.Context) error {
|
|
baseURL := ctx.String("url")
|
|
tlsCertPath := ctx.String("tls-cert-path")
|
|
if strings.Contains(baseURL, "http://") {
|
|
tlsCertPath = ""
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/v1/admin/wallet/status", baseURL)
|
|
status, err := getStatus(url, tlsCertPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(status)
|
|
return nil
|
|
}
|
|
|
|
func walletCreateOrRestoreAction(ctx *cli.Context) error {
|
|
baseURL := ctx.String("url")
|
|
password := ctx.String("password")
|
|
mnemonic := ctx.String("mnemonic")
|
|
gapLimit := ctx.Uint64("addr-gap-limit")
|
|
tlsCertPath := ctx.String("tls-cert-path")
|
|
if strings.Contains(baseURL, "http://") {
|
|
tlsCertPath = ""
|
|
}
|
|
|
|
if len(mnemonic) > 0 {
|
|
url := fmt.Sprintf("%s/v1/admin/wallet/restore", baseURL)
|
|
body := fmt.Sprintf(
|
|
`{"seed": "%s", "password": "%s", "gap_limit": %d}`,
|
|
mnemonic, password, gapLimit,
|
|
)
|
|
if _, err := post[struct{}](url, body, "", "", tlsCertPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println("wallet restored")
|
|
return nil
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/v1/admin/wallet/seed", baseURL)
|
|
seed, err := get[string](url, "seed", "", tlsCertPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
url = fmt.Sprintf("%s/v1/admin/wallet/create", baseURL)
|
|
body := fmt.Sprintf(
|
|
`{"seed": "%s", "password": "%s"}`, seed, password,
|
|
)
|
|
if _, err := post[struct{}](url, body, "", "", tlsCertPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(seed)
|
|
return nil
|
|
}
|
|
|
|
func walletUnlockAction(ctx *cli.Context) error {
|
|
baseURL := ctx.String("url")
|
|
password := ctx.String("password")
|
|
url := fmt.Sprintf("%s/v1/admin/wallet/unlock", baseURL)
|
|
body := fmt.Sprintf(`{"password": "%s"}`, password)
|
|
tlsCertPath := ctx.String("tls-cert-path")
|
|
if strings.Contains(baseURL, "http://") {
|
|
tlsCertPath = ""
|
|
}
|
|
|
|
if _, err := post[struct{}](url, body, "", "", tlsCertPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println("wallet unlocked")
|
|
return nil
|
|
}
|
|
|
|
func walletAddressAction(ctx *cli.Context) error {
|
|
baseURL := ctx.String("url")
|
|
var macaroon string
|
|
if !ctx.Bool("no-macaroon") {
|
|
macaroonPath := ctx.String("macaroon-path")
|
|
mac, err := getMacaroon(macaroonPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
macaroon = mac
|
|
}
|
|
tlsCertPath := ctx.String("tls-cert-path")
|
|
if strings.Contains(baseURL, "http://") {
|
|
tlsCertPath = ""
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/v1/admin/wallet/address", baseURL)
|
|
addr, err := get[string](url, "address", macaroon, tlsCertPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(addr)
|
|
return nil
|
|
}
|
|
|
|
func walletBalanceAction(ctx *cli.Context) error {
|
|
baseURL := ctx.String("url")
|
|
var macaroon string
|
|
if !ctx.Bool("no-macaroon") {
|
|
macaroonPath := ctx.String("macaroon-path")
|
|
mac, err := getMacaroon(macaroonPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
macaroon = mac
|
|
}
|
|
tlsCertPath := ctx.String("tls-cert-path")
|
|
if strings.Contains(baseURL, "http://") {
|
|
tlsCertPath = ""
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/v1/admin/wallet/balance", baseURL)
|
|
balance, err := getBalance(url, macaroon, tlsCertPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(balance)
|
|
return nil
|
|
}
|
|
|
|
func createNoteAction(ctx *cli.Context) error {
|
|
baseURL := ctx.String("url")
|
|
amount := ctx.Uint("amount")
|
|
quantity := ctx.Uint("quantity")
|
|
var macaroon string
|
|
if !ctx.Bool("no-macaroon") {
|
|
macaroonPath := ctx.String("macaroon-path")
|
|
mac, err := getMacaroon(macaroonPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
macaroon = mac
|
|
}
|
|
tlsCertPath := ctx.String("tls-cert-path")
|
|
if strings.Contains(baseURL, "http://") {
|
|
tlsCertPath = ""
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/v1/admin/note", baseURL)
|
|
body := fmt.Sprintf(`{"amount": %d, "quantity": %d}`, amount, quantity)
|
|
|
|
notes, err := post[[]string](url, body, "notes", macaroon, tlsCertPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, note := range notes {
|
|
fmt.Println(note)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func post[T any](url, body, key, macaroon, tlsCert string) (result T, err error) {
|
|
tlsConfig, err := getTLSConfig(tlsCert)
|
|
if err != nil {
|
|
return
|
|
}
|
|
req, err := http.NewRequest("POST", url, strings.NewReader(body))
|
|
if err != nil {
|
|
return
|
|
}
|
|
req.Header.Add("Content-Type", "application/json")
|
|
if len(macaroon) > 0 {
|
|
req.Header.Add("X-Macaroon", macaroon)
|
|
}
|
|
client := &http.Client{
|
|
Timeout: timeout,
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: tlsConfig,
|
|
},
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
buf, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
err = fmt.Errorf("failed to post: %s", string(buf))
|
|
return
|
|
}
|
|
if key == "" {
|
|
return
|
|
}
|
|
res := make(map[string]T)
|
|
if err = json.Unmarshal(buf, &res); err != nil {
|
|
return
|
|
}
|
|
|
|
result = res[key]
|
|
return
|
|
}
|
|
|
|
func get[T any](url, key, macaroon, tlsCert string) (result T, err error) {
|
|
tlsConfig, err := getTLSConfig(tlsCert)
|
|
if err != nil {
|
|
return
|
|
}
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
req.Header.Add("Content-Type", "application/json")
|
|
if len(macaroon) > 0 {
|
|
req.Header.Add("X-Macaroon", macaroon)
|
|
}
|
|
|
|
client := &http.Client{
|
|
Timeout: timeout,
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: tlsConfig,
|
|
},
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
buf, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
err = fmt.Errorf("failed to get: %s", string(buf))
|
|
return
|
|
}
|
|
|
|
res := make(map[string]T)
|
|
if err = json.Unmarshal(buf, &res); err != nil {
|
|
return
|
|
}
|
|
|
|
result = res[key]
|
|
return
|
|
}
|
|
|
|
type accountBalance struct {
|
|
Available string `json:"available"`
|
|
Locked string `json:"locked"`
|
|
}
|
|
|
|
func (b accountBalance) String() string {
|
|
return fmt.Sprintf(" available: %s\n locked: %s", b.Available, b.Locked)
|
|
}
|
|
|
|
type balance struct {
|
|
MainAccount accountBalance `json:"mainAccount"`
|
|
ConnectorsAccount accountBalance `json:"connectorsAccount"`
|
|
}
|
|
|
|
func (b balance) String() string {
|
|
return fmt.Sprintf(
|
|
"main account\n%s\nconnectors account\n%s",
|
|
b.MainAccount, b.ConnectorsAccount,
|
|
)
|
|
}
|
|
|
|
func getBalance(url, macaroon, tlsCert string) (*balance, error) {
|
|
tlsConfig, err := getTLSConfig(tlsCert)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Add("Content-Type", "application/json")
|
|
if len(macaroon) > 0 {
|
|
req.Header.Add("X-Macaroon", macaroon)
|
|
}
|
|
client := &http.Client{
|
|
Timeout: timeout,
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: tlsConfig,
|
|
},
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
buf, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
err = fmt.Errorf("%s", buf)
|
|
return nil, err
|
|
}
|
|
|
|
result := &balance{}
|
|
if err := json.Unmarshal(buf, result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
type status struct {
|
|
Initialized bool `json:"initialized"`
|
|
Unlocked bool `json:"unlocked"`
|
|
Synced bool `json:"synced"`
|
|
}
|
|
|
|
func (s status) String() string {
|
|
return fmt.Sprintf(
|
|
"initialized: %t\nunlocked: %t\nsynced: %t",
|
|
s.Initialized, s.Unlocked, s.Synced,
|
|
)
|
|
}
|
|
|
|
func getStatus(url, tlsCert string) (*status, error) {
|
|
tlsConfig, err := getTLSConfig(tlsCert)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Add("Content-Type", "application/json")
|
|
|
|
client := &http.Client{
|
|
Timeout: timeout,
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: tlsConfig,
|
|
},
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
buf, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
err = fmt.Errorf("failed to get status: %s", string(buf))
|
|
return nil, err
|
|
}
|
|
|
|
result := &status{}
|
|
if err := json.Unmarshal(buf, result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func getMacaroon(path string) (string, error) {
|
|
macBytes, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read macaroon %s: %s", path, err)
|
|
}
|
|
mac := &macaroon.Macaroon{}
|
|
if err := mac.UnmarshalBinary(macBytes); err != nil {
|
|
return "", fmt.Errorf("failed to parse macaroon %s: %s", path, err)
|
|
}
|
|
|
|
return hex.EncodeToString(macBytes), nil
|
|
}
|
|
|
|
func getTLSConfig(path string) (*tls.Config, error) {
|
|
if len(path) <= 0 {
|
|
return nil, nil
|
|
}
|
|
var buf []byte
|
|
buf, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
caCertPool := x509.NewCertPool()
|
|
if ok := caCertPool.AppendCertsFromPEM(buf); !ok {
|
|
return nil, fmt.Errorf("failed to parse tls cert")
|
|
}
|
|
|
|
return &tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
RootCAs: caCertPool,
|
|
}, nil
|
|
}
|