Skip to content

Commit

Permalink
Add JSON examples.
Browse files Browse the repository at this point in the history
  • Loading branch information
q-uint committed Oct 22, 2023
1 parent 0641394 commit 7a7802a
Show file tree
Hide file tree
Showing 46 changed files with 1,108 additions and 649 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
coverage.out
ic/.dfx
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
test:
go test -v -cover ./...

check-moc:
find ic -type f -name '*.mo' -print0 | xargs -0 $(shell dfx cache show)/moc --check

test-cover:
go test -v -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
Expand Down
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,6 @@
[![Go Version](https://img.shields.io/github/go-mod/go-version/aviate-labs/agent-go.svg)](https://github.com/aviate-labs/agent-go)
[![GoDoc Reference](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/aviate-labs/agent-go)

## Testing

There are two types of tests within this repository; the normal go tests and [DFX](https://github.com/dfinity/sdk) dependent tests. The test suite will run a local replica through DFX to run some e2e tests. If you do not have it installed then those tests will be ignored.

```shell
go test -v ./...
```

## Packages

| Package Name | Links | Description |
Expand All @@ -24,3 +16,21 @@ go test -v ./...
| `principal` | [![DOC](https://img.shields.io/badge/-DOC-blue)](https://pkg.go.dev/github.com/aviate-labs/agent-go/principal) | Generic Identifiers for the Internet Computer |

More dependencies in the [go.mod](./go.mod) file.

## CLI

```shell
go install github.com/aviate-labs/agent-go/cmd/goic@latest
```

Read more [here](cmd/goic/README.md)

## Testing

There are two types of tests within this repository; the normal go tests and [DFX](https://github.com/dfinity/sdk)
dependent tests. The test suite will run a local replica through DFX to run some e2e tests. If you do not have it
installed then those tests will be ignored.

```shell
go test -v ./...
```
95 changes: 24 additions & 71 deletions agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"net/url"
"time"

"github.com/aviate-labs/agent-go/candid"
"github.com/aviate-labs/agent-go/candid/idl"
"github.com/aviate-labs/agent-go/certificate"
"github.com/aviate-labs/agent-go/identity"
Expand Down Expand Up @@ -78,56 +77,34 @@ func New(cfg Config) (*Agent, error) {
}

// Call calls a method on a canister and unmarshals the result into the given values.
func (a Agent) Call(canisterID principal.Principal, methodName string, args []byte, values []any) error {
if len(args) == 0 {
// Default to the empty Candid argument list.
args = []byte{'D', 'I', 'D', 'L', 0, 0}
}
raw, err := a.CallRaw(canisterID, methodName, args)
func (a Agent) Call(canisterID principal.Principal, methodName string, args []any, values []any) error {
rawArgs, err := idl.Marshal(args)
if err != nil {
return err
}
return idl.Unmarshal(raw, values)
}

// CallCandid calls a method on a canister and returns the raw Candid result as a list of types and values.
func (a Agent) CallCandid(canisterID principal.Principal, methodName string, args []byte) ([]idl.Type, []interface{}, error) {
raw, err := a.CallRaw(canisterID, methodName, args)
if err != nil {
return nil, nil, err
if len(args) == 0 {
// Default to the empty Candid argument list.
rawArgs = []byte{'D', 'I', 'D', 'L', 0, 0}
}
return idl.Decode(raw)
}

// CallRaw calls a method on a canister and returns the raw Candid result.
func (a Agent) CallRaw(canisterID principal.Principal, methodName string, args []byte) ([]byte, error) {
requestID, data, err := a.sign(Request{
Type: RequestTypeCall,
Sender: a.Sender(),
CanisterID: canisterID,
MethodName: methodName,
Arguments: args,
Arguments: rawArgs,
IngressExpiry: a.expiryDate(),
})
if err != nil {
return nil, err
return err
}
if _, err := a.call(canisterID, data); err != nil {
return nil, err
}
return a.poll(canisterID, *requestID, time.Second, time.Second*10)
}

// CallString calls a method on a canister and returns the result as a string.
func (a Agent) CallString(canisterID principal.Principal, methodName string, args []byte) (string, error) {
if len(args) == 0 {
args = []byte{'D', 'I', 'D', 'L', 0, 0}
return err
}
types, values, err := a.CallCandid(canisterID, methodName, args)
raw, err := a.poll(canisterID, *requestID, time.Second, time.Second*10)
if err != nil {
return "", err
return err
}
return candid.DecodeValuesString(types, values)
return idl.Unmarshal(raw, values)
}

// GetCanisterControllers returns the list of principals that can control the given canister.
Expand Down Expand Up @@ -170,64 +147,40 @@ func (a Agent) GetCanisterModuleHash(canisterID principal.Principal) ([]byte, er
return a.GetCanisterInfo(canisterID, "module_hash")
}

// Query queries a method on a canister and unmarshals the result into the given values.
func (a Agent) Query(canisterID principal.Principal, methodName string, args []byte, values []any) error {
if len(args) == 0 {
args = []byte{'D', 'I', 'D', 'L', 0, 0}
}
raw, err := a.QueryRaw(canisterID, methodName, args)
func (a Agent) Query(canisterID principal.Principal, methodName string, args []any, values []any) error {
rawArgs, err := idl.Marshal(args)
if err != nil {
return err
}
return idl.Unmarshal(raw, values)
}

// QueryCandid queries a method on a canister and returns the raw Candid result as a list of types and values.
func (a Agent) QueryCandid(canisterID principal.Principal, methodName string, args []byte) ([]idl.Type, []interface{}, error) {
raw, err := a.QueryRaw(canisterID, methodName, args)
if err != nil {
return nil, nil, err
if len(args) == 0 {
// Default to the empty Candid argument list.
rawArgs = []byte{'D', 'I', 'D', 'L', 0, 0}
}
return idl.Decode(raw)
}

// QueryRaw queries a method on a canister and returns the raw Candid result.
func (a Agent) QueryRaw(canisterID principal.Principal, methodName string, args []byte) ([]byte, error) {
_, data, err := a.sign(Request{
Type: RequestTypeQuery,
Sender: a.Sender(),
CanisterID: canisterID,
MethodName: methodName,
Arguments: args,
Arguments: rawArgs,
IngressExpiry: a.expiryDate(),
})
if err != nil {
return nil, err
return err
}
resp, err := a.query(canisterID, data)
if err != nil {
return nil, err
return err
}
var raw []byte
switch resp.Status {
case "replied":
return resp.Reply["arg"], nil
raw = resp.Reply["arg"]
case "rejected":
return nil, fmt.Errorf("(%d) %s", resp.RejectCode, resp.RejectMsg)
return fmt.Errorf("(%d) %s", resp.RejectCode, resp.RejectMsg)
default:
panic("unreachable")
}
}

// QueryString queries a method on a canister and returns the result as a string.
func (a Agent) QueryString(canisterID principal.Principal, methodName string, args []byte) (string, error) {
if len(args) == 0 {
args = []byte{'D', 'I', 'D', 'L', 0, 0}
}
types, values, err := a.QueryCandid(canisterID, methodName, args)
if err != nil {
return "", err
}
return candid.DecodeValuesString(types, values)
return idl.Unmarshal(raw, values)
}

// RequestStatus returns the status of the request with the given ID.
Expand All @@ -237,7 +190,7 @@ func (a Agent) RequestStatus(canisterID principal.Principal, requestID RequestID
if err != nil {
return nil, nil, err
}
var state map[string]interface{}
var state map[string]any
if err := cbor.Unmarshal(c, &state); err != nil {
return nil, nil, err
}
Expand Down
53 changes: 39 additions & 14 deletions agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,62 @@ package agent_test
import (
"crypto/ed25519"
"crypto/rand"
"encoding/json"
"fmt"
"github.com/aviate-labs/agent-go"
"github.com/aviate-labs/agent-go/candid"
"github.com/aviate-labs/agent-go/ic"
"github.com/aviate-labs/agent-go/identity"
"github.com/aviate-labs/agent-go/principal"
)

func Example_anonymous_query() {
ledgerID, _ := principal.Decode("ryjl3-tyaaa-aaaaa-aaaba-cai")
a, _ := agent.New(agent.Config{})
args, err := candid.EncodeValueString("record { account = \"9523dc824aa062dcd9c91b98f4594ff9c6af661ac96747daef2090b7fe87037d\" }")
if err != nil {
fmt.Println(err)
var balance struct {
E8S uint64 `ic:"e8s"`
}
fmt.Println(a.QueryString(ledgerID, "account_balance_dfx", args))
_ = a.Query(ledgerID, "account_balance_dfx", []any{map[string]any{
"account": "9523dc824aa062dcd9c91b98f4594ff9c6af661ac96747daef2090b7fe87037d",
}}, []any{&balance})
fmt.Println(balance.E8S)
// Output:
// (record { 5035232 = 0 : nat64 }) <nil>
// 0
}

func Example_json() {
raw := `{"e8s":1}`
var balance struct {
// Tags can be combined with json tags.
E8S uint64 `ic:"e8s" json:"e8s"`
}
_ = json.Unmarshal([]byte(raw), &balance)
fmt.Println(balance.E8S)

a, _ := agent.New(agent.Config{})
_ = a.Query(ic.LEDGER_PRINCIPAL, "account_balance_dfx", []any{struct {
Account string `json:"account"`
}{
Account: "9523dc824aa062dcd9c91b98f4594ff9c6af661ac96747daef2090b7fe87037d",
}}, []any{&balance}) // Repurposing the balance struct.
rawJSON, _ := json.Marshal(balance)
fmt.Println(string(rawJSON))
// Output:
// 1
// {"e8s":0}
}

func Example_query() {
publicKey, privateKey, _ := ed25519.GenerateKey(rand.Reader)
id, _ := identity.NewEd25519Identity(publicKey, privateKey)
ledgerID, _ := principal.Decode("ryjl3-tyaaa-aaaaa-aaaba-cai")
a, _ := agent.New(agent.Config{
Identity: id,
})
args, err := candid.EncodeValueString("record { account = \"9523dc824aa062dcd9c91b98f4594ff9c6af661ac96747daef2090b7fe87037d\" }")
if err != nil {
fmt.Println(err)
a, _ := agent.New(agent.Config{Identity: id})
var balance struct {
E8S uint64 `ic:"e8s"`
}
fmt.Println(a.QueryString(ledgerID, "account_balance_dfx", args))
_ = a.Query(ledgerID, "account_balance_dfx", []any{map[string]any{
"account": "9523dc824aa062dcd9c91b98f4594ff9c6af661ac96747daef2090b7fe87037d",
}}, []any{&balance})
fmt.Println(balance.E8S)
// Output:
// (record { 5035232 = 0 : nat64 }) <nil>
// 0
}
2 changes: 0 additions & 2 deletions candid/internal/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
"fmt"
"io/ioutil"
"log"

"github.com/pegn/pegn-go"
)

func main() {
Expand Down
20 changes: 0 additions & 20 deletions cmd/README.md

This file was deleted.

40 changes: 40 additions & 0 deletions cmd/goic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Agent CLI

```
go install github.com/aviate-labs/agent-go/cmd/goic
goic version
# 0.0.1
```

## Generating Agents

To generate an agent you will need the `.did` file. Some interface specifications can be fetched directly from a
canister through the `__get_candid_interface_tmp_hack` endpoint (this gets exposed by default w/ Motoko). Based on this
file an agent can be generated.

### Options

The `generate` command can be customized by defining a custom `output` or `packageName` flag.

### Fetch The DID

```shell
goic generate did {PATH_TO_DID} {NAME}
```

```shell
goic fetch ryjl3-tyaaa-aaaaa-aaaba-cai --output=ledger.did
goic generate did ledger.did ledger --output=ledger.go --packageName=main
go fmt ledger.go
```

**OR**

```shell
goic generate remote {CANISTER_ID} {NAME}
```

```shell
goic generate remote ryjl3-tyaaa-aaaaa-aaaba-cai ledger --output=ledger.go --packageName=main
go fmt ledger.go
```
File renamed without changes.
File renamed without changes.
16 changes: 13 additions & 3 deletions cmd/main.go → cmd/goic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,25 @@ package main
import (
"fmt"
"github.com/aviate-labs/agent-go"
"github.com/aviate-labs/agent-go/cmd/internal/cmd"
"github.com/aviate-labs/agent-go/cmd/goic/internal/cmd"
"github.com/aviate-labs/agent-go/gen"
"github.com/aviate-labs/agent-go/principal"
"os"
)

var root = cmd.NewCommandFork(
"agent-go",
"agent-go is a CLI tool for creating a Go agent.",
"goic",
"`goic` is a CLI tool for creating a Go agent.",
cmd.NewCommand(
"version",
"Print the version of `goic`.",
[]string{},
[]cmd.CommandOption{},
func(args []string, options map[string]string) error {
fmt.Println("0.0.1")
return nil
},
),
cmd.NewCommand(
"fetch",
"Fetch a DID from a canister ID.",
Expand Down
Loading

0 comments on commit 7a7802a

Please sign in to comment.