Skip to content
Merged
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
37 changes: 37 additions & 0 deletions .github/scripts/codegen-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
#
# CI artifact — not part of the SDK.
#
# Renders the .trix/client-lib codegen plugin against the shared transfer
# fixture and verifies the result the way a consumer would: the rendered module
# resolves the published go-sdk at the version its generated go.mod pins — no
# replace directives.
#
# Requires `tx3c` and `go` on PATH.
set -euo pipefail

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
gen="$(mktemp -d)"
trap 'rm -rf "$gen"' EXIT

tx3c codegen \
--tii "$repo_root/sdk/testdata/transfer.tii" \
--template "$repo_root/.trix/client-lib" \
--output "$gen"

for f in protocol.go go.mod; do
test -f "$gen/$f" || { echo "missing generated file: $f"; exit 1; }
done

for sym in \
'TargetTIIVersion' \
'type TransferParams struct' \
'TRANSFER_TIR' \
'func (c *Client) Transfer(' \
'var Profiles'; do
grep -qF "$sym" "$gen/protocol.go" || { echo "generated protocol.go missing: $sym"; exit 1; }
done

( cd "$gen" && go mod tidy && go build ./... )

echo "codegen check passed"
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,27 @@ jobs:
working-directory: ./sdk
run: go test ./... -count=1

codegen:
name: codegen
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.24.2
cache: true
cache-dependency-path: sdk/go.sum

- name: Setup tx3 toolchain
uses: tx3-lang/actions/setup@v1

- name: Render and verify codegen plugin
run: bash .github/scripts/codegen-check.sh

e2e:
name: e2e
runs-on: ubuntu-latest
Expand Down
6 changes: 2 additions & 4 deletions .trix/client-lib/go.mod.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
module {{protocolName}}
module {{tii.protocol.name}}

go 1.24.2

require github.com/tx3-lang/go-sdk/sdk v0.0.3

require github.com/google/uuid v1.6.0 // indirect
require github.com/tx3-lang/go-sdk/sdk v0.11.0
141 changes: 77 additions & 64 deletions .trix/client-lib/protocol.go.hbs
Original file line number Diff line number Diff line change
@@ -1,98 +1,111 @@
// This file is auto-generated.
// This file is auto-generated by trix codegen.
// Target TII version: {{tii.tii.version}}

package protocol

import (
"encoding/hex"
"context"
"encoding/json"
"fmt"

"github.com/tx3-lang/go-sdk/sdk/trp"
"github.com/tx3-lang/go-sdk/sdk/core"
"github.com/tx3-lang/go-sdk/sdk/trp"
)

const DefaultTRPEndpoint = "{{trpEndpoint}}"
// Protocol identity, embedded from the TII at codegen time.
const (
ProtocolName = "{{tii.protocol.name}}"
ProtocolVersion = "{{tii.protocol.version}}"
TargetTIIVersion = "{{tii.tii.version}}"
)

var DefaultHeaders = map[string]string{
{{#each headers}}
"{{@key}}": "{{this}}",
{{/each}}
}
{{#if tii.environment}}
// EnvironmentSchema is the JSON Schema of the protocol environment, embedded
// verbatim from the TII.
const EnvironmentSchema = `{{{json tii.environment}}}`

var DefaultEnvArgs = map[string]string{
{{#each envArgs}}
"{{@key}}": "{{this}}",
{{/each}}
{{/if}}
// Profile holds the environment values and party addresses for a named TII profile.
type Profile struct {
Environment core.EnvMap `json:"environment"`
Parties map[string]string `json:"parties"`
}

const profilesJSON = `{{{json tii.profiles}}}`

type Bytes []byte
func (h Bytes) MarshalJSON() ([]byte, error) {
hexStr := "0x" + hex.EncodeToString(h)
return json.Marshal(hexStr)
}

{{#each transactions}}
// {{pascalCase params_name}} defines the parameters for the {{pascalCase name}} transaction
type {{pascalCase params_name}} struct {
{{#each parameters}}
{{pascalCase name}} {{typeFor type_name "go"}} `json:"{{snakeCase name}}"` // {{type_name}}
// Profiles enumerates the profiles embedded from the TII, keyed by name.
var Profiles = func() map[string]Profile {
var parsed map[string]Profile
if err := json.Unmarshal([]byte(profilesJSON), &parsed); err != nil {
panic("codegen: invalid embedded profiles: " + err.Error())
}
return parsed
}()

{{#each tii.transactions}}
// {{pascalCase @key}}Params holds the arguments for the {{@key}} transaction.
type {{pascalCase @key}}Params struct {
{{#each params.properties}}
{{pascalCase @key}} {{schemaTypeFor this "go"}} `json:"{{@key}}"`
{{/each}}
}

// {{constantCase constant_name}} contains the TIR content for the {{pascalCase name}} transaction
var {{constantCase constant_name}} = trp.TirInfo{
Content: "{{ir_bytes}}",
Encoding: "hex",
Version: "{{ir_version}}",
// {{constantCase @key}}_TIR is the embedded TIR envelope for the {{@key}} transaction.
var {{constantCase @key}}_TIR = core.TirEnvelope{
Content: "{{tir.content}}",
Encoding: "{{tir.encoding}}",
Version: "{{tir.version}}",
}

{{/each}}
// Client provides methods to interact with the tx3 protocol
// Client is a thin protocol facade over the TRP client.
type Client struct {
client *trp.Client
trp *trp.Client
profile *Profile
}

// NewClient creates a new Client with the given options
// NewClient creates a Client over the given TRP client options.
func NewClient(options trp.ClientOptions) *Client {
return &Client{
client: trp.NewClient(options),
}
return &Client{trp: trp.NewClient(options)}
}

// NewClientWithDefaults creates a new Client with default options
func NewClientWithDefaults() *Client {
options := trp.ClientOptions{
Endpoint: DefaultTRPEndpoint,
Headers: DefaultHeaders,
EnvArgs: make(map[string]interface{}),
// WithProfile selects an embedded profile by name. Its environment values and
// party addresses are applied to every subsequent transaction. It panics if
// name is not a profile declared by the protocol.
func (c *Client) WithProfile(name string) *Client {
profile, ok := Profiles[name]
if !ok {
panic(fmt.Sprintf("unknown profile %q", name))
}
c.profile = &profile
return c
}

// Convert string env args to interface{}
for k, v := range DefaultEnvArgs {
options.EnvArgs[k] = v
func (c *Client) resolve(ctx context.Context, tir core.TirEnvelope, args core.ArgMap) (*trp.TxEnvelope, error) {
params := trp.ResolveParams{Tir: tir, Args: args}
if c.profile != nil {
params.Env = c.profile.Environment
for name, address := range c.profile.Parties {
if _, set := args[name]; !set {
args[name] = address
}
}
}

return NewClient(options)
return c.trp.Resolve(ctx, params)
}

{{#each transactions}}
// {{pascalCase function_name}} resolves the {{pascalCase name}} transaction with the given parameters
func (c *Client) {{pascalCase function_name}}(args {{pascalCase params_name}}) (*trp.TxEnvelope, error) {
return c.client.Resolve(trp.ProtoTxRequest{
Tir: {{constantCase constant_name}},
Args: args,
})
}
{{#each tii.transactions}}
// {{pascalCase @key}} resolves the {{@key}} transaction.
func (c *Client) {{pascalCase @key}}(ctx context.Context, args {{pascalCase @key}}Params) (*trp.TxEnvelope, error) {
return c.resolve(ctx, {{constantCase @key}}_TIR, core.ArgMap{
{{#each params.properties}}
"{{@key}}": args.{{pascalCase @key}},
{{/each}}

// Submit sends a signed transaction to the network
func (c *Client) Submit(tx trp.TxEnvelope, witnesses []trp.WitnessInput) (*trp.SubmitResponse, error) {
return c.client.Submit(tx, witnesses)
})
}

// Instance of the singleton client
var DefaultClient *Client

// init function runs automatically when the package is imported
func init() {
DefaultClient = NewClientWithDefaults()
{{/each}}
// Submit sends a signed transaction to the network.
func (c *Client) Submit(ctx context.Context, params trp.SubmitParams) (*trp.SubmitResponse, error) {
return c.trp.Submit(ctx, params)
}
Loading