Skip to content
Open
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
93 changes: 93 additions & 0 deletions .github/workflows/docs-link-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Documentation Link Check

on:
pull_request:
paths:
- '**.md'
push:
branches:
- main
- staging
paths:
- '**.md'
workflow_dispatch:

permissions:
contents: read

jobs:
check-internal-links:
name: Internal / Repository-Relative Links
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install root dependencies
run: npm install --no-audit --no-fund

- name: Run internal link check (strict mode)
env:
NODE_OPTIONS: --max_old_space_size=4096
run: |
set -o pipefail
npm run check:links 2>&1 | tee link-check-output.log
continue-on-error: false

- name: Upload link check output on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: link-check-output
path: link-check-output.log
retention-days: 7

check-external-links:
name: External HTTP/HTTPS Links (Warn Only)
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install root dependencies
run: npm install --no-audit --no-fund

- name: Install lychee (external link checker)
uses: lycheeverse/lychee-action@v1.10.0
with:
args: >-
--verbose
--no-progress
--accept 200,206,403,429
--exclude-mail
--exclude-file .lycheeignore
--max-concurrency 10
--timeout 20
--retry-wait-time 10
--schema https
--schema http
'./**/*.md'
fail: false
output: external-link-report.md
format: markdown
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload external link report
if: always()
uses: actions/upload-artifact@v4
with:
name: external-link-report
path: external-link-report.md
retention-days: 14
12 changes: 12 additions & 0 deletions .lycheeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
^(?!https?://)
^localhost
^127\.0\.0\.1
^0\.0\.0\.0
example\.com
example\.org
example\.net
your-domain\.com
your-production-domain\.com
staging\.your-domain\.com
YOUR_CONTRACT_ID
<.*>
38 changes: 38 additions & 0 deletions .markdown-link-check.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"ignorePatterns": [
{
"pattern": "^https?://"
},
{
"pattern": "^http?://"
},
{
"pattern": "^#"
},
{
"pattern": "^mailto:"
},
{
"pattern": "^ftp://"
}
],
"replacementPatterns": [
{
"pattern": "^/",
"replacement": "./"
}
],
"httpHeaders": [
{
"urls": ["https://github.com", "https://www.github.com"],
"headers": {
"User-Agent": "markdown-link-check/1.0"
}
}
],
"timeout": "20s",
"retryOn429": true,
"retryCount": 2,
"fallbackRetryDelay": "10s",
"aliveStatusCodes": [200, 206, 403, 429]
}
21 changes: 21 additions & 0 deletions CONTRIBUTOR_DEVELOPMENT_WORKFLOW_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,27 @@ Also ensure formatting is clean before PR:
cargo fmt --all
```

### 4.1.1 Documentation Link Check (all markdown files)

Before pushing documentation changes, verify that internal repository-relative links are not broken. External HTTP/HTTPS links are checked separately in CI and treated as warnings only (temporary external downtime will not fail the pipeline).

From the **repository root**:

```bash
# Install root dev dependencies (if you haven't already)
npm install

# Check internal links strictly (fails on broken internal/relative links)
npm run check:links

# Quiet mode (suppresses per-file progress, shows only failures)
npm run check:links:quiet
```

The `check:links` script scans every `.md` file in the repository. Broken **internal** or **repository-relative** links (e.g. `[guide](./docs/README.md)`, `[adr](/docs/adr/0001-...)`) will cause the command to exit non-zero and must be fixed. External `http://` / `https://` links are excluded from strict checks to avoid CI fragility from transient outages; they are audited separately on a best-effort basis via the `check-external-links` CI job.

See [`.github/workflows/docs-link-check.yml`](.github/workflows/docs-link-check.yml) for the full CI pipeline and [`.markdown-link-check.json`](.markdown-link-check.json) for the checker configuration.

### 4.2 Listener (TypeScript)

From repo root:
Expand Down
110 changes: 110 additions & 0 deletions DEPLOYMENT_PLAYBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,113 @@ stellar contract event \
--start-ledger <ledger-number-of-deployment>
```
Verify that the output contains the correct event topics (e.g. `AutoshareCreated`) and matching data values.

---

## 5. Deployment Manifest (Auto-Generated)

Every deployment run via `make deploy`, `make deploy-testnet`, or `make deploy-mainnet` automatically writes a small JSON manifest file to `contract/contracts/hello-world/deployment-manifest.json` (override path with `MANIFEST_FILE=...`).

This manifest contains only **non-secret** metadata and is safe to commit to version control, share with team members, or attach to release notes. It is consumed by the listener, dashboard, and CI pipelines to resolve the active contract address and network without manual copy-paste.

### 5.1 Security — What Is Explicitly Excluded

The manifest generation code in the `deploy` Makefile target **nevers records** any of the following:

- `DEPLOYER_SECRET_KEY` or any private / secret key
- Any seed phrase, mnemonic, or signing material
- Any environment variable whose name contains `SECRET`, `KEY`, `TOKEN`, or `PASS`
- The CLI `--source` account identity or its derived values

If your workflow adds new secret variables, they must not be printed, echoed, or interpolated into the manifest output. The `_securityNote` field inside each manifest serves as an in-band reminder.

### 5.2 Manifest Schema (v1)

```typescript
interface DeploymentManifest {
manifestVersion: 1; // Schema version, bumped on format changes
contract: {
id: string; // Contract address (C + 55 base32 chars)
name: 'AutoShare' | 'TaskBounty'; // Human-readable contract name
wasmPath: string; // Relative path to the deployed .wasm binary
wasmSha256?: string; // SHA-256 of the deployed WASM (optional, platform support dependent)
};
network: {
name: 'testnet' | 'mainnet' | string; // NETWORK_NAME passed to make deploy
rpcUrl: string; // RPC_URL used for the deployment
passphrase: string; // NETWORK_PASSPHRASE for the ledger
};
deployedAt: string; // ISO-8601 UTC timestamp of the deploy
deployedBy: string; // Toolchain identifier ("stellar-cli")
_securityNote: string; // In-band security reminder (safe to ignore programmatically)
}
```

### 5.3 Sample Output

```json
{
"_securityNote": "This manifest intentionally excludes all private keys, seed phrases, and secret environment variables. Never commit DEPLOYER_SECRET_KEY or any signing material.",
"contract": {
"id": "CAS3...56CHAR...",
"name": "AutoShare",
"wasmPath": "target/wasm32v1-none/release/hello_world.wasm",
"wasmSha256": "a1b2c3d4..."
},
"deployedAt": "2025-07-01T12:34:56Z",
"deployedBy": "stellar-cli",
"manifestVersion": 1,
"network": {
"name": "testnet",
"passphrase": "Test SDF Network ; September 2015",
"rpcUrl": "https://soroban-testnet.stellar.org"
}
}
```

### 5.4 Consuming the Manifest

From shell (for CI scripts or quick lookups):

```bash
cd contract/contracts/hello-world
jq -r '.contract.id' deployment-manifest.json
jq -r '.network.name' deployment-manifest.json
jq -r '.deployedAt' deployment-manifest.json
```

From Node.js / TypeScript listener or dashboard config:

```typescript
import manifest from './contract/contracts/hello-world/deployment-manifest.json';

if (manifest.manifestVersion !== 1) {
throw new Error(`Unsupported manifest version: ${manifest.manifestVersion}`);
}

const contractId = manifest.contract.id;
const network = manifest.network.name;
```

### 5.5 TaskBounty Contract (Manual Step)

The TaskBounty makefile does not yet auto-generate a manifest. After deploying TaskBounty, copy the sample template below and save it next to the TaskBounty `Cargo.toml` (path: `Documents/Task Bounty/deployment-manifest.json`), filling in the values from the deployment output:

```json
{
"manifestVersion": 1,
"contract": {
"id": "<TASKBOUNTY_CONTRACT_ID>",
"name": "TaskBounty",
"wasmPath": "target/wasm32-unknown-unknown/release/task_bounty.wasm"
},
"network": {
"name": "testnet",
"rpcUrl": "https://soroban-testnet.stellar.org",
"passphrase": "Test SDF Network ; September 2015"
},
"deployedAt": "2025-07-01T00:00:00Z",
"deployedBy": "stellar-cli-manual",
"_securityNote": "Do not commit DEPLOYER_SECRET_KEY, seed phrases, or other signing material."
}
```
65 changes: 61 additions & 4 deletions contract/contracts/hello-world/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ test: build
build:
stellar contract build
@ls -l target/wasm32v1-none/release/*.wasm
@ls -l target/wasm32-unknown-unknown/release/*.wasm 2>/dev/null || true

fmt:
cargo fmt --all
Expand All @@ -31,6 +32,12 @@ endif
# Default contract wasm
CONTRACT_WASM ?= target/wasm32v1-none/release/hello_world.wasm

# Path to where the deployment manifest will be written (safe to commit).
MANIFEST_FILE ?= deployment-manifest.json

# Manifest schema version. Increment when manifest format changes.
MANIFEST_SCHEMA_VERSION := 1

deploy-testnet:
$(MAKE) deploy RPC_URL=https://soroban-testnet.stellar.org NETWORK_PASSPHRASE="Test SDF Network ; September 2015" NETWORK_NAME=testnet

Expand All @@ -44,20 +51,70 @@ deploy: check-deploy-config build
--wasm $(CONTRACT_WASM) \
--secret-key "$$DEPLOYER_SECRET_KEY" \
--rpc-url "$$RPC_URL" \
HiNETWORK_PASSHRASE" "$$NETWORK_PASSHRASE" 2>&1); \
--network-passphrase "$$NETWORK_PASSPHRASE" 2>&1); \
echo "Deployment output:"; \
echo "$$deploy_output"; \
contract_id=$$(echo "$$deploy_output" | grep -o 'C[a-z0-9]{55}' | head -n1); \
contract_id=$$(echo "$$deploy_output" | grep -oE 'C[A-Za-z0-9]{55}' | head -n1); \
if [ -z "$$contract_id" ]; then \
echo "Error: Could not extract contract ID from deployment output."; \
exit 1; \
fi; \
echo "Deployed contract ID: $$contract_id"
echo "Deployed contract ID: $$contract_id"; \
\
wasm_sha256=""; \
if command -v sha256sum >/dev/null 2>&1; then \
wasm_sha256=$$(sha256sum "$(CONTRACT_WASM)" | awk '{print $$1}'); \
elif command -v shasum >/dev/null 2>&1; then \
wasm_sha256=$$(shasum -a 256 "$(CONTRACT_WASM)" | awk '{print $$1}'); \
fi; \
\
deployed_at_iso=$$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ"); \
\
echo "Writing deployment manifest to $(MANIFEST_FILE)"; \
tmp_manifest=$$(mktemp); \
{ \
printf '{\n'; \
printf ' "manifestVersion": %s,\n' "$(MANIFEST_SCHEMA_VERSION)"; \
printf ' "contract": {\n'; \
printf ' "id": "%s",\n' "$$contract_id"; \
printf ' "wasmPath": "%s",\n' "$(CONTRACT_WASM)"; \
if [ -n "$$wasm_sha256" ]; then \
printf ' "wasmSha256": "%s",\n' "$$wasm_sha256"; \
fi; \
printf ' "name": "AutoShare"\n'; \
printf ' },\n'; \
printf ' "network": {\n'; \
printf ' "name": "%s",\n' "$(NETWORK_NAME)"; \
printf ' "rpcUrl": "%s",\n' "$(RPC_URL)"; \
printf ' "passphrase": "%s"\n' "$(NETWORK_PASSPHRASE)"; \
printf ' },\n'; \
printf ' "deployedAt": "%s",\n' "$$deployed_at_iso"; \
printf ' "deployedBy": "stellar-cli",\n'; \
printf ' "_securityNote": "This manifest intentionally excludes all private keys, seed phrases, and secret environment variables. Never commit DEPLOYER_SECRET_KEY or any signing material."\n'; \
printf '}\n'; \
} > "$$tmp_manifest"; \
\
if command -v python3 >/dev/null 2>&1; then \
python3 -c "import json,sys; json.dump(json.load(open('$$tmp_manifest')),sys.stdout,indent=2,sort_keys=True); print()" > "$(MANIFEST_FILE)"; \
rm -f "$$tmp_manifest"; \
elif command -v node >/dev/null 2>&1; then \
node -e "const fs=require('fs'); const p='$$tmp_manifest'; const d=JSON.parse(fs.readFileSync(p,'utf8')); fs.writeFileSync('$(MANIFEST_FILE)', JSON.stringify(d,null,2)+'\n')"; \
rm -f "$$tmp_manifest"; \
else \
mv "$$tmp_manifest" "$(MANIFEST_FILE)"; \
fi; \
\
echo ""; \
echo "Manifest written successfully: $(MANIFEST_FILE)"; \
echo " Contract ID : $$contract_id"; \
echo " Network : $(NETWORK_NAME)"; \
echo " Deployed at : $$deployed_at_iso"; \
[ -n "$$wasm_sha256" ] && echo " WASM SHA256 : $$wasm_sha256" || true

# Validate required deployment configuration
check-deploy-config:
@if [ -z "$$RPC_URL" ]; then echo "Error: RPC_URL is not set. Set it via environment var or .env file, or use deploy-testnet/deploy-mainnet."; exit 1; fi
@if [ -z "$$NETWORK_PASSPHRASE" ]; then echo "Error: NETWORK_PASSHRASE is not set. Use deploy-testnet/deploy-mainnet or set it explicitly."; exit 1; fi
@if [ -z "$$NETWORK_PASSPHRASE" ]; then echo "Error: NETWORK_PASSPHRASE is not set. Use deploy-testnet/deploy-mainnet or set it explicitly."; exit 1; fi
@if [ -z "$$DEPLOYER_SECRET_KEY" ]; then echo "Error: DEPLOYER_SECRET_KEY is not set. Set it in your environment or .env file."; exit 1; fi
@test -f $(CONTRACT_WASM) || (echo "Error: Contract WASM not found at $(CONTRACT_WASM). Run 'make build' first."; exit 1)

Expand Down
Loading
Loading