4
4
"bytes"
5
5
"encoding/binary"
6
6
"errors"
7
+ "fmt"
8
+ "unicode"
7
9
8
10
"github.com/icon-project/stacks-go-sdk/internal/utils"
9
11
"github.com/icon-project/stacks-go-sdk/pkg/clarity"
@@ -21,6 +23,11 @@ type TokenTransferPayload struct {
21
23
Memo string
22
24
}
23
25
26
+ type SmartContractPayload struct {
27
+ ContractName string
28
+ CodeBody string
29
+ }
30
+
24
31
type ContractCallPayload struct {
25
32
ContractAddress clarity.ClarityValue // Can be either StandardPrincipal or ContractPrincipal
26
33
ContractName string
@@ -32,6 +39,10 @@ func (t *TokenTransferTransaction) GetPayload() Payload {
32
39
return & t .Payload
33
40
}
34
41
42
+ func (t * SmartContractTransaction ) GetPayload () Payload {
43
+ return & t .Payload
44
+ }
45
+
35
46
func (t * ContractCallTransaction ) GetPayload () Payload {
36
47
return & t .Payload
37
48
}
@@ -49,6 +60,21 @@ func NewTokenTransferPayload(recipient string, amount uint64, memo string) (*Tok
49
60
}, nil
50
61
}
51
62
63
+ func NewSmartContractPayload (contractName string , codeBody string ) (* SmartContractPayload , error ) {
64
+ if err := validateContractName (contractName ); err != nil {
65
+ return nil , err
66
+ }
67
+
68
+ if err := validateCodeBody (codeBody ); err != nil {
69
+ return nil , err
70
+ }
71
+
72
+ return & SmartContractPayload {
73
+ ContractName : contractName ,
74
+ CodeBody : codeBody ,
75
+ }, nil
76
+ }
77
+
52
78
func NewContractCallPayload (contractAddress string , contractName string , functionName string , functionArgs []clarity.ClarityValue ) (* ContractCallPayload , error ) {
53
79
principalCV , err := clarity .StringToPrincipal (contractAddress )
54
80
if err != nil {
@@ -114,6 +140,63 @@ func (p *TokenTransferPayload) Deserialize(data []byte) (int, error) {
114
140
return offset , nil
115
141
}
116
142
143
+ func (p * SmartContractPayload ) Serialize () ([]byte , error ) {
144
+ buf := make ([]byte , 0 , len (p .ContractName )+ len (p .CodeBody )+ 6 ) // 1 + 1 + 4 bytes for headers
145
+
146
+ // Payload type
147
+ buf = append (buf , byte (stacks .PayloadTypeSmartContract ))
148
+
149
+ // Contract name
150
+ if len (p .ContractName ) > stacks .MaxStringLengthBytes {
151
+ return nil , fmt .Errorf ("contract name exceeds maximum length of %d" , stacks .MaxStringLengthBytes )
152
+ }
153
+ buf = append (buf , byte (len (p .ContractName )))
154
+ buf = append (buf , []byte (p .ContractName )... )
155
+
156
+ // Code body with 4-byte length prefix
157
+ codeBodyLen := make ([]byte , 4 )
158
+ binary .BigEndian .PutUint32 (codeBodyLen , uint32 (len (p .CodeBody )))
159
+ buf = append (buf , codeBodyLen ... )
160
+ buf = append (buf , []byte (p .CodeBody )... )
161
+
162
+ return buf , nil
163
+ }
164
+
165
+ func (p * SmartContractPayload ) Deserialize (data []byte ) (int , error ) {
166
+ if len (data ) < 2 || stacks .PayloadType (data [0 ]) != stacks .PayloadTypeSmartContract {
167
+ return 0 , errors .New ("invalid smart contract payload" )
168
+ }
169
+
170
+ offset := 1
171
+
172
+ // Contract name
173
+ nameLen := int (data [offset ])
174
+ offset ++
175
+ if nameLen > stacks .MaxStringLengthBytes {
176
+ return 0 , fmt .Errorf ("contract name length %d exceeds maximum %d" , nameLen , stacks .MaxStringLengthBytes )
177
+ }
178
+ if len (data [offset :]) < nameLen {
179
+ return 0 , errors .New ("insufficient data for contract name" )
180
+ }
181
+ p .ContractName = string (data [offset : offset + nameLen ])
182
+ offset += nameLen
183
+
184
+ // Code body
185
+ if len (data [offset :]) < 4 {
186
+ return 0 , errors .New ("insufficient data for code body length" )
187
+ }
188
+ codeBodyLen := binary .BigEndian .Uint32 (data [offset : offset + 4 ])
189
+ offset += 4
190
+
191
+ if len (data [offset :]) < int (codeBodyLen ) {
192
+ return 0 , errors .New ("insufficient data for code body" )
193
+ }
194
+ p .CodeBody = string (data [offset : offset + int (codeBodyLen )])
195
+ offset += int (codeBodyLen )
196
+
197
+ return offset , nil
198
+ }
199
+
117
200
func (p * ContractCallPayload ) Serialize () ([]byte , error ) {
118
201
buf := make ([]byte , 0 , 128 )
119
202
@@ -202,3 +285,32 @@ func (p *ContractCallPayload) Deserialize(data []byte) (int, error) {
202
285
203
286
return offset , nil
204
287
}
288
+
289
+ func validateContractName (name string ) error {
290
+ if len (name ) == 0 || len (name ) > stacks .MaxStringLengthBytes {
291
+ return fmt .Errorf ("contract name must be between 1 and %d characters" , stacks .MaxStringLengthBytes )
292
+ }
293
+
294
+ // First character must be a letter
295
+ if ! unicode .IsLetter (rune (name [0 ])) {
296
+ return errors .New ("contract name must start with a letter" )
297
+ }
298
+
299
+ // Subsequent characters must be letters, numbers, hyphens, or underscores
300
+ for _ , r := range name [1 :] {
301
+ if ! unicode .IsLetter (r ) && ! unicode .IsNumber (r ) && r != '-' && r != '_' {
302
+ return errors .New ("contract name can only contain letters, numbers, hyphens, and underscores" )
303
+ }
304
+ }
305
+
306
+ return nil
307
+ }
308
+
309
+ func validateCodeBody (code string ) error {
310
+ for i , r := range code {
311
+ if r != '\n' && r != '\t' && (r < 0x20 || r > 0x7e ) {
312
+ return fmt .Errorf ("invalid character in code body at position %d: %q" , i , r )
313
+ }
314
+ }
315
+ return nil
316
+ }
0 commit comments