While this sounds pretty basic, I have hard time trying to find any method for this. While in JS/TS one can simply create a new TonClient
from @ton/ton
, call client.open
passing a blueprint wrapper to it and then use methods written like
async getSoldJettonsAmount(provider: ContractProvider): Promise<bigint> {
const { stack } = await provider.get("sold_jettons_amount", []);
return stack.readBigNumber();
}
I haven't found any docs for this in github.com/tonkeeper/tongo, github.com/mercuryoio/tonlib-go, or github.com/stracedude/ton-client-go (I have to admit, I may have overlooked something as the docs of all the libs are quite brief). Can anyone share how to call a getter from Golang? Currently, my best bet seems to be rewriting TON Access TS wrappers in Go from scratch, or create a separate microsevice in TS and communicate with it from the Go app.
Here's a rough implementation that I've come up with (it's using TON Access and is generally reproduced from JS wrapper behavior):
var (
nodeIdCache string
nodeIdCacheInit bool
nodeIdCacheLastFetch time.Time
)
func fetchTonAccessNode() (string, error) {
if !nodeIdCacheInit || time.Since(nodeIdCacheLastFetch) > time.Minute {
nodeId, err := fetchTonAccessNodeWithoutCache()
if err != nil {
return "", err
}
nodeIdCache = nodeId
nodeIdCacheLastFetch = time.Now()
nodeIdCacheInit = true
}
return nodeIdCache, nil
}
func fetchTonAccessNodeWithoutCache() (string, error) {
resp, err := http.Get("https://ton.access.orbs.network/mngr/nodes?npm_version=2.3.3")
if err != nil {
return "", err
}
defer resp.Body.Close()
var nodes []TonAccessNodesResponse
err = json.NewDecoder(resp.Body).Decode(&nodes)
if err != nil {
return "", err
}
var nodeIds []string
for _, node := range nodes {
}
if len(nodeIds) == 0 {
return "", errors.New("no TON Access nodes available")
}
return nodeIds[0], nil
}
func buildRpcCallGetterNoArgBody(contractAddress, methodName string) string {
return `{"id":"` + `1` + `","jsonrpc":"2.0","method":"runGetMethod","params":{"address":"` +
contractAddress + `","method":"` + methodName + `","stack":[]}}`
}
func callGetterWithNoArg(contractAddress, methodName string) ([]string, error) {
nodeId, err := fetchTonAccessNode()
if err != nil {
return []string{}, errors.New("fetchTonAccessNode: " + err.Error())
}
callGetterUrl := "https://ton.access.orbs.network/" + nodeId + "/1/mainnet/toncenter-api-v2/jsonRPC"
reqBody := buildRpcCallGetterNoArgBody(contractAddress, methodName)
resp, err := http.Post(callGetterUrl, "application/json", bytes.NewBufferString(reqBody))
if err != nil {
return []string{}, errors.New("http.Post: " + err.Error())
}
defer resp.Body.Close()
var respDecoded TonAccessGetterResponse
err = json.NewDecoder(resp.Body).Decode(&respDecoded)
if err != nil {
return []string{}, errors.New("json.NewDecoder for jResp.Body: " + err.Error())
}
if !respDecoded.Ok {
return []string{}, errors.New(fmt.Sprintf("respDecoded.Ok: %+v, expected to be true", respDecoded.Ok))
}
if respDecoded.Result.ExitCode != 0 {
return []string{}, errors.New(fmt.Sprintf(
"respDecoded.Result.ExitCode: %+v, expected to be 0", respDecoded.Result.ExitCode,
))
}
values := []string{}
for _, stackBit := range respDecoded.Result.Stack {
// can use stackBit[0] for consistent parsing, but it requires dynamic types, not sure if Go has them
values = append(values, stackBit[1])
}
return values, nil
}
As you may see, there's a number of hardcoded values and some checks are missing (like filtering TON Access nodes
by their status), but it works. Improvements are welcome; if you are interested, I'll create a Github repo for this.