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
9 changes: 2 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
# Dependencies
/target
/Cargo.lock
node_modules/
wallet-client/node_modules/

# JS build intermediates
wallet_connect.bundle.js

# Environment
.env
Expand Down Expand Up @@ -48,5 +43,5 @@ tarpaulin-report.html
cobertura.xml
tarpaulin-ci/

# Test snapshots (generated locally, not committed)
/campaign/test_snapshots/
# Soroban test snapshots (build artifacts)
**/test_snapshots/
3 changes: 0 additions & 3 deletions .kilo/kilo.jsonc

This file was deleted.

44 changes: 6 additions & 38 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
## make fmt-tools - Format crates/tools only (tracked: issue #13)
## make lint - Lint contracts (excludes crates/tools — see issue #13)
## make lint-tools - Lint crates/tools only (tracked: issue #13)
## make lint-schema - Validate docs/audit-log.schema.json with ajv-cli (issue #41)
## make all-lint - Run lint + lint-tools + lint-schema (full workspace coverage)
## make all-lint - Run lint + lint-tools (full workspace coverage)

.PHONY: build build-wasm build-tools test fmt fmt-tools lint lint-tools lint-schema all-lint \
.PHONY: build build-wasm build-tools test fmt fmt-tools lint lint-tools all-lint \
clean optimize help setup deploy-testnet deploy-sandbox sandbox-start \
audit deny wire-test
audit deny

# Default target
build: build-wasm build-tools
Expand Down Expand Up @@ -73,27 +72,8 @@ lint-tools:
# Aggregate lint target: runs both contract and tools linters.
# Provides full workspace coverage while keeping the two scopes separable.
# See issue #13 for the tracked plan to unify under a single --workspace pass.
all-lint: lint lint-tools lint-schema
@echo "✅ All linting passed (contracts + tools + schema)"

# Validate docs/audit-log.schema.json using ajv-cli (issue #41).
# Checks that the schema itself parses correctly and that the embedded examples
# all validate against it. Requires ajv-cli:
# npm install -g ajv-cli (or: npx ajv-cli)
# ajv v8+ uses draft-07 by default; no extra flags needed.
lint-schema:
@echo "🔍 Validating docs/audit-log.schema.json..."
@if command -v ajv >/dev/null 2>&1; then \
ajv validate --spec=draft7 -s docs/audit-log.schema.json -d docs/audit-log.schema.json 2>/dev/null || true; \
ajv compile --spec=draft7 -s docs/audit-log.schema.json; \
elif command -v npx >/dev/null 2>&1; then \
npx --yes ajv-cli compile --spec=draft7 -s docs/audit-log.schema.json; \
else \
echo "⚠️ ajv-cli not found — skipping JSON Schema validation."; \
echo " Install with: npm install -g ajv-cli"; \
exit 0; \
fi
@echo "✅ Schema validation passed"
all-lint: lint lint-tools
@echo "✅ All linting passed (contracts + tools)"

# Clean build artifacts
clean:
Expand Down Expand Up @@ -144,17 +124,6 @@ deny:
cargo deny check
@echo "✅ License check passed"

# Wire-format snapshot validation — ensures the on-chain error-code
# representation has not drifted from the committed fixture.
# Regenerate the fixture with:
# cargo test -p milestonex-campaign update_wire_fixture # (future helper)
# Or just paste the output of `cargo test campaign_error_discriminants_are_unique`
# into campaign/test_snapshots/wire_code_fixture.txt.
wire-test:
@echo "🔌 Validating error-code wire-format snapshot..."
cargo test -p milestonex-campaign -- wire_code_table_matches_fixture
@echo "✅ Wire-format snapshot matches fixture"

# Optimize WASM binaries using wasm-opt (-Oz)
optimize: build
@echo "🔧 Optimizing WASM binaries with wasm-opt..."
Expand All @@ -178,8 +147,7 @@ help:
@echo " make fmt-tools - Format crates/tools only (tracked: issue #13)"
@echo " make lint - Lint contract crates (crates/tools excluded; see issue #13)"
@echo " make lint-tools - Lint crates/tools only (tracked: issue #13)"
@echo " make lint-schema - Validate docs/audit-log.schema.json with ajv-cli (issue #41)"
@echo " make all-lint - Run lint + lint-tools + lint-schema (full workspace coverage)"
@echo " make all-lint - Run lint + lint-tools (full workspace coverage)"
@echo " make clean - Clean build artifacts"
@echo " make sandbox-start - Start local Stellar sandbox (requires Docker)"
@echo " make deploy-sandbox - Deploy contract to local sandbox"
Expand Down
106 changes: 12 additions & 94 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ is tracked in [issue #37](https://github.com/MillestoneX/MilestoneX-Contracts/is
- `vault` — Show SecureVault status and security best practices.
- `toggle <testnet|mainnet>` — Switch the active network profile.

### Deployment

- `deploy [--dry-run] [--source <key>] [--fee <stroops>]` — Deploy the canonical campaign WASM to the configured network. Uses `SOROBAN_ADMIN_SECRET_KEY` (or `--source`), `SOROBAN_NETWORK`, and `SOROBAN_RPC_URL`/`SOROBAN_NETWORK_PASSPHRASE` overrides. Writes `deployments/<network>.json` and `.milestonex_contract_id`.

### Asset Issuing

- `asset config` — Show asset configuration.
Expand Down Expand Up @@ -114,11 +110,6 @@ milestonex-cli config
milestonex-cli network
milestonex-cli toggle testnet

# Deploy the canonical campaign contract to testnet
milestonex-cli deploy
milestonex-cli deploy --dry-run
milestonex-cli deploy --source my_key --fee 1000000

# Issue a custom asset and establish trustline
milestonex-cli asset generate
milestonex-cli asset trustline GABJ2... USDC
Expand All @@ -133,73 +124,6 @@ milestonex-cli response process '{"requestId":"req_123","xdr":"AAAA...","signer"

For the full command list, run `milestonex-cli` with no arguments.

## 🌐 Wallet Client (`wallet_connect.html`)

`wallet_connect.html` is a single-file browser application that provides a full
donation UX for any deployed `milestonex-campaign` contract instance. It is
generated by a small Webpack build in the `wallet-client/` directory and bundles
`@stellar/stellar-sdk` and `@stellar/freighter-api` so it has zero runtime
dependencies.

### Features

- **Freighter wallet lifecycle** — connect, authorize, and disconnect using the
Freighter browser extension.
- **Campaign ID query param** — share links like `wallet_connect.html?campaign=<CONTRACT_ID>`
to deep-link directly into a specific deployed campaign.
- **Campaign state display** — shows goal, total raised, donor count, donation
count, days remaining, progress bar, and all milestones (Locked / Unlocked /
Released) with a ↻ Refresh button.
- **Multi-asset donation form** — drop-down pre-populated from the campaign's
`accepted_assets`, supports native XLM and any Stellar asset (USDC, NGNT, etc.).
- **Soroban XDR signing** — builds an `invokeHostFunction` transaction via
`stellar-sdk`, simulates it to obtain the footprint, presents the assembled
XDR to Freighter for signing, then displays the signed XDR for review.
- **One-click submit** — submits the signed XDR to the Soroban RPC, polls for
confirmation, shows the transaction hash with an Explorer link, and
auto-refreshes the campaign state.
- **Testnet / mainnet** — automatically selects the correct Horizon and RPC
endpoints based on the network reported by Freighter.

### Build

```bash
cd wallet-client
npm install
npm run build # writes wallet_connect.html to the project root
```

For development with live reload:

```bash
npm run dev # starts webpack-dev-server at http://localhost:3000
```

### Usage

1. Open `wallet_connect.html` in a browser that has the
[Freighter](https://freighter.app) extension installed.
2. Paste a deployed campaign contract ID into the **Campaign** field and click
**Load** (or pass `?campaign=<ID>` in the URL).
3. Click **Connect Freighter** and approve the connection.
4. Enter an amount (in stroops — 1 XLM = 10 000 000 stroops), select an asset,
add an optional memo, and click **Sign & Donate**.
5. Approve the transaction in Freighter.
6. Review the signed XDR in the **Signed XDR** panel, then click
**Submit to Network**.
7. The transaction hash and a Stellar Explorer link appear once confirmed.

### Source layout

```
wallet-client/
├── package.json # npm dependencies + build scripts
├── webpack.config.js # bundles everything into a single HTML file
└── src/
├── index.js # all wallet/signing/campaign logic
└── template.html # HTML template (Webpack inlines the JS bundle)
```

## 🛠️ Development Setup

### Quick Start (New Contributors)
Expand Down Expand Up @@ -319,8 +243,9 @@ cargo test --workspace
> The commands below match `crates/tools/src/main.rs` and the canonical
> status table in
> [`docs/deployment.md`](docs/deployment.md#known-limitations--cli-status).
> `invoke` is currently a stub in the CLI binary — use `stellar contract invoke`
> natively. `account` is deprecated but still functional — it delegates to
> `deploy` and `invoke` are currently stubs in the CLI binary;
> use the native `stellar contract …` commands or `make deploy-testnet`
> instead. `account` is deprecated but still functional — it delegates to
> `keypair` commands with a deprecation warning. `config init`,
> `contract-id`, `build-donation-tx`, `submit-tx`, `verify-tx`,
> `prepare-wallet-signing`, and `complete-wallet-signing` shown in older
Expand Down Expand Up @@ -348,10 +273,6 @@ cargo run -p milestonex-tools -- keymanager vault-status
cargo run -p milestonex-tools -- keypair generate-master
cargo run -p milestonex-tools -- keypair fund GABJ2... 10

# Deploy the canonical campaign contract to testnet
cargo run -p milestonex-tools -- deploy
cargo run -p milestonex-tools -- deploy --dry-run

# Wallet signing + response
cargo run -p milestonex-tools -- signing build-donation GBJCHU... 1 5000000 XLM "Supporting education"
cargo run -p milestonex-tools -- response process '{"requestId":"req_123","xdr":"AAAA...","signer":"GBJCHU...","signedAt":1234567890}'
Expand Down Expand Up @@ -413,28 +334,25 @@ SOROBAN_ADMIN_KEY=GA7...

### Step 3: Deploy to Testnet

> The CLI `deploy` command wraps `stellar contract deploy` and supports
> `--dry-run`, `--source <key_name>`, and `--fee <stroops>`. You can also
> use the Makefile target (`make deploy-testnet`) or `scripts/deploy.sh`.
> The in-CLI `deploy` command is a stub today. Use the build-in Makefile
> target (or `scripts/deploy.sh`) which is wired into `stellar contract deploy`
> for real network output. Tracking: issue
> [#37](https://github.com/MillestoneX/MilestoneX-Contracts/issues/37).

```bash
# Deploy via the CLI (uses SOROBAN_ADMIN_SECRET_KEY from .env)
cargo run -p milestonex-tools -- deploy

# Or use a named key and dry-run first:
cargo run -p milestonex-tools -- deploy --source my_key --dry-run

# Or use the Makefile wrapper (uses scripts/deploy.sh + stellar-cli)
# Deploy via the Makefile wrapper (uses scripts/deploy.sh + stellar-cli)
make deploy-testnet
# Or invoke the deploy script directly:
bash scripts/deploy.sh testnet
```

Expected output:

```
ℹ️ Using optimized WASM: target/wasm32v1-none/release/milestonex_core.wasm
🚀 Deploying to testnet...
RPC: https://soroban-testnet.stellar.org:443
WASM: target/wasm32v1-none/release/milestonex_campaign.wasm
WASM: target/wasm32v1-none/release/milestonex_core.wasm
✅ Contract deployed!
📝 Contract ID: CB7...ABC
💾 Deployment record saved to deployments/testnet.json
Expand Down Expand Up @@ -505,7 +423,7 @@ stellar contract invoke \

- **"WASM file not found"**: Run `make build-wasm` to build the contracts first.
- **"Unknown command" or "coming soon"**: You ran an `milestonex-cli` command
that is still a stub (`invoke`). Run
that is still a stub (`deploy`, `invoke`). Run
`cargo run -p milestonex-tools` with no arguments to see which commands are
actually implemented, and follow
[`docs/deployment.md`](docs/deployment.md#known-limitations--cli-status).
Expand Down
4 changes: 0 additions & 4 deletions campaign/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ crate-type = ["cdylib", "rlib"]
soroban-sdk = { workspace = true }
common = { workspace = true }

[features]
default = []
diag = []

[dev-dependencies]
soroban-sdk = { version = "26.0.1", features = ["testutils"] }
# Inherit the workspace-wide ed25519-dalek = 2.2.0 exact pin (see
Expand Down
36 changes: 15 additions & 21 deletions campaign/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,16 @@ use soroban_sdk::{panic_with_error, Env};
/// - `Error::ContractFrozen` if contract is frozen (freeze invariant: all writes rejected)
/// - `Error::InvalidCampaignTransition` if campaign is already Ended or Cancelled
pub fn end_campaign(env: &Env) {
// Freeze check — reject all mutating operations while frozen.
// Must precede require_auth() so the freeze invariant short-circuits
// before any auth work is consumed.
if is_frozen(env) {
panic_with_error!(env, Error::ContractFrozen);
}

let mut campaign =
get_campaign(env).unwrap_or_else(|| panic_with_error!(env, Error::NotInitialized));

campaign.creator.require_auth();

// Freeze invariant: all write operations are rejected while frozen (see freeze()).
if is_frozen(env) {
panic_with_error!(env, Error::ContractFrozen);
}

validate_campaign_transition(env, &campaign.status, &CampaignStatus::Ended)
.unwrap_or_else(|e| panic_with_error!(env, e));

Expand All @@ -53,18 +51,16 @@ pub fn end_campaign(env: &Env) {
/// - `Error::ContractFrozen` if contract is frozen (freeze invariant: all writes rejected)
/// - `Error::InvalidCampaignTransition` if campaign is already Cancelled
pub fn cancel_campaign(env: &Env) {
// Freeze check — reject all mutating operations while frozen.
// Must precede require_auth() so the freeze invariant short-circuits
// before any auth work is consumed.
if is_frozen(env) {
panic_with_error!(env, Error::ContractFrozen);
}

let mut campaign =
get_campaign(env).unwrap_or_else(|| panic_with_error!(env, Error::NotInitialized));

campaign.creator.require_auth();

// Freeze invariant: all write operations are rejected while frozen (see freeze()).
if is_frozen(env) {
panic_with_error!(env, Error::ContractFrozen);
}

validate_campaign_transition(env, &campaign.status, &CampaignStatus::Cancelled)
.unwrap_or_else(|e| panic_with_error!(env, e));

Expand All @@ -91,18 +87,16 @@ pub fn cancel_campaign(env: &Env) {
/// - `Error::InvalidEndTime` if `new_end_time` is more than ten years out
/// - `Error::InvalidCampaignTransition` if campaign is not Active or GoalReached
pub fn extend_deadline(env: &Env, new_end_time: u64) {
// Freeze check — reject all mutating operations while frozen.
// Must precede require_auth() so the freeze invariant short-circuits
// before any auth work is consumed.
if is_frozen(env) {
panic_with_error!(env, Error::ContractFrozen);
}

let mut campaign =
get_campaign(env).unwrap_or_else(|| panic_with_error!(env, Error::NotInitialized));

campaign.creator.require_auth();

// Freeze invariant: all write operations are rejected while frozen (see freeze()).
if is_frozen(env) {
panic_with_error!(env, Error::ContractFrozen);
}

match campaign.status {
CampaignStatus::Active | CampaignStatus::GoalReached => {}
_ => panic_with_error!(env, Error::InvalidCampaignTransition),
Expand Down
12 changes: 0 additions & 12 deletions campaign/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@

use soroban_sdk::{Address, Env, String, Symbol};

#[cfg(feature = "diag")]
use crate::types::CampaignMetrics;

/// Emitted when a donation is received by the campaign.
pub fn donation_received(
env: &Env,
Expand Down Expand Up @@ -103,12 +100,3 @@ pub fn contract_unfrozen(env: &Env, admin: &Address, timestamp: u64) {
env.events()
.publish(("campaign", "contract_unfrozen"), (admin, timestamp));
}

/// Emit current diagnostic metrics as an event.
/// Only compiled when the `diag` feature is enabled.
#[cfg(feature = "diag")]
pub fn diagnostics_emit(env: &Env, metrics: &CampaignMetrics) {
let ledger = env.ledger().sequence();
env.events()
.publish(("campaign", "diagnostics"), (metrics, ledger));
}
Loading
Loading