refactor: Simplify NWC protocol structures and update method handling

- cmd/lerproxy/app/bufpool.go
  - Removed bufferPool-related code and `Pool` struct

- cmd/nwcclient/main.go
  - Renamed `Method` to `Capability` for clarity in method handling

- pkg/utils/values/values.go
  - Added utility functions to return pointers for various types

- pkg/utils/pointers/pointers.go
  - Revised documentation to reference `utils/values` package for pointer utilities

- pkg/protocol/nwc/types.go
  - Replaced redundant types and structures with simplified versions
  - Introduced dedicated structs for `MakeInvoice`, `PayInvoice`, and related results
  - Refactored `Transaction` and its fields for consistent type usage

- pkg/protocol/nwc/uri.go
  - Added `ParseConnectionURI` function for URI parsing and validation

- pkg/protocol/nwc/client.go
  - Refactored `Client` struct to improve key management and relay handling
  - Introduced `Request` struct for generic method invocation payloads
This commit is contained in:
2025-08-05 20:18:32 +01:00
parent 996fb3aeb7
commit b74f4757e7
11 changed files with 501 additions and 1036 deletions

View File

@@ -1,15 +0,0 @@
package app
import "sync"
var bufferPool = &sync.Pool{
New: func() interface{} {
buf := make([]byte, 32*1024)
return &buf
},
}
type Pool struct{}
func (bp Pool) Get() []byte { return *(bufferPool.Get().(*[]byte)) }
func (bp Pool) Put(b []byte) { bufferPool.Put(&b) }

View File

@@ -45,7 +45,7 @@ func main() {
// Parse connection URL and method
connectionURL := os.Args[1]
methodStr := os.Args[2]
method := nwc.Method(methodStr)
method := nwc.Capability(methodStr)
// Parse the wallet connect URL
opts, err := nwc.ParseWalletConnectURL(connectionURL)

View File

@@ -158,8 +158,8 @@ func Decrypt(b64ciphertextWrapped, conversationKey []byte) (
return
}
// GenerateConversationKey performs an ECDH key generation hashed with the nip-44-v2 using hkdf.
func GenerateConversationKey(pkh, skh string) (ck []byte, err error) {
// GenerateConversationKeyFromHex performs an ECDH key generation hashed with the nip-44-v2 using hkdf.
func GenerateConversationKeyFromHex(pkh, skh string) (ck []byte, err error) {
if skh >= "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" ||
skh == "0000000000000000000000000000000000000000000000000000000000000000" {
err = errorf.E(
@@ -184,6 +184,17 @@ func GenerateConversationKey(pkh, skh string) (ck []byte, err error) {
return
}
func GenerateConversationKeyWithSigner(sign signer.I, pk []byte) (
ck []byte, err error,
) {
var shared []byte
if shared, err = sign.ECDH(pk); chk.E(err) {
return
}
ck = hkdf.Extract(sha256.New, shared, []byte("nip44-v2"))
return
}
func encrypt(key, nonce, message []byte) (dst []byte, err error) {
var cipher *chacha20.Cipher
if cipher, err = chacha20.NewUnauthenticatedCipher(key, nonce); chk.E(err) {

View File

@@ -47,7 +47,9 @@ func assertCryptPriv(
return
}
expectedBytes = []byte(expected)
if ok = assert.Equalf(t, string(expectedBytes), string(actualBytes), "wrong encryption"); !ok {
if ok = assert.Equalf(
t, string(expectedBytes), string(actualBytes), "wrong encryption",
); !ok {
return
}
decrypted, err = Decrypt(expectedBytes, k1)
@@ -62,8 +64,8 @@ func assertDecryptFail(
) {
var (
k1, ciphertextBytes []byte
ok bool
err error
ok bool
err error
)
k1, err = hex.Dec(conversationKey)
if ok = assert.NoErrorf(
@@ -79,7 +81,7 @@ func assertDecryptFail(
func assertConversationKeyFail(
t *testing.T, priv string, pub string, msg string,
) {
_, err := GenerateConversationKey(pub, priv)
_, err := GenerateConversationKeyFromHex(pub, priv)
assert.ErrorContains(t, err, msg)
}
@@ -98,7 +100,7 @@ func assertConversationKeyGeneration(
); !ok {
return false
}
actualConversationKey, err = GenerateConversationKey(pub, priv)
actualConversationKey, err = GenerateConversationKeyFromHex(pub, priv)
if ok = assert.NoErrorf(
t, err, "conversation key generation failed: %v", err,
); !ok {
@@ -1312,7 +1314,7 @@ func TestMaxLength(t *testing.T) {
pub2, _ := keys.GetPublicKeyHex(string(sk2))
salt := make([]byte, 32)
rand.Read(salt)
conversationKey, _ := GenerateConversationKey(pub2, string(sk1))
conversationKey, _ := GenerateConversationKeyFromHex(pub2, string(sk1))
plaintext := strings.Repeat("a", MaxPlaintextSize)
plaintextBytes := []byte(plaintext)
encrypted, err := Encrypt(
@@ -1366,7 +1368,9 @@ func assertCryptPub(
return
}
expectedBytes = []byte(expected)
if ok = assert.Equalf(t, string(expectedBytes), string(actualBytes), "wrong encryption"); !ok {
if ok = assert.Equalf(
t, string(expectedBytes), string(actualBytes), "wrong encryption",
); !ok {
return
}
decrypted, err = Decrypt(expectedBytes, k1)

View File

@@ -275,9 +275,9 @@ var (
WalletRequest = &T{23194}
// NWCWalletResponse is an event type that...
NWCWalletResponse = &T{23195}
WalletResponse = NWCWalletResponse
WalletResponse = &T{23195}
NWCNotification = &T{23196}
WalletNotification = NWCNotification
WalletNotification = &T{23196}
// NostrConnect is an event type that...
NostrConnect = &T{24133}
HTTPAuth = &T{27235}

View File

@@ -1,15 +1,14 @@
package nwc
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"orly.dev/pkg/crypto/encryption"
"orly.dev/pkg/crypto/p256k"
"orly.dev/pkg/encoders/event"
"orly.dev/pkg/encoders/filter"
"orly.dev/pkg/encoders/filters"
"orly.dev/pkg/encoders/hex"
"orly.dev/pkg/encoders/kind"
"orly.dev/pkg/encoders/kinds"
"orly.dev/pkg/encoders/tag"
@@ -19,596 +18,265 @@ import (
"orly.dev/pkg/protocol/ws"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/context"
"orly.dev/pkg/utils/log"
"strings"
"sync"
"orly.dev/pkg/utils/values"
"time"
)
// Options represents options for a NWC client
type Options struct {
RelayURL string
Secret signer.I
WalletPubkey []byte
Lud16 string
}
// Client represents a NWC client
type Client struct {
options Options
relay *ws.Client
mu sync.Mutex
pool *ws.Pool
relays []string
clientSecretKey signer.I
walletPublicKey []byte
conversationKey []byte // nip44
}
// ParseWalletConnectURL parses a wallet connect URL
func ParseWalletConnectURL(walletConnectURL string) (opts *Options, err error) {
if !strings.HasPrefix(walletConnectURL, "nostr+walletconnect://") {
return nil, fmt.Errorf("unexpected scheme. Should be nostr+walletconnect://")
}
// Parse URL
colonIndex := strings.Index(walletConnectURL, ":")
if colonIndex == -1 {
err = fmt.Errorf("invalid URL format")
type Request struct {
Method string `json:"method"`
Params any `json:"params"`
}
type ResponseError struct {
Code string `json:"code"`
Message string `json:"message"`
}
func (err *ResponseError) Error() string {
return fmt.Sprintf("%s %s", err.Code, err.Message)
}
type Response struct {
ResultType string `json:"result_type"`
Error *ResponseError `json:"error"`
Result any `json:"result"`
}
func NewClient(c context.T, connectionURI string) (cl *Client, err error) {
var parts *ConnectionParams
if parts, err = ParseConnectionURI(connectionURI); chk.E(err) {
return
}
walletConnectURL = walletConnectURL[colonIndex+1:]
if strings.HasPrefix(walletConnectURL, "//") {
walletConnectURL = walletConnectURL[2:]
}
walletConnectURL = "https://" + walletConnectURL
var u *url.URL
if u, err = url.Parse(walletConnectURL); chk.E(err) {
err = fmt.Errorf("failed to parse URL: %w", err)
clientKey := &p256k.Signer{}
if err = clientKey.InitSec(parts.clientSecretKey); chk.E(err) {
return
}
// Get wallet pubkey
walletPubkey := u.Host
if len(walletPubkey) != 64 {
err = fmt.Errorf("incorrect wallet pubkey found in auth string")
var ck []byte
if ck, err = encryption.GenerateConversationKeyWithSigner(
clientKey,
parts.walletPublicKey,
); chk.E(err) {
return
}
var pk []byte
if pk, err = hex.Dec(walletPubkey); chk.E(err) {
err = fmt.Errorf("failed to decode pubkey: %w", err)
return
}
// Get relay URL
relayURL := u.Query().Get("relay")
if relayURL == "" {
return nil, fmt.Errorf("no relay URL found in auth string")
}
// Get secret
secret := u.Query().Get("secret")
if secret == "" {
return nil, fmt.Errorf("no secret found in auth string")
}
var sk []byte
if sk, err = hex.Dec(secret); chk.E(err) {
return
}
sign := &p256k.Signer{}
if err = sign.InitSec(sk); chk.E(err) {
return
}
opts = &Options{
RelayURL: relayURL,
Secret: sign,
WalletPubkey: pk,
cl = &Client{
pool: ws.NewPool(c),
relays: parts.relays,
clientSecretKey: clientKey,
walletPublicKey: parts.walletPublicKey,
conversationKey: ck,
}
return
}
// NewNWCClient creates a new NWC client
func NewNWCClient(options *Options) (cl *Client, err error) {
if options.RelayURL == "" {
err = fmt.Errorf("missing relay URL")
return
type rpcOptions struct {
timeout *time.Duration
}
func (cl *Client) RPC(
c context.T, method Capability, params, result any, opts *rpcOptions,
) (err error) {
timeout := time.Duration(10)
if opts == nil && opts.timeout == nil {
timeout = *opts.timeout
}
if options.Secret == nil {
err = fmt.Errorf("missing secret")
return
}
if options.WalletPubkey == nil {
err = fmt.Errorf("missing wallet pubkey")
return
}
return &Client{
options: Options{
RelayURL: options.RelayURL,
Secret: options.Secret,
WalletPubkey: options.WalletPubkey,
Lud16: options.Lud16,
ctx, cancel := context.Timeout(c, timeout)
defer cancel()
var req []byte
if req, err = json.Marshal(
Request{
Method: string(method),
Params: params,
},
}, nil
}
// NostrWalletConnectURL returns the nostr wallet connect URL
func (c *Client) NostrWalletConnectURL() string {
return c.GetNostrWalletConnectURL(true)
}
// GetNostrWalletConnectURL returns the nostr wallet connect URL
func (c *Client) GetNostrWalletConnectURL(includeSecret bool) string {
params := url.Values{}
params.Add("relay", c.options.RelayURL)
if includeSecret {
params.Add("secret", hex.Enc(c.options.Secret.Sec()))
); chk.E(err) {
return
}
return fmt.Sprintf(
"nostr+walletconnect://%s?%s", c.options.WalletPubkey, params.Encode(),
var content []byte
if content, err = encryption.Encrypt(req, cl.conversationKey); chk.E(err) {
return
}
ev := &event.E{
Content: content,
CreatedAt: timestamp.Now(),
Kind: kind.WalletRequest,
Tags: tags.New(
tag.New([]byte("p"), cl.walletPublicKey),
tag.New("encryption", "nip44_v2"),
),
}
if err = ev.Sign(cl.clientSecretKey); chk.E(err) {
return
}
hasWorked := make(chan struct{})
evs := cl.pool.SubMany(
c, cl.relays, &filters.T{
F: []*filter.F{
{
Limit: values.ToUintPointer(1),
Kinds: kinds.New(kind.WalletRequest),
Authors: tag.New(cl.walletPublicKey),
Tags: tags.New(tag.New([]byte("#e"), ev.ID)),
},
},
},
)
}
// Connected returns whether the client is connected to the relay
func (c *Client) Connected() bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.relay != nil && c.relay.IsConnected()
}
// GetPublicKey returns the client's public key
func (c *Client) GetPublicKey() (pubkey []byte, err error) {
pubkey = c.options.Secret.Pub()
return
}
// Close closes the relay connection
func (c *Client) Close() {
c.mu.Lock()
defer c.mu.Unlock()
if c.relay != nil {
c.relay.Close()
c.relay = nil
}
}
// Encrypt encrypts content for a pubkey
func (c *Client) encrypt(pubkey, content []byte) (
cipherText []byte, err error,
) {
var sharedSecret []byte
if sharedSecret, err = c.options.Secret.ECDH(pubkey); chk.E(err) {
return
}
cipherText, err = encryption.EncryptNip4(content, sharedSecret)
return
}
// Decrypt decrypts content from a pubkey
func (c *Client) decrypt(pubkey, content []byte) (plaintext []byte, err error) {
var sharedSecret []byte
if sharedSecret, err = c.options.Secret.ECDH(pubkey); chk.E(err) {
return
}
plaintext, err = encryption.DecryptNip4(content, sharedSecret)
return
}
// GetInfo gets wallet info
func (c *Client) GetInfo() (response *GetInfoResponse, err error) {
var result []byte
if result, err = c.executeRequest(GetInfo, nil); chk.E(err) {
return
}
response = &GetInfoResponse{}
if err = json.Unmarshal(result, response); err != nil {
err = fmt.Errorf("failed to unmarshal response: %w", err)
return
}
return
}
// GetBudget gets wallet budget
func (c *Client) GetBudget() (response *GetBudgetResponse, err error) {
var result []byte
result, err = c.executeRequest(GetBudget, nil)
if err != nil {
return nil, err
}
response = &GetBudgetResponse{}
if err = json.Unmarshal(result, response); err != nil {
err = fmt.Errorf("failed to unmarshal response: %w", err)
return
}
return
}
// GetBalance gets wallet balance
func (c *Client) GetBalance() (response *GetBalanceResponse, err error) {
var result []byte
if result, err = c.executeRequest(GetBalance, nil); chk.E(err) {
return
}
response = &GetBalanceResponse{}
if err = json.Unmarshal(result, response); err != nil {
err = fmt.Errorf("failed to unmarshal response: %w", err)
return
}
return
}
// PayInvoice pays an invoice
func (c *Client) PayInvoice(request *PayInvoiceRequest) (
response *PayResponse, err error,
) {
var result []byte
result, err = c.executeRequest(PayInvoice, request)
if err != nil {
return nil, err
}
response = &PayResponse{}
if err = json.Unmarshal(result, response); err != nil {
err = fmt.Errorf("failed to unmarshal response: %w", err)
return
}
return
}
// PayKeysend sends a keysend payment
func (c *Client) PayKeysend(request *PayKeysendRequest) (
response *PayResponse, err error,
) {
var result []byte
if result, err = c.executeRequest(PayKeysend, request); chk.E(err) {
return
}
response = &PayResponse{}
if err = json.Unmarshal(result, response); err != nil {
err = fmt.Errorf("failed to unmarshal response: %w", err)
return
}
return
}
// MakeInvoice creates an invoice
func (c *Client) MakeInvoice(request *MakeInvoiceRequest) (
response *Transaction, err error,
) {
var result []byte
if result, err = c.executeRequest(MakeInvoice, request); chk.E(err) {
return
}
response = &Transaction{}
if err = json.Unmarshal(result, response); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return
}
// LookupInvoice looks up an invoice
func (c *Client) LookupInvoice(request *LookupInvoiceRequest) (
response *Transaction, err error,
) {
var result []byte
if result, err = c.executeRequest(LookupInvoice, request); chk.E(err) {
return
}
response = &Transaction{}
if err = json.Unmarshal(result, response); err != nil {
err = fmt.Errorf("failed to unmarshal response: %w", err)
return
}
return
}
// ListTransactions lists transactions
func (c *Client) ListTransactions(request *ListTransactionsRequest) (
response *ListTransactionsResponse, err error,
) {
var result []byte
if result, err = c.executeRequest(ListTransactions, request); chk.E(err) {
return
}
response = &ListTransactionsResponse{}
if err = json.Unmarshal(result, response); chk.E(err) {
err = fmt.Errorf("failed to unmarshal response: %w", err)
return
}
return
}
// SignMessage signs a message
func (c *Client) SignMessage(request *SignMessageRequest) (
response *SignMessageResponse, err error,
) {
var result []byte
if result, err = c.executeRequest(SignMessage, request); chk.E(err) {
return
}
response = &SignMessageResponse{}
if err = json.Unmarshal(result, response); err != nil {
err = fmt.Errorf("failed to unmarshal response: %w", err)
return
}
return
}
// NotificationHandler is a function that handles notifications
type NotificationHandler func(*Notification)
// SubscribeNotifications subscribes to notifications
func (c *Client) SubscribeNotifications(
handler NotificationHandler,
notificationTypes []NotificationType,
) (stop func(), err error) {
if handler == nil {
err = fmt.Errorf("missing notification handler")
return
}
ctx, cancel := context.Cancel(context.Bg())
doneCh := make(chan struct{})
stop = func() {
cancel()
<-doneCh
}
go func() {
defer close(doneCh)
for {
for _, u := range cl.relays {
go func(u string) {
var relay *ws.Client
if relay, err = cl.pool.EnsureRelay(u); chk.E(err) {
return
}
if err = relay.Publish(c, ev); chk.E(err) {
return
}
select {
case hasWorked <- struct{}{}:
case <-ctx.Done():
err = fmt.Errorf("context canceled waiting for request send")
return
default:
// Check connection
if err := c.checkConnected(); err != nil {
time.Sleep(1 * time.Second)
continue
}
// Get client pubkey
var clientPubkey []byte
if clientPubkey, err = c.GetPublicKey(); chk.E(err) {
time.Sleep(1 * time.Second)
continue
}
// Subscribe to events
f := &filter.F{
Kinds: kinds.New(kind.WalletResponse),
Authors: tag.New(c.options.WalletPubkey),
Tags: tags.New(tag.New([]byte("#p"), clientPubkey)),
}
var sub *ws.Subscription
if sub, err = c.relay.Subscribe(
context.Bg(), &filters.T{
F: []*filter.F{f},
},
); chk.E(err) {
time.Sleep(1 * time.Second)
continue
}
// Handle events
for {
select {
case <-ctx.Done():
sub.Close()
return
case ev := <-sub.Events:
// Decrypt content
var decryptedContent []byte
if decryptedContent, err = c.decrypt(
c.options.WalletPubkey, ev.Content,
); chk.E(err) {
log.E.F(
"Failed to decrypt event content: %v\n", err,
)
continue
}
// Parse notification
notification := &Notification{}
if err = json.Unmarshal(
decryptedContent, notification,
); chk.E(err) {
log.E.F(
"Failed to parse notification: %v\n", err,
)
continue
}
// Check if notification type is requested
if len(notificationTypes) > 0 {
found := false
for _, t := range notificationTypes {
if notification.NotificationType == t {
found = true
break
}
}
if !found {
continue
}
}
// Handle notification
handler(notification)
case <-sub.EndOfStoredEvents:
// Ignore
}
}
}
}(u)
}
select {
case <-hasWorked:
// continue
case <-ctx.Done():
err = fmt.Errorf("timed out waiting for relays")
return
}
select {
case <-ctx.Done():
err = fmt.Errorf("context canceled waiting for response")
case e := <-evs:
var plain []byte
if plain, err = encryption.Decrypt(
e.Event.Content, cl.conversationKey,
); chk.E(err) {
return
}
}()
resp := &Response{
Result: &result,
}
if err = json.Unmarshal(plain, resp); chk.E(err) {
return
}
return
}
return
}
// executeRequest executes a NIP-47 request
func (c *Client) executeRequest(
method Method,
params any,
) (msg json.RawMessage, err error) {
// Default timeout values
replyTimeout := 3 * time.Second
publishTimeout := 3 * time.Second
// Create context with timeout
ctx, cancel := context.Timeout(context.Bg(), replyTimeout)
defer cancel()
// Create result channel
resultCh := make(chan json.RawMessage, 1)
errCh := make(chan error, 1)
// Check connection
if err = c.checkConnected(); err != nil {
return nil, err
}
// Create request
request := struct {
Method Method `json:"method"`
Params any `json:"params,omitempty"`
}{
Method: method,
Params: params,
}
// Marshal request
requestJSON, err := json.Marshal(request)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
// Encrypt request
var encryptedContent []byte
if encryptedContent, err = c.encrypt(
c.options.WalletPubkey, requestJSON,
); chk.E(err) {
return nil, fmt.Errorf("failed to encrypt request: %w", err)
}
// Create request event
requestEvent := &event.E{
Kind: kind.WalletRequest,
CreatedAt: timestamp.New(time.Now().Unix()),
Tags: tags.New(tag.New("p", hex.Enc(c.options.WalletPubkey))),
Content: encryptedContent,
}
// Sign request event
err = requestEvent.Sign(c.options.Secret)
if err != nil {
return nil, fmt.Errorf("failed to sign request event: %w", err)
}
// Subscribe to response events
f := &filter.F{
Kinds: kinds.New(kind.WalletResponse),
Authors: tag.New(c.options.WalletPubkey),
Tags: tags.New(tag.New([]byte("#p"), requestEvent.ID)),
}
log.I.F("%s", f.Marshal(nil))
var sub *ws.Subscription
if sub, err = c.relay.Subscribe(
ctx, &filters.T{
F: []*filter.F{f},
},
); chk.E(err) {
err = fmt.Errorf(
"failed to subscribe to response events: %w", err,
)
return
}
defer sub.Close()
// Set up reply timeout
replyTimer := time.AfterFunc(
replyTimeout, func() {
errCh <- NewReplyTimeoutError(
fmt.Sprintf("Timeout waiting for reply to %s", method),
"TIMEOUT",
)
func (cl *Client) GetWalletServiceInfo(c context.T) (
wsi *WalletServiceInfo, err error,
) {
lim := uint(1)
evc := cl.pool.SubMany(
c, cl.relays, &filters.T{
F: []*filter.F{
{
Limit: &lim,
Kinds: kinds.New(kind.WalletInfo),
Authors: tag.New(cl.walletPublicKey),
},
},
},
)
defer replyTimer.Stop()
// Handle response events
go func() {
var resErr error
for {
select {
case <-ctx.Done():
return
case ev := <-sub.Events:
// Decrypt content
var decryptedContent []byte
decryptedContent, resErr = c.decrypt(
c.options.WalletPubkey, ev.Content,
)
if chk.E(resErr) {
errCh <- fmt.Errorf(
"failed to decrypt response: %w",
resErr,
)
return
}
// Parse response
var response struct {
ResultType string `json:"result_type"`
Result json.RawMessage `json:"result"`
Error *struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
if resErr = json.Unmarshal(
decryptedContent, &response,
); chk.E(resErr) {
errCh <- fmt.Errorf("failed to parse response: %w", resErr)
return
}
// Check for error
if response.Error != nil {
errCh <- NewWalletError(
response.Error.Message,
response.Error.Code,
)
return
}
// Send result
resultCh <- response.Result
return
case <-sub.EndOfStoredEvents:
// Ignore
select {
case <-c.Done():
err = fmt.Errorf("GetWalletServiceInfo canceled")
return
case ev := <-evc:
var encryptionTypes []EncryptionType
var notificationTypes []NotificationType
encryptionTag := ev.Event.Tags.GetFirst(tag.New("encryption"))
notificationsTag := ev.Event.Tags.GetFirst(tag.New("notifications"))
if encryptionTag != nil {
et := encryptionTag.ToSliceOfBytes()
encType := bytes.Split(et[0], []byte(" "))
for _, e := range encType {
encryptionTypes = append(encryptionTypes, e)
}
}
}()
// Publish request event
publishCtx, publishCancel := context.Timeout(
context.Bg(), publishTimeout,
)
defer publishCancel()
if err = c.relay.Publish(publishCtx, requestEvent); chk.E(err) {
err = fmt.Errorf("failed to publish request event: %w", err)
return
}
// Wait for result or error
select {
case msg = <-resultCh:
return
case err = <-errCh:
return
case <-ctx.Done():
err = NewReplyTimeoutError(
fmt.Sprintf("Timeout waiting for reply to %s", method),
"TIMEOUT",
)
return
}
}
// checkConnected checks if the client is connected to the relay
func (c *Client) checkConnected() (err error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.options.RelayURL == "" {
return fmt.Errorf("missing relay URL")
}
if c.relay == nil {
if c.relay, err = ws.RelayConnect(
context.Bg(), c.options.RelayURL,
); chk.E(err) {
return NewNetworkError(
"Failed to connect to "+c.options.RelayURL,
"OTHER",
)
if notificationsTag != nil {
nt := notificationsTag.ToSliceOfBytes()
notifs := bytes.Split(nt[0], []byte(" "))
for _, e := range notifs {
notificationTypes = append(notificationTypes, e)
}
}
} else if !c.relay.IsConnected() {
c.relay.Close()
if c.relay, err = ws.RelayConnect(
context.Bg(), c.options.RelayURL,
); chk.E(err) {
return NewNetworkError(
"Failed to connect to "+c.options.RelayURL,
"OTHER",
)
cp := bytes.Split(ev.Event.Content, []byte(" "))
var capabilities []Capability
for _, capability := range cp {
capabilities = append(capabilities, capability)
}
wsi = &WalletServiceInfo{
EncryptionTypes: encryptionTypes,
NotificationTypes: notificationTypes,
Capabilities: capabilities,
}
}
return nil
return
}
func (cl *Client) GetInfo(c context.T) (gi *GetInfoResult, err error) {
gi = &GetInfoResult{}
if err = cl.RPC(c, GetInfo, nil, gi, nil); chk.E(err) {
return
}
return
}
func (cl *Client) MakeInvoice(
c context.T, params *MakeInvoiceParams,
) (mi *MakeInvoiceResult, err error) {
mi = &MakeInvoiceResult{}
if err = cl.RPC(c, MakeInvoice, params, &mi, nil); chk.E(err) {
return
}
return
}
func (cl *Client) PayInvoice(
c context.T, params *PayInvoiceParams,
) (pi *PayInvoiceResult, err error) {
pi = &PayInvoiceResult{}
if err = cl.RPC(c, PayInvoice, params, &pi, nil); chk.E(err) {
return
}
return
}
func (cl *Client) LookupInvoice(
c context.T, params *LookupInvoiceParams,
) (li *LookupInvoiceResult, err error) {
li = &LookupInvoiceResult{}
if err = cl.RPC(c, LookupInvoice, params, &li, nil); chk.E(err) {
return
}
return
}
func (cl *Client) ListTransactions(
c context.T, params *ListTransactionsParams,
) (lt *ListTransactionsResult, err error) {
lt = &ListTransactionsResult{}
if err = cl.RPC(c, ListTransactions, params, &lt, nil); chk.E(err) {
return
}
return
}
func (cl *Client) GetBalance(c context.T) (gb *GetBalanceResult, err error) {
gb = &GetBalanceResult{}
if err = cl.RPC(c, GetBalance, nil, gb, nil); chk.E(err) {
return
}
return
}

View File

@@ -1,473 +1,116 @@
package nwc
import (
"fmt"
"time"
// Capability represents a NIP-47 method
type Capability []byte
var (
GetInfo = Capability("get_info")
GetBalance = Capability("get_balance")
GetBudget = Capability("get_budget")
MakeInvoice = Capability("make_invoice")
PayInvoice = Capability("pay_invoice")
PayKeysend = Capability("pay_keysend")
LookupInvoice = Capability("lookup_invoice")
ListTransactions = Capability("list_transactions")
SignMessage = Capability("sign_message")
CreateConnection = Capability("create_connection")
MakeHoldInvoice = Capability("make_hold_invoice")
SettleHoldInvoice = Capability("settle_hold_invoice")
CancelHoldInvoice = Capability("cancel_hold_invoice")
MultiPayInvoice = Capability("multi_pay_invoice")
MultiPayKeysend = Capability("multi_pay_keysend")
)
// EncryptionType represents the encryption type used for NIP-47 messages
type EncryptionType string
type EncryptionType []byte
const (
Nip04 EncryptionType = "nip04"
Nip44V2 EncryptionType = "nip44_v2"
var (
Nip04 = EncryptionType("nip04")
Nip44V2 = EncryptionType("nip44_v2")
)
// AuthorizationUrlOptions represents options for creating an NWC authorization URL
type AuthorizationUrlOptions struct {
Name string `json:"name,omitempty"`
Icon string `json:"icon,omitempty"`
RequestMethods []Method `json:"requestMethods,omitempty"`
NotificationTypes []NotificationType `json:"notificationTypes,omitempty"`
ReturnTo string `json:"returnTo,omitempty"`
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
MaxAmount *int64 `json:"maxAmount,omitempty"`
BudgetRenewal BudgetRenewalPeriod `json:"budgetRenewal,omitempty"`
Isolated bool `json:"isolated,omitempty"`
Metadata interface{} `json:"metadata,omitempty"`
}
type NotificationType []byte
// Err is the base error type for NIP-47 errors
type Err struct {
Message string
Code string
}
func (e *Err) Error() string {
return fmt.Sprintf("%s (code: %s)", e.Message, e.Code)
}
// NewError creates a new Error
func NewError(message, code string) *Err {
return &Err{
Message: message,
Code: code,
}
}
// NetworkError represents a network error in NIP-47 operations
type NetworkError struct{ *Err }
// NewNetworkError creates a new NetworkError
func NewNetworkError(message, code string) *NetworkError {
return &NetworkError{
Err: NewError(message, code),
}
}
// WalletError represents a wallet error in NIP-47 operations
type WalletError struct {
*Err
}
// NewWalletError creates a new WalletError
func NewWalletError(message, code string) *WalletError {
return &WalletError{
Err: NewError(message, code),
}
}
// TimeoutError represents a timeout error in NIP-47 operations
type TimeoutError struct{ *Err }
// NewTimeoutError creates a new TimeoutError
func NewTimeoutError(message, code string) *TimeoutError {
return &TimeoutError{
Err: NewError(message, code),
}
}
// PublishTimeoutError represents a publish timeout error in NIP-47 operations
type PublishTimeoutError struct{ *TimeoutError }
// NewPublishTimeoutError creates a new PublishTimeoutError
func NewPublishTimeoutError(message, code string) *PublishTimeoutError {
return &PublishTimeoutError{
TimeoutError: NewTimeoutError(message, code),
}
}
// ReplyTimeoutError represents a reply timeout error in NIP-47 operations
type ReplyTimeoutError struct{ *TimeoutError }
// NewReplyTimeoutError creates a new ReplyTimeoutError
func NewReplyTimeoutError(message, code string) *ReplyTimeoutError {
return &ReplyTimeoutError{
TimeoutError: NewTimeoutError(message, code),
}
}
// PublishError represents a publish error in NIP-47 operations
type PublishError struct{ *Err }
// NewPublishError creates a new PublishError
func NewPublishError(message, code string) *PublishError {
return &PublishError{
Err: NewError(message, code),
}
}
// ResponseDecodingError represents a response decoding error in NIP-47 operations
type ResponseDecodingError struct{ *Err }
// NewResponseDecodingError creates a new ResponseDecodingError
func NewResponseDecodingError(message, code string) *ResponseDecodingError {
return &ResponseDecodingError{
Err: NewError(message, code),
}
}
// ResponseValidationError represents a response validation error in NIP-47 operations
type ResponseValidationError struct{ *Err }
// NewResponseValidationError creates a new ResponseValidationError
func NewResponseValidationError(message, code string) *ResponseValidationError {
return &ResponseValidationError{
Err: NewError(message, code),
}
}
// UnexpectedResponseError represents an unexpected response error in NIP-47 operations
type UnexpectedResponseError struct{ *Err }
// NewUnexpectedResponseError creates a new UnexpectedResponseError
func NewUnexpectedResponseError(message, code string) *UnexpectedResponseError {
return &UnexpectedResponseError{
Err: NewError(message, code),
}
}
// UnsupportedEncryptionError represents an unsupported encryption error in NIP-47 operations
type UnsupportedEncryptionError struct {
*Err
}
// NewUnsupportedEncryptionError creates a new UnsupportedEncryptionError
func NewUnsupportedEncryptionError(message, code string) *UnsupportedEncryptionError {
return &UnsupportedEncryptionError{
Err: NewError(message, code),
}
}
// WithDTag represents a type with a dTag field
type WithDTag struct {
DTag string `json:"dTag"`
}
// WithOptionalId represents a type with an optional id field
type WithOptionalId struct {
ID string `json:"id,omitempty"`
}
// Method represents a NIP-47 method
type Method string
// SingleMethod represents a single NIP-47 method
const (
GetInfo Method = "get_info"
GetBalance Method = "get_balance"
GetBudget Method = "get_budget"
MakeInvoice Method = "make_invoice"
PayInvoice Method = "pay_invoice"
PayKeysend Method = "pay_keysend"
LookupInvoice Method = "lookup_invoice"
ListTransactions Method = "list_transactions"
SignMessage Method = "sign_message"
CreateConnection Method = "create_connection"
MakeHoldInvoice Method = "make_hold_invoice"
SettleHoldInvoice Method = "settle_hold_invoice"
CancelHoldInvoice Method = "cancel_hold_invoice"
var (
PaymentReceived = NotificationType("payment_received")
PaymentSent = NotificationType("payment_sent")
)
// MultiMethod represents a multi NIP-47 method
const (
MultiPayInvoice Method = "multi_pay_invoice"
MultiPayKeysend Method = "multi_pay_keysend"
)
// Capability represents a NIP-47 capability
type Capability string
const (
Notifications Capability = "notifications"
)
// BudgetRenewalPeriod represents a budget renewal period
type BudgetRenewalPeriod string
const (
Daily BudgetRenewalPeriod = "daily"
Weekly BudgetRenewalPeriod = "weekly"
Monthly BudgetRenewalPeriod = "monthly"
Yearly BudgetRenewalPeriod = "yearly"
Never BudgetRenewalPeriod = "never"
)
// GetInfoResponse represents a response to a get_info request
type GetInfoResponse struct {
Alias string `json:"alias"`
Color string `json:"color"`
Pubkey string `json:"pubkey"`
Network string `json:"network"`
BlockHeight int64 `json:"block_height"`
BlockHash string `json:"block_hash"`
Methods []Method `json:"methods"`
Notifications []NotificationType `json:"notifications,omitempty"`
Metadata interface{} `json:"metadata,omitempty"`
Lud16 string `json:"lud16,omitempty"`
type WalletServiceInfo struct {
EncryptionTypes []EncryptionType
Capabilities []Capability
NotificationTypes []NotificationType
}
// GetBudgetResponse represents a response to a get_budget request
type GetBudgetResponse struct {
UsedBudget int64 `json:"used_budget,omitempty"`
TotalBudget int64 `json:"total_budget,omitempty"`
RenewsAt *int64 `json:"renews_at,omitempty"`
RenewalPeriod BudgetRenewalPeriod `json:"renewal_period,omitempty"`
type GetInfoResult struct {
Alias string `json:"alias"`
Color string `json:"color"`
Pubkey string `json:"pubkey"`
Network string `json:"network"`
BlockHeight uint `json:"block_height"`
BlockHash string `json:"block_hash"`
Methods []string `json:"methods"`
Notifications []string `json:"notifications"`
}
// GetBalanceResponse represents a response to a get_balance request
type GetBalanceResponse struct {
Balance int64 `json:"balance"` // msats
type MakeInvoiceParams struct {
Amount uint64 `json:"amount"`
Expiry *uint32 `json:"expiry"`
Description string `json:"description"`
DescriptionHash string `json:"description_hash"`
Metadata any `json:"metadata"`
}
// PayResponse represents a response to a pay request
type PayResponse struct {
type PayInvoiceParams struct {
Invoice string `json:"invoice"`
Amount *uint64 `json:"amount"`
Metadata any `json:"metadata"`
}
type LookupInvoiceParams struct {
PaymentHash string `json:"payment_hash"`
Invoice string `json:"invoice"`
}
type ListTransactionsParams struct {
From uint64 `json:"from"`
To uint64 `json:"to"`
Limit uint16 `json:"limit"`
Offset uint32 `json:"offset"`
Unpaid bool `json:"unpaid"`
UnpaidOutgoing bool `json:"unpaid_outgoing"`
UnpaidIncoming bool `json:"unpaid_incoming"`
Type string `json:"type"`
}
type GetBalanceResult struct {
Balance uint64 `json:"balance"`
}
type PayInvoiceResult struct {
Preimage string `json:"preimage"`
FeesPaid int64 `json:"fees_paid"`
FeesPaid uint64 `json:"fees_paid"`
}
// MultiPayInvoiceRequest represents a request to pay multiple invoices
type MultiPayInvoiceRequest struct {
Invoices []PayInvoiceRequestWithID `json:"invoices"`
}
// PayInvoiceRequestWithID combines PayInvoiceRequest with WithOptionalId
type PayInvoiceRequestWithID struct {
PayInvoiceRequest
WithOptionalId
}
// MultiPayKeysendRequest represents a request to pay multiple keysends
type MultiPayKeysendRequest struct {
Keysends []PayKeysendRequestWithID `json:"keysends"`
}
// PayKeysendRequestWithID combines PayKeysendRequest with WithOptionalId
type PayKeysendRequestWithID struct {
PayKeysendRequest
WithOptionalId
}
// MultiPayInvoiceResponse represents a response to a multi_pay_invoice request
type MultiPayInvoiceResponse struct {
Invoices []MultiPayInvoiceResponseItem `json:"invoices"`
Errors []interface{} `json:"errors"` // TODO: add error handling
}
// MultiPayInvoiceResponseItem represents an item in a multi_pay_invoice response
type MultiPayInvoiceResponseItem struct {
Invoice PayInvoiceRequest `json:"invoice"`
PayResponse
WithDTag
}
// MultiPayKeysendResponse represents a response to a multi_pay_keysend request
type MultiPayKeysendResponse struct {
Keysends []MultiPayKeysendResponseItem `json:"keysends"`
Errors []interface{} `json:"errors"` // TODO: add error handling
}
// MultiPayKeysendResponseItem represents an item in a multi_pay_keysend response
type MultiPayKeysendResponseItem struct {
Keysend PayKeysendRequest `json:"keysend"`
PayResponse
WithDTag
}
// ListTransactionsRequest represents a request to list transactions
type ListTransactionsRequest struct {
From *int64 `json:"from,omitempty"`
Until *int64 `json:"until,omitempty"`
Limit *int64 `json:"limit,omitempty"`
Offset *int64 `json:"offset,omitempty"`
Unpaid *bool `json:"unpaid,omitempty"`
UnpaidOutgoing *bool `json:"unpaid_outgoing,omitempty"` // NOTE: non-NIP-47 spec compliant
UnpaidIncoming *bool `json:"unpaid_incoming,omitempty"` // NOTE: non-NIP-47 spec compliant
Type *string `json:"type,omitempty"` // "incoming" or "outgoing"
}
// ListTransactionsResponse represents a response to a list_transactions request
type ListTransactionsResponse struct {
type MakeInvoiceResult = Transaction
type LookupInvoiceResult = Transaction
type ListTransactionsResult struct {
Transactions []Transaction `json:"transactions"`
TotalCount int64 `json:"total_count"` // NOTE: non-NIP-47 spec compliant
TotalCount uint32 `json:"total_count"`
}
// TransactionType represents the type of a transaction
type TransactionType string
const (
Incoming TransactionType = "incoming"
Outgoing TransactionType = "outgoing"
)
// TransactionState represents the state of a transaction
type TransactionState string
const (
Settled TransactionState = "settled"
Pending TransactionState = "pending"
Failed TransactionState = "failed"
)
// Transaction represents a transaction
type Transaction struct {
Type TransactionType `json:"type"`
State TransactionState `json:"state"` // NOTE: non-NIP-47 spec compliant
Invoice string `json:"invoice"`
Description string `json:"description"`
DescriptionHash string `json:"description_hash"`
Preimage string `json:"preimage"`
PaymentHash string `json:"payment_hash"`
Amount int64 `json:"amount"`
FeesPaid int64 `json:"fees_paid"`
SettledAt int64 `json:"settled_at"`
CreatedAt int64 `json:"created_at"`
ExpiresAt int64 `json:"expires_at"`
SettleDeadline *int64 `json:"settle_deadline,omitempty"` // NOTE: non-NIP-47 spec compliant
Metadata *TransactionMetadata `json:"metadata,omitempty"`
}
// TransactionMetadata represents metadata for a transaction
type TransactionMetadata struct {
Comment string `json:"comment,omitempty"` // LUD-12
PayerData *PayerData `json:"payer_data,omitempty"` // LUD-18
RecipientData *RecipientData `json:"recipient_data,omitempty"` // LUD-18
Nostr *NostrData `json:"nostr,omitempty"` // NIP-57
ExtraData map[string]interface{} `json:"-"` // For additional fields
}
// PayerData represents payer data for a transaction
type PayerData struct {
Email string `json:"email,omitempty"`
Name string `json:"name,omitempty"`
Pubkey string `json:"pubkey,omitempty"`
}
// RecipientData represents recipient data for a transaction
type RecipientData struct {
Identifier string `json:"identifier,omitempty"`
}
// NostrData represents Nostr data for a transaction
type NostrData struct {
Pubkey string `json:"pubkey"`
Tags [][]string `json:"tags"`
}
// NotificationType represents a notification type
type NotificationType string
const (
PaymentReceived NotificationType = "payment_received"
PaymentSent NotificationType = "payment_sent"
HoldInvoiceAccepted NotificationType = "hold_invoice_accepted"
)
// Notification represents a notification
type Notification struct {
NotificationType NotificationType `json:"notification_type"`
Notification Transaction `json:"notification"`
}
// PayInvoiceRequest represents a request to pay an invoice
type PayInvoiceRequest struct {
Invoice string `json:"invoice"`
Metadata *TransactionMetadata `json:"metadata,omitempty"`
Amount *int64 `json:"amount,omitempty"` // msats
}
// PayKeysendRequest represents a request to pay a keysend
type PayKeysendRequest struct {
Amount int64 `json:"amount"` // msats
Pubkey string `json:"pubkey"`
Preimage string `json:"preimage,omitempty"`
TlvRecords []TlvRecord `json:"tlv_records,omitempty"`
}
// TlvRecord represents a TLV record
type TlvRecord struct {
Type int64 `json:"type"`
Value string `json:"value"`
}
// MakeInvoiceRequest represents a request to make an invoice
type MakeInvoiceRequest struct {
Amount int64 `json:"amount"` // msats
Description string `json:"description,omitempty"`
DescriptionHash string `json:"description_hash,omitempty"`
Expiry *int64 `json:"expiry,omitempty"` // in seconds
Metadata *TransactionMetadata `json:"metadata,omitempty"`
}
// MakeHoldInvoiceRequest represents a request to make a hold invoice
type MakeHoldInvoiceRequest struct {
MakeInvoiceRequest
PaymentHash string `json:"payment_hash"`
}
// SettleHoldInvoiceRequest represents a request to settle a hold invoice
type SettleHoldInvoiceRequest struct {
Preimage string `json:"preimage"`
}
// SettleHoldInvoiceResponse represents a response to a settle_hold_invoice request
type SettleHoldInvoiceResponse struct{}
// CancelHoldInvoiceRequest represents a request to cancel a hold invoice
type CancelHoldInvoiceRequest struct {
PaymentHash string `json:"payment_hash"`
}
// CancelHoldInvoiceResponse represents a response to a cancel_hold_invoice request
type CancelHoldInvoiceResponse struct{}
// LookupInvoiceRequest represents a request to lookup an invoice
type LookupInvoiceRequest struct {
PaymentHash string `json:"payment_hash,omitempty"`
Invoice string `json:"invoice,omitempty"`
}
// SignMessageRequest represents a request to sign a message
type SignMessageRequest struct {
Message string `json:"message"`
}
// CreateConnectionRequest represents a request to create a connection
type CreateConnectionRequest struct {
Pubkey string `json:"pubkey"`
Name string `json:"name"`
RequestMethods []Method `json:"request_methods"`
NotificationTypes []NotificationType `json:"notification_types,omitempty"`
MaxAmount *int64 `json:"max_amount,omitempty"`
BudgetRenewal *BudgetRenewalPeriod `json:"budget_renewal,omitempty"`
ExpiresAt *int64 `json:"expires_at,omitempty"`
Isolated *bool `json:"isolated,omitempty"`
Metadata any `json:"metadata,omitempty"`
}
// CreateConnectionResponse represents a response to a create_connection request
type CreateConnectionResponse struct {
WalletPubkey string `json:"wallet_pubkey"`
}
// SignMessageResponse represents a response to a sign_message request
type SignMessageResponse struct {
Message string `json:"message"`
Signature string `json:"signature"`
}
// TimeoutValues represents timeout values for NIP-47 requests
type TimeoutValues struct {
ReplyTimeout *int64 `json:"replyTimeout,omitempty"`
PublishTimeout *int64 `json:"publishTimeout,omitempty"`
Type string `json:"type"`
State string `json:"state"`
Invoice string `json:"invoice"`
Description string `json:"description"`
DescriptionHash string `json:"description_hash"`
Preimage string `json:"preimage"`
PaymentHash string `json:"payment_hash"`
Amount uint64 `json:"amount"`
FeesPaid uint64 `json:"fees_paid"`
CreatedAt uint64 `json:"created_at"`
ExpiresAt uint64 `json:"expires_at"`
SettledAt *uint64 `json:"settled_at"`
Metadata any `json:"metadata"`
}

49
pkg/protocol/nwc/uri.go Normal file
View File

@@ -0,0 +1,49 @@
package nwc
import (
"errors"
"net/url"
"orly.dev/pkg/crypto/p256k"
"orly.dev/pkg/utils/chk"
)
type ConnectionParams struct {
clientSecretKey []byte
walletPublicKey []byte
relays []string
}
func ParseConnectionURI(nwcUri string) (parts *ConnectionParams, err error) {
var p *url.URL
if p, err = url.Parse(nwcUri); chk.E(err) {
return
}
parts = &ConnectionParams{}
if p.Scheme != "nostr+walletconnect" {
err = errors.New("incorrect scheme")
return
}
if parts.walletPublicKey, err = p256k.HexToBin(p.Host); chk.E(err) {
err = errors.New("invalid public key")
return
}
query := p.Query()
var ok bool
if parts.relays, ok = query["relay"]; !ok {
err = errors.New("missing relay parameter")
return
}
if len(parts.relays) == 0 {
return nil, errors.New("no relays")
}
var secret string
if secret = query.Get("secret"); secret == "" {
err = errors.New("missing secret parameter")
return
}
if parts.clientSecretKey, err = p256k.HexToBin(secret); chk.E(err) {
err = errors.New("invalid secret")
return
}
return
}

View File

@@ -10,6 +10,7 @@ import (
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/context"
"orly.dev/pkg/utils/errorf"
"orly.dev/pkg/utils/log"
"strconv"
"sync"
"sync/atomic"
@@ -178,7 +179,7 @@ func (sub *Subscription) Fire() (err error) {
} else {
b = countenvelope.NewRequest(id, sub.Filters).Marshal(b)
}
// log.T.F("{%s} sending %s", sub.Relay.URL, b)
log.T.F("{%s} sending %s", sub.Relay.URL, b)
sub.live.Store(true)
if err = <-sub.Relay.Write(b); chk.T(err) {
sub.cancel()

View File

@@ -5,8 +5,11 @@ import (
"time"
)
// PointerToValue is a generic interface to refer to any pointer to almost any kind of common
// type of value.
// PointerToValue is a generic interface (type constraint) to refer to any
// pointer to almost any kind of common type of value.
//
// see the utils/values package for a set of methods to accept these values and
// return the correct type pointer to them.
type PointerToValue interface {
~*uint | ~*int | ~*uint8 | ~*uint16 | ~*uint32 | ~*uint64 | ~*int8 | ~*int16 | ~*int32 |
~*int64 | ~*float32 | ~*float64 | ~*string | ~*[]string | ~*time.Time | ~*time.Duration |

101
pkg/utils/values/values.go Normal file
View File

@@ -0,0 +1,101 @@
package values
import (
"orly.dev/pkg/encoders/unix"
"time"
)
// ToUintPointer returns a pointer to the uint value passed in.
func ToUintPointer(v uint) *uint {
return &v
}
// ToIntPointer returns a pointer to the int value passed in.
func ToIntPointer(v int) *int {
return &v
}
// ToUint8Pointer returns a pointer to the uint8 value passed in.
func ToUint8Pointer(v uint8) *uint8 {
return &v
}
// ToUint16Pointer returns a pointer to the uint16 value passed in.
func ToUint16Pointer(v uint16) *uint16 {
return &v
}
// ToUint32Pointer returns a pointer to the uint32 value passed in.
func ToUint32Pointer(v uint32) *uint32 {
return &v
}
// ToUint64Pointer returns a pointer to the uint64 value passed in.
func ToUint64Pointer(v uint64) *uint64 {
return &v
}
// ToInt8Pointer returns a pointer to the int8 value passed in.
func ToInt8Pointer(v int8) *int8 {
return &v
}
// ToInt16Pointer returns a pointer to the int16 value passed in.
func ToInt16Pointer(v int16) *int16 {
return &v
}
// ToInt32Pointer returns a pointer to the int32 value passed in.
func ToInt32Pointer(v int32) *int32 {
return &v
}
// ToInt64Pointer returns a pointer to the int64 value passed in.
func ToInt64Pointer(v int64) *int64 {
return &v
}
// ToFloat32Pointer returns a pointer to the float32 value passed in.
func ToFloat32Pointer(v float32) *float32 {
return &v
}
// ToFloat64Pointer returns a pointer to the float64 value passed in.
func ToFloat64Pointer(v float64) *float64 {
return &v
}
// ToStringPointer returns a pointer to the string value passed in.
func ToStringPointer(v string) *string {
return &v
}
// ToStringSlicePointer returns a pointer to the []string value passed in.
func ToStringSlicePointer(v []string) *[]string {
return &v
}
// ToTimePointer returns a pointer to the time.Time value passed in.
func ToTimePointer(v time.Time) *time.Time {
return &v
}
// ToDurationPointer returns a pointer to the time.Duration value passed in.
func ToDurationPointer(v time.Duration) *time.Duration {
return &v
}
// ToBytesPointer returns a pointer to the []byte value passed in.
func ToBytesPointer(v []byte) *[]byte {
return &v
}
// ToByteSlicesPointer returns a pointer to the [][]byte value passed in.
func ToByteSlicesPointer(v [][]byte) *[][]byte {
return &v
}
// ToUnixTimePointer returns a pointer to the unix.Time value passed in.
func ToUnixTimePointer(v unix.Time) *unix.Time {
return &v
}