Skip to content

Commit

Permalink
Merge pull request #29 from xssnick/wallet
Browse files Browse the repository at this point in the history
Added wallet functionality & fixes
  • Loading branch information
xssnick authored Jun 1, 2022
2 parents b4a1ed1 + 38a4635 commit 156e24c
Show file tree
Hide file tree
Showing 22 changed files with 2,876 additions and 119 deletions.
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,34 @@ if err != nil {
// initialize ton api lite connection wrapper
api := ton.NewAPIClient(client)
```
### Wallet
You can use existing wallet or generate new one using `wallet.NewSeed()`, wallet will be initialized on first sent message from it. This library will deploy and initialize wallet contract if it is not initialized yet.

You can also send any message to any contract using `w.Send` method, it accepts `tlb.InternalMessage` structure, you can dive into `w.Transfer` implementation and see how it works.

Example of basic usage:
```golang
words := strings.Split("birth pattern ...", " ")

w, err := wallet.FromSeed(api, words, wallet.V3)
if err != nil {
panic(err)
}

balance, err := w.GetBalance(context.Background(), block)
if err != nil {
panic(err)
}

if balance.NanoTON().Uint64() >= 3000000 {
addr := address.MustParseAddr("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N")
err = w.Transfer(context.Background(), addr, new(tlb.Grams).MustFromTON("0.003"), "Hey bro, happy birthday!")
if err != nil {
panic(err)
}
}
```
You can find full working example at `example/wallet/main.go`
### Interacting with contracts
Here are the description of features which allow us to trigger contract's methods

Expand Down Expand Up @@ -193,7 +221,7 @@ client.SetOnDisconnect(func(addr, serverKey string) {
* ✅ Send external message
* ✅ Get transactions
* Deploy contracts
* Wallet operations
* Wallet operations
* Payment processing
* ✅ Cell dictionaries support
* MustLoad methods
Expand Down
2 changes: 1 addition & 1 deletion example/account-state/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func main() {
return
}

addr := address.MustParseAddr("EQAoUyP1KBBRvTVAUxlAI_9mmSH05guWrNZ5PfmVFL7zs2b6")
addr := address.MustParseAddr("EQDEGeK4o7bNgazTln27r0RC4YcOmerzIni3gUpsyqxfgMWk")

res, err := api.GetAccount(context.Background(), b, addr)
if err != nil {
Expand Down
File renamed without changes.
File renamed without changes.
64 changes: 64 additions & 0 deletions example/wallet/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import (
"context"
"log"
"strings"

"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/liteclient"
"github.com/xssnick/tonutils-go/liteclient/tlb"
"github.com/xssnick/tonutils-go/ton"
"github.com/xssnick/tonutils-go/ton/wallet"
)

func main() {
client := liteclient.NewClient()

// connect to mainnet lite server
err := client.Connect(context.Background(), "135.181.140.212:13206", "K0t3+IWLOXHYMvMcrGZDPs+pn58a17LFbnXoQkKc2xw=")
if err != nil {
log.Fatalln("connection err: ", err.Error())
return
}

api := ton.NewAPIClient(client)

// seed words of account, you can generate them with any wallet or using wallet.NewSeed() method
words := strings.Split("birth pattern then forest walnut then phrase walnut fan pumpkin pattern then cluster blossom verify then forest velvet pond fiction pattern collect then then", " ")

w, err := wallet.FromSeed(api, words, wallet.V3)
if err != nil {
log.Fatalln("FromPrivateKey err:", err.Error())
return
}

log.Println("wallet address:", w.Address())

block, err := api.GetBlockInfo(context.Background())
if err != nil {
log.Fatalln("get block err:", err.Error())
return
}

balance, err := w.GetBalance(context.Background(), block)
if err != nil {
log.Fatalln("GetBalance err:", err.Error())
return
}

if balance.NanoTON().Uint64() >= 3000000 {
addr := address.MustParseAddr("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N")
err = w.Transfer(context.Background(), addr, new(tlb.Grams).MustFromTON("0.003"), "Hey bro, happy birthday!")
if err != nil {
log.Fatalln("Transfer err:", err.Error())
return
}

log.Println("transaction sent, balance left:", balance.TON())

return
}

log.Println("not enough balance:", balance.TON())
}
46 changes: 21 additions & 25 deletions liteclient/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,19 +383,7 @@ func (cn *connection) queryADNL(qid, payload []byte) error {
binary.LittleEndian.PutUint32(data, uint32(t))

data = append(data, qid...)
if len(payload) >= 0xFE {
ln := make([]byte, 4)
binary.LittleEndian.PutUint32(data, uint32(len(payload)<<8)|0xFE)
data = append(data, ln...)
} else {
data = append(data, byte(len(payload)))
}
data = append(data, payload...)

left := len(data) % 4
if left != 0 {
data = append(data, make([]byte, 4-left)...)
}
data = append(data, storableBytes(payload)...)

return cn.send(data)
}
Expand All @@ -404,26 +392,34 @@ func (cn *connection) queryLiteServer(qid []byte, typeID int32, payload []byte)
data := make([]byte, 4)
binary.LittleEndian.PutUint32(data, uint32(LiteServerQuery))

if len(payload) >= 0xFE {
typData := make([]byte, 4)
binary.LittleEndian.PutUint32(typData, uint32(typeID))

data = append(data, storableBytes(append(typData, payload...))...)

return cn.queryADNL(qid, data)
}

func storableBytes(buf []byte) []byte {
var data []byte

// store buf length
if len(buf) >= 0xFE {
ln := make([]byte, 4)
binary.LittleEndian.PutUint32(data, uint32((len(payload)+4)<<8)|0xFE)
binary.LittleEndian.PutUint32(ln, uint32(len(buf)<<8)|0xFE)
data = append(data, ln...)
} else {
data = append(data, byte(len(payload)+4))
data = append(data, byte(len(buf)))
}

typData := make([]byte, 4)
binary.LittleEndian.PutUint32(typData, uint32(typeID))
data = append(data, buf...)

data = append(data, typData...)
data = append(data, payload...)

left := len(data) % 4
if left != 0 {
data = append(data, make([]byte, 4-left)...)
// adjust actual length to fit % 4 = 0
if round := len(data) % 4; round != 0 {
data = append(data, make([]byte, 4-round)...)
}

return cn.queryADNL(qid, data)
return data
}

func (c *Client) DefaultReconnect(waitBeforeReconnect time.Duration, maxTries int) OnDisconnectCallback {
Expand Down
4 changes: 2 additions & 2 deletions liteclient/tlb/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const (
type AccountStorage struct {
Status AccountStatus
LastTransactionLT uint64
Balance Grams
Balance *Grams
}

type StorageUsed struct {
Expand Down Expand Up @@ -175,7 +175,7 @@ func (s *AccountStorage) LoadFromCell(loader *cell.LoadCell) error {
}

s.LastTransactionLT = lastTransaction
s.Balance = Grams{coins}
s.Balance = new(Grams).FromNanoTON(coins)

return nil
}
4 changes: 2 additions & 2 deletions liteclient/tlb/depth-balance-info.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

type DepthBalanceInfo struct {
Depth uint32
Coins Grams
Coins *Grams
}

func (d *DepthBalanceInfo) LoadFromCell(loader *cell.LoadCell) error {
Expand Down Expand Up @@ -35,7 +35,7 @@ func (d *DepthBalanceInfo) LoadFromCell(loader *cell.LoadCell) error {
}

d.Depth = uint32(depth)
d.Coins = Grams{val: grams}
d.Coins = new(Grams).FromNanoTON(grams)

return nil
}
41 changes: 36 additions & 5 deletions liteclient/tlb/grams.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,51 @@
package tlb

import (
"errors"
"fmt"
"math/big"
)

type Grams struct {
val *big.Int
}
type Grams big.Int

func (g Grams) TON() string {
f := new(big.Float).SetInt(g.val)
f := new(big.Float).SetInt((*big.Int)(&g))
t := new(big.Float).Quo(f, new(big.Float).SetUint64(1000000000))

return t.String()
}

func (g Grams) NanoTON() *big.Int {
return g.val
return (*big.Int)(&g)
}

func (g *Grams) FromNanoTON(val *big.Int) *Grams {
*g = Grams(*val)
return g
}

func (g *Grams) MustFromTON(val string) *Grams {
v, err := g.FromTON(val)
if err != nil {
panic(err)
return nil
}
return v
}

func (g *Grams) FromTON(val string) (*Grams, error) {
f, ok := new(big.Float).SetString(val)
if !ok {
return nil, errors.New("invalid string")
}

f = f.Mul(f, new(big.Float).SetUint64(1000000000))
i, _ := f.Int(new(big.Int))

*g = Grams(*i)
return g, nil
}

func (g *Grams) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%q", g.TON())), nil
}
Loading

0 comments on commit 156e24c

Please sign in to comment.