diff --git a/.github/scripts/codegen-check.sh b/.github/scripts/codegen-check.sh new file mode 100644 index 0000000..dbe61ed --- /dev/null +++ b/.github/scripts/codegen-check.sh @@ -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" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89f38c6..242871a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.trix/client-lib/go.mod.hbs b/.trix/client-lib/go.mod.hbs index dbc1394..353a0a0 100644 --- a/.trix/client-lib/go.mod.hbs +++ b/.trix/client-lib/go.mod.hbs @@ -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 diff --git a/.trix/client-lib/protocol.go.hbs b/.trix/client-lib/protocol.go.hbs index 6c3b195..6295901 100644 --- a/.trix/client-lib/protocol.go.hbs +++ b/.trix/client-lib/protocol.go.hbs @@ -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) }