Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 72 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ go get github.com/mangonet-labs/mgo-go-sdk
```
├─ account # Account management
│ ├─ keypair # Key pair management
│ └─ signer # Signer
│ └─ signer # Signer
├─ bcs # Serialization and deserialization
├─ client # Core client functionalities
│ ├─ httpconn # HTTP connection management
│ ├─ wsconn # WebSocket connection management
├─ config # Configuration
├─ config # Configuration
├─ model # Data models
│ ├─ request # Request data structures
│ └─ response # Response data structures
Expand Down Expand Up @@ -124,6 +124,76 @@ func main() {
}
```

## Multi-Transaction Blocks

The MGO Go SDK supports **Programmable Transaction Blocks (PTBs)**, allowing you to execute multiple transactions atomically within a single transaction block:

```go
// Create a transaction with multiple operations
tx := transaction.NewTransaction()
tx.SetMgoClient(cli).SetSigner(key).SetSender(sender).SetGasPrice(1000).SetGasBudget(50000000)

// Operation 1: Split coins
splitResult := tx.SplitCoins(tx.Gas(), []transaction.Argument{
tx.Pure(uint64(1000000000 * 0.01)), // 0.01 MGO
})

// Operation 2: Transfer split coins
tx.TransferObjects([]transaction.Argument{splitResult}, tx.Pure(recipient))

// Execute all operations atomically
resp, err := tx.Execute(ctx, options, "WaitForLocalExecution")
```

### Benefits of Multi-Transaction Blocks:

- **Atomicity**: All operations succeed or fail together
- **Gas Efficiency**: Lower gas costs compared to separate transactions
- **Chaining**: Use outputs from one operation as inputs to another
- **Consistency**: Ensure state consistency across multiple operations

### Multiple Move Calls in One Transaction

You can also make multiple Move calls within a single transaction:

```go
// Multiple Move calls in one transaction
tx := transaction.NewTransaction()
tx.SetMgoClient(cli).SetSigner(key).SetSender(sender).SetGasPrice(1000).SetGasBudget(100000000)

// Move Call 1: Split coins
splitResult := tx.MoveCall(
"0x0000000000000000000000000000000000000000000000000000000000000002",
"pay", "split", typeArgs,
[]transaction.Argument{tx.Gas(), tx.Pure(uint64(1000000000 * 0.05))},
)

// Move Call 2: Use result from first call
tx.MoveCall(
"0x0000000000000000000000000000000000000000000000000000000000000002",
"pay", "join", typeArgs,
[]transaction.Argument{tx.Gas(), splitResult}, // Chain the calls
)

// Move Call 3: Custom contract interaction
tx.MoveCall(
"0xpackage_id", "module_name", "function_name", typeArgs,
[]transaction.Argument{splitResult, tx.Pure(params)},
)

// Execute all Move calls atomically
resp, err := tx.Execute(ctx, options, "WaitForLocalExecution")
```

For detailed examples and patterns, see:

- `docs/MULTI_TRANSACTION_GUIDE.md` - Comprehensive guide
- `docs/MULTI_MOVE_CALL_GUIDE.md` - Multi Move call patterns
- `examples/multi_transaction_example.go` - Working examples
- `examples/demo_multi_move_call.go` - Move call demonstrations
- `test/multi_transaction/` - Test cases and validation
- `test/multi_move_call/` - Move call test cases

## Usage Examples

For detailed usage examples, refer to the `test` directory, which includes sample implementations.
Expand Down
16 changes: 16 additions & 0 deletions bcs/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ func (d *Decoder) decodeEnum(v reflect.Value) (int, error) {
return n, err
}

// Check if enumId is within bounds
numFields := v.NumField()
if enumId >= numFields {
return n, fmt.Errorf("enum variant %d out of range for struct with %d fields", enumId, numFields)
}

field := v.Field(enumId)

k, err := d.decode(field)
Expand All @@ -244,6 +250,11 @@ func (d *Decoder) decodeByteSlice(v reflect.Value) (int, error) {
return n, nil
}

// Add safety check for unreasonable sizes
if size > 100*1024*1024 { // 100MB limit
return n, fmt.Errorf("byte slice size too large: %d bytes", size)
}

tmp := make([]byte, size)

read, err := d.reader.Read(tmp)
Expand Down Expand Up @@ -324,6 +335,11 @@ func (d *Decoder) decodeSlice(v reflect.Value) (int, error) {
return n, err
}

// Add safety check for unreasonable sizes
if size > 1000000 { // 1M elements limit
return n, fmt.Errorf("slice size too large: %d elements", size)
}

elementType := v.Type().Elem()

tmp := reflect.MakeSlice(v.Type(), 0, size)
Expand Down
95 changes: 95 additions & 0 deletions cmd/decode-transfer/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package main

import (
"bufio"
"flag"
"fmt"
"os"
"strings"

"github.com/mangonet-labs/mgo-go-sdk/transaction"
)

func main() {
var (
format = flag.String("format", "auto", "Input format: auto, hex, base64, json")
output = flag.String("output", "pretty", "Output format: pretty, json")
inputFile = flag.String("file", "", "Read from file instead of stdin")
rawMessage = flag.String("message", "", "Raw message to decode")
)
flag.Parse()

var input string
var err error

// Get input from various sources
if *rawMessage != "" {
input = *rawMessage
} else if *inputFile != "" {
content, err := os.ReadFile(*inputFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
os.Exit(1)
}
input = string(content)
} else {
// Read from stdin
fmt.Println("Enter raw transfer message (press Ctrl+D when done):")
scanner := bufio.NewScanner(os.Stdin)
var lines []string
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
os.Exit(1)
}
input = strings.Join(lines, "\n")
}

input = strings.TrimSpace(input)
if input == "" {
fmt.Fprintf(os.Stderr, "No input provided\n")
flag.Usage()
os.Exit(1)
}

// Create decoder and decode the message
decoder := transaction.NewRawMessageDecoder()
var decoded *transaction.DecodedTransferMessage

switch *format {
case "auto":
decoded, err = decoder.DecodeRawMessage(input)
case "hex":
decoded, err = transaction.DecodeTransactionHex(input)
case "base64":
decoded, err = transaction.DecodeTransactionBase64(input)
case "json":
decoded, err = decoder.DecodeJSONMessage(input)
default:
fmt.Fprintf(os.Stderr, "Unknown format: %s\n", *format)
os.Exit(1)
}

if err != nil {
fmt.Fprintf(os.Stderr, "Error decoding message: %v\n", err)
os.Exit(1)
}

// Output the result
switch *output {
case "pretty":
fmt.Println(decoded.PrettyPrint())
case "json":
jsonOutput, err := decoded.ToJSON()
if err != nil {
fmt.Fprintf(os.Stderr, "Error converting to JSON: %v\n", err)
os.Exit(1)
}
fmt.Println(jsonOutput)
default:
fmt.Fprintf(os.Stderr, "Unknown output format: %s\n", *output)
os.Exit(1)
}
}
Loading