txlink.gno

4.55 Kb ยท 170 lines
  1// Package txlink provides utilities for creating transaction-related links
  2// compatible with Gnoweb, Gnobro, and other clients within the Gno ecosystem.
  3//
  4// This package is optimized for generating lightweight transaction links with
  5// flexible arguments, allowing users to build dynamic links that integrate
  6// seamlessly with various Gno clients.
  7//
  8// The package offers a way to generate clickable transaction MD links
  9// for the current "relative realm":
 10//
 11//  Using a builder pattern for more structured URLs:
 12//     txlink.NewLink("MyFunc").
 13//         AddArgs("k1", "v1", "k2", "v2"). // or multiple at once
 14//         SetSend("1000000ugnot").
 15//         URL()
 16//
 17// The builder pattern (TxBuilder) provides a fluent interface for constructing
 18// transaction URLs in the current "relative realm". Like Call, it supports both
 19// local realm paths and fully qualified paths through the underlying Call
 20// implementation.
 21//
 22// The Call function remains the core implementation, used both directly and
 23// internally by the builder pattern to generate the final URLs.
 24//
 25// This package is a streamlined alternative to helplink, providing similar
 26// functionality for transaction links without the full feature set of helplink.
 27
 28package txlink
 29
 30import (
 31	"net/url"
 32	"std"
 33	"strings"
 34)
 35
 36var chainDomain = std.ChainDomain()
 37
 38// Realm represents a specific realm for generating tx links.
 39type Realm string
 40
 41// TxBuilder provides a fluent interface for building transaction URLs
 42type TxBuilder struct {
 43	fn        string   // function name
 44	args      []string // key-value pairs
 45	send      string   // optional send amount
 46	realm_XXX Realm    // realm for the URL
 47}
 48
 49// NewLink creates a transaction link builder for the specified function in the current realm.
 50func NewLink(fn string) *TxBuilder {
 51	return Realm("").NewLink(fn)
 52}
 53
 54// NewLink creates a transaction link builder for the specified function in this realm.
 55func (r Realm) NewLink(fn string) *TxBuilder {
 56	if fn == "" {
 57		return nil
 58	}
 59	return &TxBuilder{fn: fn, realm_XXX: r}
 60}
 61
 62// addArg adds a key-value argument pair. Returns the builder for chaining.
 63func (b *TxBuilder) addArg(key, value string) *TxBuilder {
 64	if b == nil {
 65		return nil
 66	}
 67	if key == "" {
 68		return b
 69	}
 70
 71	// Special case: "." prefix is for reserved keywords.
 72	if strings.HasPrefix(key, ".") {
 73		panic("invalid key")
 74	}
 75
 76	b.args = append(b.args, key, value)
 77	return b
 78}
 79
 80// AddArgs adds multiple key-value pairs at once. Arguments should be provided
 81// as pairs: AddArgs("key1", "value1", "key2", "value2").
 82func (b *TxBuilder) AddArgs(args ...string) *TxBuilder {
 83	if b == nil {
 84		return nil
 85	}
 86	if len(args)%2 != 0 {
 87		panic("odd number of arguments")
 88	}
 89	// Add key-value pairs
 90	for i := 0; i < len(args); i += 2 {
 91		key := args[i]
 92		value := args[i+1]
 93		b.addArg(key, value)
 94	}
 95	return b
 96}
 97
 98// SetSend adds a send amount. (Only one send amount can be specified.)
 99func (b *TxBuilder) SetSend(amount string) *TxBuilder {
100	if b == nil {
101		return nil
102	}
103	if amount == "" {
104		return b
105	}
106	b.send = amount
107	return b
108}
109
110// URL generates the final URL using the standard $help&func=name format.
111func (b *TxBuilder) URL() string {
112	if b == nil || b.fn == "" {
113		return ""
114	}
115	args := b.args
116	if b.send != "" {
117		args = append(args, ".send", b.send)
118	}
119	return b.realm_XXX.Call(b.fn, args...)
120}
121
122// Call returns a URL for the specified function with optional key-value
123// arguments, for the current realm.
124func Call(fn string, args ...string) string {
125	return Realm("").Call(fn, args...)
126}
127
128// prefix returns the URL prefix for the realm.
129func (r Realm) prefix() string {
130	// relative
131	if r == "" {
132		curPath := std.CurrentRealm().PkgPath()
133		return strings.TrimPrefix(curPath, chainDomain)
134	}
135
136	// local realm -> /realm
137	rlm := string(r)
138	if strings.HasPrefix(rlm, chainDomain) {
139		return strings.TrimPrefix(rlm, chainDomain)
140	}
141
142	// remote realm -> https://remote.land/realm
143	return "https://" + string(r)
144}
145
146// Call returns a URL for the specified function with optional key-value
147// arguments.
148func (r Realm) Call(fn string, args ...string) string {
149	if len(args) == 0 {
150		return r.prefix() + "$help&func=" + fn
151	}
152
153	// Create url.Values to properly encode parameters.
154	// But manage &func=fn as a special case to keep it as the first argument.
155	values := url.Values{}
156
157	// Check if args length is even
158	if len(args)%2 != 0 {
159		panic("odd number of arguments")
160	}
161	// Add key-value pairs to values
162	for i := 0; i < len(args); i += 2 {
163		key := args[i]
164		value := args[i+1]
165		values.Add(key, value)
166	}
167
168	// Build the base URL and append encoded query parameters
169	return r.prefix() + "$help&func=" + fn + "&" + values.Encode()
170}