txlink.gno
4.59 Kb · 171 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 "chain/runtime"
32 "chain/runtime/unsafe"
33 "net/url"
34 "strings"
35)
36
37var chainDomain = runtime.ChainDomain()
38
39// Realm represents a specific realm for generating tx links.
40type Realm string
41
42// TxBuilder provides a fluent interface for building transaction URLs
43type TxBuilder struct {
44 fn string // function name
45 args []string // key-value pairs
46 send string // optional send amount
47 realm_XXX Realm // realm for the URL
48}
49
50// NewLink creates a transaction link builder for the specified function in the current realm.
51func NewLink(fn string) *TxBuilder {
52 return Realm("").NewLink(fn)
53}
54
55// NewLink creates a transaction link builder for the specified function in this realm.
56func (r Realm) NewLink(fn string) *TxBuilder {
57 if fn == "" {
58 return nil
59 }
60 return &TxBuilder{fn: fn, realm_XXX: r}
61}
62
63// addArg adds a key-value argument pair. Returns the builder for chaining.
64func (b *TxBuilder) addArg(key, value string) *TxBuilder {
65 if b == nil {
66 return nil
67 }
68 if key == "" {
69 return b
70 }
71
72 // Special case: "." prefix is for reserved keywords.
73 if strings.HasPrefix(key, ".") {
74 panic("invalid key")
75 }
76
77 b.args = append(b.args, key, value)
78 return b
79}
80
81// AddArgs adds multiple key-value pairs at once. Arguments should be provided
82// as pairs: AddArgs("key1", "value1", "key2", "value2").
83func (b *TxBuilder) AddArgs(args ...string) *TxBuilder {
84 if b == nil {
85 return nil
86 }
87 if len(args)%2 != 0 {
88 panic("odd number of arguments")
89 }
90 // Add key-value pairs
91 for i := 0; i < len(args); i += 2 {
92 key := args[i]
93 value := args[i+1]
94 b.addArg(key, value)
95 }
96 return b
97}
98
99// SetSend adds a send amount. (Only one send amount can be specified.)
100func (b *TxBuilder) SetSend(amount string) *TxBuilder {
101 if b == nil {
102 return nil
103 }
104 if amount == "" {
105 return b
106 }
107 b.send = amount
108 return b
109}
110
111// URL generates the final URL using the standard $help&func=name format.
112func (b *TxBuilder) URL() string {
113 if b == nil || b.fn == "" {
114 return ""
115 }
116 args := b.args
117 if b.send != "" {
118 args = append(args, ".send", b.send)
119 }
120 return b.realm_XXX.Call(b.fn, args...)
121}
122
123// Call returns a URL for the specified function with optional key-value
124// arguments, for the current realm.
125func Call(fn string, args ...string) string {
126 return Realm("").Call(fn, args...)
127}
128
129// prefix returns the URL prefix for the realm.
130func (r Realm) prefix() string {
131 // relative
132 if r == "" {
133 curPath := unsafe.CurrentRealm().PkgPath()
134 return strings.TrimPrefix(curPath, chainDomain)
135 }
136
137 // local realm -> /realm
138 rlm := string(r)
139 if strings.HasPrefix(rlm, chainDomain) {
140 return strings.TrimPrefix(rlm, chainDomain)
141 }
142
143 // remote realm -> https://remote.land/realm
144 return "https://" + string(r)
145}
146
147// Call returns a URL for the specified function with optional key-value
148// arguments.
149func (r Realm) Call(fn string, args ...string) string {
150 if len(args) == 0 {
151 return r.prefix() + "$help&func=" + fn
152 }
153
154 // Create url.Values to properly encode parameters.
155 // But manage &func=fn as a special case to keep it as the first argument.
156 values := url.Values{}
157
158 // Check if args length is even
159 if len(args)%2 != 0 {
160 panic("odd number of arguments")
161 }
162 // Add key-value pairs to values
163 for i := 0; i < len(args); i += 2 {
164 key := args[i]
165 value := args[i+1]
166 values.Add(key, value)
167 }
168
169 // Build the base URL and append encoded query parameters
170 return r.prefix() + "$help&func=" + fn + "&" + values.Encode()
171}