diff --git a/.github/workflows/dx-e2e-beta.yml b/.github/workflows/dx-e2e-beta.yml index 5547ffb..12bffc8 100644 --- a/.github/workflows/dx-e2e-beta.yml +++ b/.github/workflows/dx-e2e-beta.yml @@ -63,6 +63,21 @@ jobs: journey: ${{ matrix.journey }} github-token: ${{ secrets.GITHUB_TOKEN }} + swift-sdk: + name: beta (macos-15 · 11-swift-sdk) + needs: warmup + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '16.4' + - uses: ./.github/actions/dx-e2e-run + with: + channel: beta + journey: 11-swift-sdk + github-token: ${{ secrets.GITHUB_TOKEN }} + java-sdk: name: beta (ubuntu-24.04 · 12-java-sdk) needs: warmup diff --git a/.github/workflows/dx-e2e-stable.yml b/.github/workflows/dx-e2e-stable.yml index 361d386..e31e641 100644 --- a/.github/workflows/dx-e2e-stable.yml +++ b/.github/workflows/dx-e2e-stable.yml @@ -66,6 +66,21 @@ jobs: journey: ${{ matrix.journey }} github-token: ${{ secrets.GITHUB_TOKEN }} + swift-sdk: + name: stable (macos-15 · 11-swift-sdk) + needs: warmup + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '16.4' + - uses: ./.github/actions/dx-e2e-run + with: + channel: stable + journey: 11-swift-sdk + github-token: ${{ secrets.GITHUB_TOKEN }} + java-sdk: name: stable (ubuntu-24.04 · 12-java-sdk) needs: warmup diff --git a/journeys/11-swift-sdk/Package.swift b/journeys/11-swift-sdk/Package.swift new file mode 100644 index 0000000..59d3b79 --- /dev/null +++ b/journeys/11-swift-sdk/Package.swift @@ -0,0 +1,21 @@ +// swift-tools-version: 6.1 + +import PackageDescription + +let package = Package( + name: "SwiftSDKJourney", + platforms: [.macOS(.v14)], + dependencies: [ + .package(url: "https://github.com/tx3-lang/swift-sdk.git", exact: "0.15.0"), + .package(name: "DemoClient", path: "__GENERATED__"), + ], + targets: [ + .executableTarget( + name: "SwiftSDKJourney", + dependencies: [ + .product(name: "Tx3SDK", package: "swift-sdk"), + .product(name: "DemoClient", package: "DemoClient"), + ] + ) + ] +) diff --git a/journeys/11-swift-sdk/README.md b/journeys/11-swift-sdk/README.md new file mode 100644 index 0000000..20ff904 --- /dev/null +++ b/journeys/11-swift-sdk/README.md @@ -0,0 +1,54 @@ +# 11-swift-sdk + +The generated Swift client developer journey against the real assembled toolchain: `trix` +scaffolds a project, its built-in `swift-client` plugin delegates to the real `tx3c` template, +SwiftPM compiles that generated package inside a clean host consumer, and the executable drives +resolve → sign → submit → confirmed against the disposable local Dolos devnet. + +- **Scope:** native macOS runtime journey. It requires Swift 6.1/Xcode 16.4, Git, and the normal + `trix`, `tx3c`, `dolos`, and `cshell` toolchain. No live-network secret is used. +- **Toolchain gate:** `#@ min-tx3c: 0.25.0`, the first tx3c release containing the built-in + `swift-client` template. Older tx3c releases skip before checking the Swift prerequisite. The + journey also fails explicitly + if trix is older than `0.28.0`, the first release registering `swift-client`. Stable and beta + manifests must supply tx3c `^0.25.0` and trix `^0.28.0` for CI to count as coverage. +- **Package pin:** the host package requires the public Swift SDK at exactly `0.15.0` + (published annotated tag `v0.15.0`). The generated + package's `from: 0.15.0` constraint therefore resolves to that exact version as well. +- **Fixtures:** `Package.swift` and `main.swift` model the host application. Generated files stay + under `.tx3/codegen/swift-client/demo/` and are never checked in. + +For pre-publication validation only, `TX3_SWIFT_SDK_PATH=/path/to/reviewed/swift-sdk` makes the +journey clone that checkout into its disposable workdir, tag the clone as `0.15.0`, and configure a +SwiftPM mirror. This exercises the same exact-version package graph without mutating the source +checkout. Released-channel CI intentionally leaves the override unset and resolves the public tag. + +## Phases + +1. `trix init -y` and `trix codegen --plugin swift-client` assert built-in rendering, per-protocol + layout, the embedded TIR envelope, party setters, and the typed transaction method. +2. `swift build --configuration debug` compiles both the generated `DemoClient` library and an + independent executable consumer against the exact SDK package requirement. +3. `devnet.toml` funds the canonical CIP-1852 signer vector from + `sdk-spec/test-vectors/crypto/signer-vectors.json`; the consumer binds that vector's mnemonic + and address with trix's deterministic `bob` receiver. trix's `alice` uses an underived Icarus + root key and cannot be used with the SDK's CIP-1852 signer. The consumer resolves, signs, and + submits a transfer, then waits for a confirmed status from the local devnet. The exit trap + reaps only this journey's Dolos config path, including when devnet startup or the consumer fails. + +## Verification + +```sh +TX3_TRIX_PATH=/path/to/trix \ +TX3_TX3C_PATH=/path/to/tx3c \ +TX3_DOLOS_PATH=/path/to/dolos \ +TX3_CSHELL_PATH=/path/to/cshell \ + ./run.sh --local --journey 11-swift-sdk + +./run.sh --channel stable --journey 11-swift-sdk +./run.sh --channel beta --journey 11-swift-sdk +``` + +Both CI workflows select this journey on native `macos-15` with Xcode `16.4`. A missing Swift +compiler or unresolved SDK package fails the job. Failed runs preserve the consumer, generated +package, devnet files, and command logs through the harness's artifact upload. diff --git a/journeys/11-swift-sdk/devnet.toml b/journeys/11-swift-sdk/devnet.toml new file mode 100644 index 0000000..f0a9197 --- /dev/null +++ b/journeys/11-swift-sdk/devnet.toml @@ -0,0 +1,5 @@ +# Canonical SDK CIP-1852 signer vector, funded only in this disposable devnet. +# Source: sdk-spec/test-vectors/crypto/signer-vectors.json (cardanoCip1852). +[[utxos]] +address = "addr_test1vq8ac7qqy0vtulyl7wntmsxc6wex80gvcyjy33qffrhm7ss9hjl0y" +value = 100000000000 diff --git a/journeys/11-swift-sdk/journey.sh b/journeys/11-swift-sdk/journey.sh new file mode 100755 index 0000000..90d20e6 --- /dev/null +++ b/journeys/11-swift-sdk/journey.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# +# Journey 11 — Swift SDK generated-client DX. See README.md for coverage. +# Run via e2e/run.sh, which provides $TRIX and an isolated working directory. +# +#@ min-tx3c: 0.25.0 + +source "${E2E_LIB:?E2E_LIB not set — run this journey via e2e/run.sh}" + +JOURNEY_HOME="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" + +journey_begin "11-swift-sdk" "generate a Swift client → compile a clean consumer → sign, submit, and confirm on devnet" + +TRIX_VERSION="$("${TRIX}" --version | sed -n 's/^trix //p')" +version_ge "${TRIX_VERSION}" "0.28.0" \ + || die "journey 11 requires trix >= 0.28.0 with the built-in swift-client plugin" + +command -v swift >/dev/null 2>&1 \ + || die "swift not found — journey 11 requires the approved macOS/Xcode runner" +command -v git >/dev/null 2>&1 || die "git not found — SwiftPM package resolution requires git" + +# 1. Scaffold under the stable protocol name `demo`, then drive trix into the +# built-in tx3c swift-client template. The output is a standalone SwiftPM +# package, unlike the bare-module TypeScript output exercised by journey 07. +mkdir demo && cd demo +run_cmd "trix init -y — scaffold a new project" "${TRIX}" init -y +run_cmd "trix codegen --plugin swift-client — generate bindings" \ + "${TRIX}" codegen --plugin swift-client +assert_output_contains "Added [[codegen]]" "plugin entry seeded into trix.toml" +assert_output_contains "Bindgen successful for 'demo'" +grep -qiF "Reading template from" "${LAST_OUTPUT_FILE}" \ + && die "built-in plugin downloaded templates instead of using tx3c's built-in template" +ok "built-in swift-client template rendered by tx3c, no template download" + +GENERATED="${PWD}/.tx3/codegen/swift-client/demo" +assert_exists "${GENERATED}/Package.swift" "generated SwiftPM package manifest" +assert_exists "${GENERATED}/Sources/DemoClient/Client.swift" "generated typed client" +assert_exists "${GENERATED}/Sources/DemoClient/Types.swift" "generated parameter types" +[[ -e "${PWD}/.tx3/codegen/swift-client/Package.swift" ]] \ + && die "generated package leaked outside the per-protocol subdirectory" + +run_cmd "read the generated client" cat "${GENERATED}/Sources/DemoClient/Client.swift" +assert_output_contains 'PROTOCOL_NAME = "demo"' "protocol identity embedded" +assert_output_contains "TRANSFER_TIR" "transfer TIR envelope embedded" +assert_output_contains "struct DemoClient" "typed Swift client emitted" +assert_output_contains "func withSender" "sender party setter emitted" +assert_output_contains "func transfer" "typed transfer builder emitted" + +# 2. Compile a separate host package. Its direct exact SDK requirement pins the +# generated package's compatible `from: 0.15.0` constraint to precisely 0.15.0. +mkdir -p app/Sources/SwiftSDKJourney +sed "s|__GENERATED__|${GENERATED}|g" "${JOURNEY_HOME}/Package.swift" > app/Package.swift +cp "${JOURNEY_HOME}/main.swift" app/Sources/SwiftSDKJourney/main.swift + +# Pre-publication runs may point at a reviewed SDK checkout without mutating it. +# Clone it inside the disposable journey workdir, add the release-equivalent +# tag there, and use SwiftPM's official mirror mechanism. Released-channel CI +# leaves this unset and therefore resolves the real public 0.15.0 tag. +if [[ -n "${TX3_SWIFT_SDK_PATH:-}" ]]; then + [[ -f "${TX3_SWIFT_SDK_PATH}/Package.swift" ]] \ + || die "TX3_SWIFT_SDK_PATH must name a Swift SDK checkout" + run_cmd "clone reviewed Swift SDK checkout for local package resolution" \ + git clone --quiet "${TX3_SWIFT_SDK_PATH}" .tx3/swift-sdk-mirror + run_cmd "tag the disposable mirror as the release-equivalent 0.15.0" \ + git -C .tx3/swift-sdk-mirror tag 0.15.0 + run_cmd "configure the consumer to use the local SDK mirror" \ + swift package --package-path app config set-mirror \ + --original https://github.com/tx3-lang/swift-sdk.git \ + --mirror "file://${PWD}/.tx3/swift-sdk-mirror" +fi + +run_cmd "swift build --configuration debug — compile generated client and consumer" \ + swift build --package-path app --configuration debug +assert_exists "app/.build/debug/SwiftSDKJourney" "host consumer compiled" + +# 3. Fund the canonical CIP-1852 signer vector in this disposable devnet. +# trix's alice uses an underived Icarus root key, so it cannot be bound to +# CardanoSigner. These public SDK vector credentials are devnet-only fixtures. +cp "${JOURNEY_HOME}/devnet.toml" devnet.toml +ALICE="addr_test1vq8ac7qqy0vtulyl7wntmsxc6wex80gvcyjy33qffrhm7ss9hjl0y" +BOB="$("${TRIX}" identities bob address-testnet 2>/dev/null | grep '^addr' | head -n1)" +[[ -n "${BOB}" ]] || die "could not resolve bob's testnet address" +ALICE_MNEMONIC="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + +DEVNET_HOME="${PWD}/.tx3/dolos" +cleanup_devnet() { + # Install before startup and match only this journey's unique config path. + pkill -f "dolos.*${DEVNET_HOME}/dolos[.]toml daemon" >/dev/null 2>&1 || true +} +trap cleanup_devnet EXIT +run_cmd "trix devnet --background — start a local devnet" "${TRIX}" devnet --background +assert_output_contains "devnet started in background" +for _ in $(seq 1 30); do + (exec 3<>/dev/tcp/127.0.0.1/8164) 2>/dev/null && { exec 3>&- 3<&-; break; } + sleep 1 +done +sleep 3 + +run_cmd "SwiftSDKJourney — resolve, sign, submit, and confirm a transfer" \ + app/.build/debug/SwiftSDKJourney \ + "http://127.0.0.1:8164" "${ALICE}" "${BOB}" "${ALICE_MNEMONIC}" +assert_output_contains '"hash"' "submitted transaction hash returned" +if grep -Eq '"status":"(confirmed|finalized)"' "${LAST_OUTPUT_FILE}"; then + ok "submitted transaction confirmed" +else + die "submitted transaction did not reach confirmed or finalized status" +fi + +journey_end diff --git a/journeys/11-swift-sdk/main.swift b/journeys/11-swift-sdk/main.swift new file mode 100644 index 0000000..bf0677a --- /dev/null +++ b/journeys/11-swift-sdk/main.swift @@ -0,0 +1,30 @@ +import DemoClient +import Foundation +import Tx3SDK + +let arguments = CommandLine.arguments +guard arguments.count == 5, let endpoint = URL(string: arguments[1]) else { + FileHandle.standardError.write( + Data("usage: SwiftSDKJourney \n".utf8) + ) + exit(2) +} + +let sender = try Address(arguments[2]) +let receiver = try Address(arguments[3]) +let signer = try CardanoSigner(mnemonic: arguments[4], address: sender) + +let client = DemoClient( + options: ClientOptions(endpoint: endpoint), + profile: .local +) +.withSender(.signer(signer)) +.withReceiver(.address(receiver)) + +let resolved = try await client.transfer(TransferParams(quantity: 2_000_000)).resolve() +let submitted = try await resolved.sign().submit() +let status = try await submitted.waitForConfirmed( + PollConfig(attempts: 30, delay: .seconds(1)) +) + +print("{\"hash\":\"\(submitted.hash)\",\"status\":\"\(status.stage.rawValue)\"}")