goton

How can I call a TON contract getter method from a Go app?


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.


Solution

  • 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.