Skip to content

Commit 18fb5e9

Browse files
willemnealchadoh
andauthored
feat: add managed contracts and stellar-registry-build (#322)
# Changes to Registry Contract ## Managed registries The biggest change is now when constructing the contract there is an argument for an optional manager. If provided (or added later), the manager account must authorize any initial publishes or deploying or registering contract names. This way we can have an initial root registry which only contains verified published wasms and named contract, which allows us and ecosystem partners to ensure that current established contracts will be in the verified registry. If the manager is not provided then anyone can publish or register a contract name with a registry. ## Root registry The root registry is managed and in the constructor if `is_root` is true, deploys the `unverified` registry as unmanaged and registers the name. ## Name resolution Since there are now multiple registries the CLI and other tooling will resolve names using the root registry as the source of truth. For example, `unverified/hello_world` would first fetch the contract id for `unverified` from the root registry and then search in that registry for `hello_world`. ## `NormalizedName` This new type helps to ensure that the names for publishing and registering contract ids are valid and normalized, e.g. `contract_name` becomes `contract-name` . It is used as keys in storage, this way inputs are forced to be parsed. As @ifropc had shown in his recent PR it is easy to miss a path if not done this way. ## `register_contract` and `fetch_contract_owner` The other big change to the contract is a method to register a contract name without deploying the contract through the registry. This is required to support existing contracts and those who want to handle deploying themselves. To support this the storage was updated to track the owner account which registered the name. And a new `Register` event was added. This means that when deploying through the contract a deploy and register event is made. `fetch_contract_owner` was also added to allow looking up the owner who registered the contract name. ## `deploy_unnamed` Since the `root` contract is now managed, we added a way to deploy a published wasm without registering a contract name. This allows a few possibilities: - A project that wants to manage Wasm binaries on Registry while obfuscating their contract itself. Perhaps they are not ready to have a public-facing contract yet, or perhaps they never will want their contract to be public. - People, especially learners, who want to deploy a throwaway contract directly from the Wasm Registry, without having a permanent entry show up for this contract in the Contract Registry. # Changes to `stellar-registry-cli` ## Using new name resolution The contract and wasm name resolution was added to the registry cli commands anywhere a name is used. So for example, ```bash stellar registry fetch_wash_hash --wasm-name unverified/hello_world ``` — this will fetch a contract called `hello-world` from the `unverified` registry. ## New commands The following commands were added to support the available contract methods: - `deploy-unnamed` - `fetch-contract-id` - `fetch-contract-owner` - `publish-hash` - `registry-contract` # New `stellar-registry-build` crate This crate helps to resolve contracts both at build time and for the `stellar-registry-cli`. As mentioned above, resolving a contract name may now involve searching multiple registries, e.g. `unverified/hello_world`. It can also resolves contracts the same way as stellar CLI, via the deployer and salt. For example: ```rust import_from_registry("unverified/hello_world"); import_from_alias("hello_world"); import_from_deployer("hello_world:G233..."); ``` # Add Github Attestation The new github action and workflow which is triggered by any tag with the pattern `registry-v*` and publishes a release including a wasm file with an attestation. A `from-github` option was added to the registry CLI`s `publish` command, which downloads the wasm file with the added github attestation to publish. --------- Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com>
1 parent 7e73907 commit 18fb5e9

65 files changed

Lines changed: 5522 additions & 1427 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cargo/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
# b = "build"
66
# c = "check"
7-
# t = "nextest run "
7+
t = "nextest run "
88
r = "run --quiet --"
99
# rr = "run --release"
1010
# recursive_example = "rr --example recursions"

.config/nextest.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[[profile.default.overrides]]
2-
filter = 'package(stellar-scaffold-cli) | package(stellar-registry-cli) | package(registry)'
2+
filter = 'package(stellar-scaffold-cli) | package(stellar-registry-cli)'
33
slow-timeout = { period = "90s", terminate-after = 10 }
4-
retry = 3
4+
retry = 3
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Build and Release # name it whatever you like
2+
on:
3+
push:
4+
tags:
5+
- "registry-v*" # triggered whenever a new tag (prefixed with "v") is pushed to the repository
6+
7+
permissions: # required permissions for the workflow
8+
id-token: write
9+
contents: write
10+
attestations: write
11+
12+
jobs:
13+
release-contract-a:
14+
uses: ./.github/workflows/release-contract.yml
15+
with:
16+
release_name: ${{ github.ref_name }} # use git tag as unique release name
17+
release_description: "Contract release" # some boring placeholder text to attach
18+
home_domain: "theaha.co" # home domain for the contract
19+
# relative_path: '["contracts/registry"]' # relative path to your really awesome contract
20+
package: "registry" # package name to build
21+
secrets: # the authentication token will be automatically created by GitHub
22+
release_token: ${{ secrets.GITHUB_TOKEN }} # don't modify this line
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
name: Build and Release Contract
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
relative_path:
7+
description: "Relative path to the working directory"
8+
type: string
9+
required: false
10+
make_target:
11+
description: "Make target for the contract"
12+
type: string
13+
required: false
14+
package:
15+
description: "Package to build"
16+
type: string
17+
required: false
18+
release_name:
19+
description: "Name for the release"
20+
required: true
21+
type: string
22+
release_description:
23+
description: "Description for the release"
24+
required: false
25+
type: string
26+
home_domain:
27+
description: "Home domain"
28+
required: false
29+
type: string
30+
secrets:
31+
release_token:
32+
description: "Github token"
33+
required: true
34+
35+
permissions:
36+
id-token: write
37+
contents: write
38+
attestations: write
39+
40+
jobs:
41+
build:
42+
runs-on: ubuntu-latest
43+
steps:
44+
- uses: cargo-bins/cargo-binstall@main
45+
- name: install scaffold-stellar
46+
run: cargo binstall -y stellar-scaffold-cli
47+
- name: Set working directory
48+
run: |
49+
RANDOM_DIR=$(openssl rand -hex 8)
50+
WORK_DIR="${{ github.workspace }}/$RANDOM_DIR"
51+
mkdir -p "$WORK_DIR"
52+
echo "WORK_DIR=$WORK_DIR" >> $GITHUB_ENV
53+
echo "Using working directory: $WORK_DIR"
54+
55+
- name: Checkout code
56+
uses: actions/checkout@v4
57+
with:
58+
path: ${{ env.WORK_DIR }}
59+
60+
- name: Set relative path
61+
run: |
62+
# Set relative path after checking out the code
63+
if [ "${{ inputs.relative_path }}" ]; then
64+
WORK_DIR="$WORK_DIR/${{ inputs.relative_path }}"
65+
echo "WORK_DIR=$WORK_DIR" >> $GITHUB_ENV
66+
echo "Using relative path: $WORK_DIR"
67+
fi
68+
69+
- name: Run Make (if applicable)
70+
if: inputs.make_target != ''
71+
working-directory: ${{ env.WORK_DIR }}
72+
run: |
73+
make ${{ inputs.make_target }}
74+
75+
- name: Update Rust and Add wasm32 Target
76+
working-directory: ${{ env.WORK_DIR }}
77+
run: |
78+
rustup update
79+
rustup target add wasm32v1-none
80+
81+
- name: Print versions
82+
run: |
83+
rustc --version
84+
cargo --version
85+
86+
- name: Install jq
87+
run: sudo apt-get update && sudo apt-get install -y jq
88+
89+
- name: Get Cargo.toml metadata
90+
working-directory: ${{ env.WORK_DIR }}
91+
run: |
92+
if [ ! -f "Cargo.toml" ]; then
93+
echo "Cargo.toml does not exist"
94+
exit 1
95+
fi
96+
CARGO_METADATA=$(cargo metadata --format-version=1 --no-deps)
97+
echo "CARGO_METADATA=$CARGO_METADATA" >> $GITHUB_ENV
98+
99+
- name: Set output directory path
100+
run: |
101+
RANDOM_DIR=$(openssl rand -hex 8)
102+
OUTPUT="$WORK_DIR/$RANDOM_DIR"
103+
echo "OUTPUT=$OUTPUT" >> $GITHUB_ENV
104+
105+
- name: Build contract
106+
uses: stellar/stellar-cli@v22.8.1
107+
with:
108+
version: "22.8.1"
109+
- run: |
110+
# Navigate to the working directory
111+
cd ${WORK_DIR}
112+
113+
# Build command arguments
114+
COMMAND_ARGS="--out-dir ${OUTPUT} --meta source_repo=github:${{ github.repository }}"
115+
if [ "${{ inputs.package }}" ]; then
116+
COMMAND_ARGS="--package ${{ inputs.package }} $COMMAND_ARGS"
117+
PACKAGE_NAME=${{ inputs.package }}
118+
else
119+
PACKAGE_NAME=$(grep -m1 '^name =' Cargo.toml | cut -d '"' -f2)
120+
fi
121+
if [ "${{ inputs.home_domain }}" ]; then
122+
COMMAND_ARGS="$COMMAND_ARGS --meta home_domain=${{ inputs.home_domain }}"
123+
fi
124+
125+
# Build the contract
126+
stellar scaffold build $COMMAND_ARGS
127+
128+
# Get the package version
129+
PACKAGE_VERSION=$(echo "$CARGO_METADATA" | jq '.packages[] | select(.name == "'"${PACKAGE_NAME}"'") | .version' | sed -e 's/"//g')
130+
if [ -z "$PACKAGE_VERSION" ]; then
131+
echo "ERROR: Failed to get the package version"
132+
exit 1
133+
fi
134+
135+
# Build the wasm file name
136+
WASM_FILE_NAME="${PACKAGE_NAME}_v${PACKAGE_VERSION}.wasm"
137+
138+
# Navigate to the output directory
139+
cd ${OUTPUT}
140+
141+
# Find the .wasm file and copy it as unoptimized.wasm for hash calculation
142+
find ${OUTPUT} -name "*.wasm" -exec cp {} ${WASM_FILE_NAME} \;
143+
stellar contract optimize --wasm ${WASM_FILE_NAME} --wasm-out ${WASM_FILE_NAME}
144+
145+
# Calculate the hash of the wasm file
146+
WASM_HASH=$(sha256sum $WASM_FILE_NAME | cut -d ' ' -f 1)
147+
148+
# Set environment variables
149+
echo "PACKAGE_VERSION=$PACKAGE_VERSION" >> $GITHUB_ENV
150+
echo "WASM_FILE_NAME=$WASM_FILE_NAME" >> $GITHUB_ENV
151+
echo "WASM_HASH=$WASM_HASH" >> $GITHUB_ENV
152+
echo "PACKAGE_NAME=$PACKAGE_NAME" >> $GITHUB_ENV
153+
echo "BUILD_INFO=$BUILD_INFO" >> $GITHUB_ENV
154+
155+
- name: Build release name
156+
run: |
157+
CLI_VERSION=$(stellar --version | grep -oP 'stellar \K\S+')
158+
if [ -n "${{ inputs.relative_path }}" ]; then
159+
relative_path=$(echo "_${{ inputs.relative_path }}" | sed 's/\W\+/_/g')
160+
fi
161+
162+
# Check if the release_name input is equal to PACKAGE_VERSION
163+
if [ "${{ inputs.release_name }}" != "${PACKAGE_VERSION}" ] && [ "${{ inputs.release_name }}" != "v${PACKAGE_VERSION}" ]; then
164+
pkg_version="_pkg${PACKAGE_VERSION}"
165+
else
166+
pkg_version=""
167+
fi
168+
169+
TAG_NAME="${{ inputs.release_name }}"
170+
echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV
171+
172+
- name: Create release
173+
working-directory: ${{ env.OUTPUT }}
174+
env:
175+
GH_TOKEN: ${{ secrets.release_token }}
176+
run: |
177+
gh release create "${{ env.TAG_NAME }}" "${{ env.OUTPUT }}/${{ env.WASM_FILE_NAME }}" \
178+
--title "${{ env.TAG_NAME }}" \
179+
--notes "${{ inputs.release_description }}"
180+
shell: bash
181+
182+
- name: Set up Node.js
183+
uses: actions/setup-node@v4
184+
with:
185+
node-version: "14"
186+
187+
- name: Build output
188+
run: |
189+
JSON_OUTPUT=$(node -e "console.log(JSON.stringify({
190+
wasm: process.env.WASM,
191+
hash: process.env.HASH,
192+
relPath: (process.env.REL_PATH || undefined),
193+
package: (process.env.PACKAGE || undefined),
194+
make: (process.env.MAKE || undefined)
195+
}))")
196+
echo "WASM_OUTPUT='$JSON_OUTPUT'" >> $GITHUB_ENV
197+
env:
198+
REL_PATH: ${{ inputs.relative_path }}
199+
PACKAGE: ${{ inputs.package }}
200+
MAKE: ${{ inputs.make_target }}
201+
HASH: ${{ env.WASM_HASH }}
202+
WASM: ${{ env.WASM_FILE_NAME }}
203+
204+
- name: Output WASM ${{ env.WASM_OUTPUT }}
205+
run: echo ${{ env.WASM_OUTPUT }}
206+
207+
- name: Send release info
208+
run: |
209+
JSON_OBJECT=$(node -e "console.log(JSON.stringify({
210+
repository: process.env.REPOSITORY,
211+
commitHash: process.env.COMMIT_HASH,
212+
jobId: process.env.JOB_ID,
213+
runId: process.env.RUN_ID,
214+
contractHash: process.env.CONTRACT_HASH,
215+
relativePath: process.env.RELATIVE_PATH || undefined,
216+
packageName: process.env.PACKAGE_NAME || undefined,
217+
makeTarget: process.env.MAKE_TARGET || undefined
218+
}))")
219+
220+
echo "JSON to send: $JSON_OBJECT"
221+
222+
curl -X POST "https://api.stellar.expert/explorer/public/contract-validation/match" \
223+
-H "Content-Type: application/json" \
224+
-d "$JSON_OBJECT" \
225+
--max-time 15
226+
env:
227+
REPOSITORY: ${{ github.server_url }}/${{ github.repository }}
228+
COMMIT_HASH: ${{ github.sha }}
229+
JOB_ID: ${{ github.job }}
230+
RUN_ID: ${{ github.run_id }}
231+
CONTRACT_HASH: ${{ env.WASM_HASH }}
232+
RELATIVE_PATH: ${{ inputs.relative_path }}
233+
PACKAGE_NAME: ${{ inputs.package }}
234+
MAKE_TARGET: ${{ inputs.make_target }}
235+
236+
- name: Attest
237+
uses: actions/attest-build-provenance@v1
238+
with:
239+
subject-path: "${{ env.OUTPUT }}/${{ env.WASM_FILE_NAME }}"
240+
subject-name: "${{ env.WASM_FILE_NAME }}"

CLAUDE.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Scaffold Stellar is a developer toolkit for building dApps and smart contracts on the Stellar blockchain. It provides two Stellar CLI plugins:
8+
- **stellar-scaffold** (`crates/stellar-scaffold-cli`) - Project scaffolding, building, and frontend generation
9+
- **stellar-registry** (`crates/stellar-registry-cli`) - Publishing and deploying contracts via an on-chain registry
10+
11+
## Common Commands
12+
13+
```bash
14+
# Setup development environment (installs stellar-cli v23.3.0)
15+
just setup
16+
17+
# Build contracts and optimize registry wasm
18+
just build
19+
20+
# Run unit tests (builds first)
21+
just test
22+
23+
# Run integration tests (requires local Stellar RPC via Docker)
24+
just test-integration
25+
26+
# Format check
27+
cargo fmt --all -- --check
28+
29+
# Lint (requires contracts built first)
30+
cargo clippy --all
31+
32+
# Run CLI directly during development
33+
just scaffold <args> # runs stellar-scaffold
34+
just registry <args> # runs stellar-registry
35+
```
36+
37+
## Architecture
38+
39+
### Crate Structure
40+
41+
| Crate | Purpose |
42+
|-------|---------|
43+
| `stellar-scaffold-cli` | Main CLI: init, build, generate, watch commands |
44+
| `stellar-registry-cli` | Registry CLI: publish, deploy, download, upgrade commands |
45+
| `stellar-build` | Contract building logic and dependency resolution |
46+
| `stellar-registry-build` | Registry interaction and contract deployment logic |
47+
| `stellar-registry` | Shared registry types and utilities |
48+
| `stellar-scaffold-macro` | Procedural macros |
49+
| `stellar-scaffold-test` | Test utilities and fixture contracts |
50+
51+
### Key Contracts
52+
53+
- `contracts/registry` - The on-chain Registry contract (manages wasm publication and contract deployment)
54+
- `contracts/test/*` - Test contracts for integration testing
55+
- `crates/stellar-scaffold-test/fixtures/` - Fixture contracts for CLI testing
56+
57+
### CLI Command Flow
58+
59+
**stellar-scaffold commands:** init → build → generate → watch
60+
- `init` - Scaffolds new project from template (uses degit to fetch from scaffold-stellar-frontend repo)
61+
- `build` - Builds contracts and generates TypeScript clients based on `environments.toml`
62+
- `generate contract` - Adds new contract to existing project
63+
- `watch` - Monitors and rebuilds on changes
64+
65+
**stellar-registry commands:** publish → deploy → create-alias
66+
- `publish` - Uploads wasm to registry with semantic versioning
67+
- `deploy` - Instantiates a published wasm as a named contract
68+
- `create-alias` - Creates local stellar contract alias from registry
69+
70+
## Testing
71+
72+
- Unit tests run without external dependencies: `cargo t`
73+
- Integration tests require local Stellar RPC running via Docker (stellar/quickstart image)
74+
- Feature flag `integration-tests` enables RPC-dependent tests
75+
- Test fixtures in `crates/stellar-scaffold-test/fixtures/`
76+
77+
## Build Profile
78+
79+
Contracts use a custom `[profile.contracts]` with aggressive optimization:
80+
- `opt-level = "z"` (size optimization)
81+
- `lto = true`
82+
- `strip = "symbols"`
83+
- Build with: `stellar contract build --profile contracts`

0 commit comments

Comments
 (0)