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
1 change: 1 addition & 0 deletions .github/release-train
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.15
136 changes: 115 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,139 @@ name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
defaults:
run:
shell: bash
unit:
name: unit (${{ matrix.runner }})
strategy:
fail-fast: false
matrix:
runner:
- ubuntu-24.04
- ubuntu-24.04-arm
- windows-2025
- windows-11-arm
- macos-15-intel
- macos-14
runner: [ubuntu-24.04, macos-15, windows-2025]
runs-on: ${{ matrix.runner }}
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
cache: maven
- name: Check formatting
run: ./mvnw -B -ntp spotless:check
- name: Run unit tests
run: ./mvnw -B -ntp test
- name: Verify package
run: ./mvnw -B -ntp verify
- name: Resolve package from an external consumer
run: ./mvnw -B -ntp install && ./mvnw -B -ntp -f examples/consumer/pom.xml package
- run: ./mvnw -B -ntp spotless:check
shell: bash
- run: ./mvnw -B -ntp test
shell: bash
- run: ./mvnw -B -ntp verify
shell: bash
- name: Build and run a clean external consumer
shell: bash
run: |
set -euo pipefail
repository="${RUNNER_TEMP}/consumer-m2"
consumer="${RUNNER_TEMP}/consumer-app"
cp -R examples/consumer "$consumer"
./mvnw -B -ntp -Drevision=0.15.0-SNAPSHOT -Dmaven.repo.local="$repository" install
./mvnw -B -ntp -Dmaven.repo.local="$repository" \
-Dtx3.sdk.version=0.15.0-SNAPSHOT \
-f "$consumer/pom.xml" verify exec:java
- name: Record dependency tree
shell: bash
run: ./mvnw -B -ntp dependency:tree -DoutputFile=target/dependency-tree.txt
- name: Inspect package contents
shell: bash
run: |
set -euo pipefail
jar tf target/tx3-sdk-0.15.0.jar | tee target/package-contents.txt
grep -qx 'META-INF/LICENSE' target/package-contents.txt
grep -qx 'land/tx3/sdk/Tx3Client.class' target/package-contents.txt
- uses: actions/upload-artifact@v4
with:
name: dependency-tree-${{ matrix.runner }}
path: target/dependency-tree.txt
name: package-evidence-${{ matrix.runner }}
path: |
target/tx3-sdk-0.15.0.jar
target/package-contents.txt
target/dependency-tree.txt

e2e:
name: live TRP e2e
runs-on: ubuntu-24.04
timeout-minutes: 20
env:
CI: "true"
TRP_ENDPOINT_PREPROD: ${{ secrets.TRP_ENDPOINT_PREPROD }}
TRP_API_KEY_PREPROD: ${{ secrets.TRP_API_KEY_PREPROD }}
TEST_PARTY_A_ADDRESS: ${{ secrets.TEST_PARTY_A_ADDRESS }}
TEST_PARTY_A_MNEMONIC: ${{ secrets.TEST_PARTY_A_MNEMONIC }}
TEST_PARTY_B_ADDRESS: ${{ secrets.TEST_PARTY_B_ADDRESS }}
TEST_PARTY_B_MNEMONIC: ${{ secrets.TEST_PARTY_B_MNEMONIC }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
cache: maven
- name: Require canonical e2e configuration
shell: bash
run: |
set -euo pipefail
for name in TRP_ENDPOINT_PREPROD TRP_API_KEY_PREPROD TEST_PARTY_A_ADDRESS TEST_PARTY_A_MNEMONIC TEST_PARTY_B_ADDRESS TEST_PARTY_B_MNEMONIC; do
if [ -z "${!name:-}" ]; then
echo "Missing required e2e configuration: $name" >&2
exit 1
fi
done
- run: ./mvnw -B -ntp -Pe2e verify

codegen:
name: generated Java client
runs-on: ubuntu-24.04
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
cache: maven
- uses: dtolnay/rust-toolchain@1.91
- name: Install this checkout as the snapshot runtime
run: ./mvnw -B -ntp -Drevision=0.15.0-SNAPSHOT -Dmaven.repo.local="${RUNNER_TEMP}/codegen-m2" -DskipTests install
- name: Build pinned tx3c
uses: actions/checkout@v4
with:
repository: tx3-lang/tx3
ref: 456f68503a1306c865b17666a9ee1f1d801bd23b
path: .tx3c-source
persist-credentials: false
- run: cargo build -p tx3c --locked
working-directory: .tx3c-source
- name: Render and compile canonical fixtures
shell: bash
run: |
set -euo pipefail
for fixture in transfer complex; do
output="${RUNNER_TEMP}/generated-${fixture}"
.tx3c-source/target/debug/tx3c codegen \
--tii "src/test/resources/fixtures/${fixture}.tii" \
--template java-client \
--output "$output"
./mvnw -B -ntp -f "$output/pom.xml" \
-Dmaven.repo.local="${RUNNER_TEMP}/codegen-m2" \
-Dtx3.sdk.version=0.15.0-SNAPSHOT verify
done
79 changes: 79 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Release

on:
push:
tags:
- "v*.*.*"

permissions:
contents: read

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
publish:
name: publish Maven Central package
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
cache: maven
server-id: central
server-username: MAVEN_CENTRAL_USERNAME
server-password: MAVEN_CENTRAL_TOKEN
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE
- name: Require release credentials
env:
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
MAVEN_GPG_PRIVATE_KEY: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: |
set -euo pipefail
for name in MAVEN_CENTRAL_USERNAME MAVEN_CENTRAL_TOKEN MAVEN_GPG_PRIVATE_KEY MAVEN_GPG_PASSPHRASE; do
if [ -z "${!name:-}" ]; then
echo "Missing required release secret: $name" >&2
exit 1
fi
done
- name: Validate tag, package version, and fleet train
run: |
set -euo pipefail
if [ "$(git cat-file -t "refs/tags/${GITHUB_REF_NAME}")" != "tag" ]; then
echo "Release tag must be annotated: ${GITHUB_REF_NAME}" >&2
exit 1
fi
scripts/check-release-version.sh "${GITHUB_REF_NAME}"
- name: Verify signing identity
run: |
set -euo pipefail
expected="6F26BB15DE0BACF2150B7C359BFB64674ED089FE"
actual="$(gpg --batch --with-colons --fingerprint | awk -F: '$1 == "fpr" { print $10; exit }')"
if [ "$actual" != "$expected" ]; then
echo "Imported signing key fingerprint does not match the approved identity." >&2
exit 1
fi
- name: Test, package, sign, and publish
env:
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: ./mvnw -B -ntp -Prelease deploy
- name: Verify clean Maven Central consumption
run: |
set -euo pipefail
repository="$(mktemp -d)"
consumer="$(mktemp -d)"
cp -R examples/consumer/. "$consumer"
./mvnw -B -ntp -Dmaven.repo.local="$repository" \
-Dtx3.sdk.version="${GITHUB_REF_NAME#v}" \
-f "$consumer/pom.xml" verify exec:java
47 changes: 45 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,10 @@ var status = submitted
`waitForConfirmed` accepts confirmed or finalized status, while `waitForFinalized` accepts only
finalized status. Dropped and rolled-back transactions fail with `PollingException.Kind.TERMINAL_STAGE`;
exhausted attempts fail with `PollingException.Kind.TIMEOUT`. Cancelling a returned polling future
cancels its in-flight status request or scheduled delay. `submit()` rejects a TRP response whose
hash differs from the signed transaction with `SubmissionException`.
cancels its in-flight status request or scheduled delay. Status polling retries transient network and
timeout failures, HTTP 408/425/429 responses, and HTTP 5xx responses within the configured attempt
limit; other transport failures are returned immediately. `submit()` rejects a TRP response whose hash
differs from the signed transaction with `SubmissionException`.

## Development

Expand All @@ -161,6 +163,47 @@ To record the resolved dependency tree exactly as CI does:

The test suite is deterministic and needs no TRP endpoint or credentials.

Unit tests and live tests are selected independently. The ordinary `test` and `verify` commands
exclude the `e2e` JUnit tag. To exercise the canonical transfer lifecycle against preprod, set
`TRP_ENDPOINT_PREPROD`, `TRP_API_KEY_PREPROD`, `TEST_PARTY_A_ADDRESS`,
`TEST_PARTY_A_MNEMONIC`, `TEST_PARTY_B_ADDRESS`, and `TEST_PARTY_B_MNEMONIC`, then run:

```shell
./mvnw -B -ntp -Pe2e verify
```

Without those variables a local e2e run is skipped with the missing names. CI treats any missing
value as an error. The live suite loads the pinned `transfer.tii`, resolves, signs with party A,
submits, and waits for confirmed status; finalized polling remains covered by mocked-TRP unit tests
because preprod does not report finalized transactions through `checkStatus`. The suite also checks
typed missing-argument and bad-endpoint failures. Secret values are never logged or stored by the
tests.

## Generated clients

The `java-client` template is built into `tx3c`. Generate typed bindings from a TII document and
compile them against this runtime with:

```shell
tx3c codegen --tii transfer.tii --template java-client --output generated-client
./mvnw -B -ntp -f generated-client/pom.xml verify
```

Generated clients use the same `Tx3ClientBuilder`, `ArgValue`, lifecycle, signer, and typed error
APIs shown above and do not need the source TII at runtime. CI renders the canonical transfer and
complex fixtures with the pinned tx3c revision and compiles both from a clean output directory.

## Release mechanics

Releases are driven only by annotated `vMAJOR.MINOR.PATCH` tags. The tag must exactly match the
POM version and the `MAJOR.MINOR` value in `.github/release-train`. The release workflow reruns the
checks, builds the main, source, and Javadoc artifacts, verifies the approved signing-key
fingerprint, signs the artifacts, publishes `land.tx3:tx3-sdk` through the Maven Central Portal,
waits for publication, and resolves the released version from a fresh Maven repository before
running the external consumer. Publishing credentials and signing material are supplied only by
the restricted organization secrets documented for repository operators; local builds do not
need them.

## Platform scope

The supported baseline is Java SE 21 on macOS, Linux, and Windows, on x64 and ARM64 where hosted
Expand Down
11 changes: 10 additions & 1 deletion examples/consumer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
<artifactId>tx3-sdk-consumer</artifactId>
<version>1.0.0</version>
<properties>
<tx3.sdk.version>0.15.0</tx3.sdk.version>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>land.tx3</groupId>
<artifactId>tx3-sdk</artifactId>
<version>0.15.0</version>
<version>${tx3.sdk.version}</version>
</dependency>
</dependencies>
<build>
Expand All @@ -34,6 +35,14 @@
<artifactId>maven-jar-plugin</artifactId>
<version>3.5.1</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.3</version>
<configuration>
<mainClass>example.Consumer</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Loading
Loading