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
18 changes: 18 additions & 0 deletions deployments/deployment-manifest.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"schema_version": "1.0.0",
"contract": {
"id": "CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA64P7TV5A4W",
"name": "notify-chain-events",
"wasm_hash": "d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5"
},
"network": {
"name": "testnet",
"rpc_url": "https://soroban-testnet.stellar.org",
"passphrase": "Test SDF Network ; September 2015"
},
"deployment": {
"deployer_public_key": "GBRPYHIL2CI3WHGSUJGY6O7SROQOMJG7QBCACN4QPKUOQNXJDGONXHPA",
"timestamp": "2026-08-29T12:00:00.000Z",
"git_commit": "1bc97f1e6727ab42f3e2b0d8825d13701ffde891"
}
}
51 changes: 51 additions & 0 deletions docs/CONTRACT_DEPLOYMENT_MANIFEST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 📋 Contract Deployment Artifact Manifest Specification

This document defines the deployment manifest schema for NotifyChain's Soroban smart contracts across local, testnet, and production environments (Issue #716).

---

## 1. Motivation & Purpose

To ensure reproducible testing and seamless frontend/listener synchronization, contract deployments produce a deterministic JSON manifest (`deployments/deployment-manifest.json`) recording contract IDs, bytecode hashes, network RPC endpoints, and git commits.

---

## 2. Manifest Schema

```json
{
"schema_version": "1.0.0",
"contract": {
"id": "<SOROBAN_CONTRACT_ID_C...>",
"name": "notify-chain-events",
"wasm_hash": "<SHA256_HEX_WASM_HASH>"
},
"network": {
"name": "testnet",
"rpc_url": "https://soroban-testnet.stellar.org",
"passphrase": "Test SDF Network ; September 2015"
},
"deployment": {
"deployer_public_key": "<STELLAR_PUBLIC_KEY_G...>",
"timestamp": "2026-08-29T12:00:00.000Z",
"git_commit": "<GIT_COMMIT_SHA>"
}
}
```

---

## 3. Security Invariants

* **No Secret Keys**: Manifest generation strictly forbids and rejects Stellar secret keys (`S...`). Only public identifiers (`C...`, `G...`) and hashes are included.
* **Non-destructive Overwrites**: Manifests are committed per-environment to allow automated integration testing without manual parameter copying.

---

## 4. Usage

Generate deployment manifest from environment variables:

```bash
node scripts/generate-deployment-manifest.js
```
76 changes: 76 additions & 0 deletions scripts/generate-deployment-manifest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env node
/**
* Contract Deployment Artifact Manifest Generator (Issue #716)
*
* Captures essential contract deployment metadata into a deterministic JSON manifest.
* Guarantees zero secret keys or sensitive credentials are ever persisted.
*/

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

function getGitCommit() {
try {
return execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
} catch {
return 'unknown';
}
}

function generateDeploymentManifest(options = {}) {
const contractId = options.contractId || process.env.CONTRACT_ID || 'CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA64P7TV5A4W';
const network = options.network || process.env.STELLAR_NETWORK || 'testnet';
const rpcUrl = options.rpcUrl || process.env.STELLAR_RPC_URL || 'https://soroban-testnet.stellar.org';
const networkPassphrase =
options.networkPassphrase ||
process.env.STELLAR_NETWORK_PASSPHRASE ||
'Test SDF Network ; September 2015';
const deployer = options.deployer || process.env.STELLAR_ACCOUNT_ID || 'GBRPYHIL2CI3WHGSUJGY6O7SROQOMJG7QBCACN4QPKUOQNXJDGONXHPA';
const wasmHash = options.wasmHash || process.env.WASM_HASH || 'd4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5';

// Security Invariant: Detect and throw if any secret keys are inadvertently passed
const allValues = [contractId, network, rpcUrl, networkPassphrase, deployer, wasmHash].join(' ');
if (/\bS[A-Z2-7]{55}\b/.test(allValues)) {
throw new Error('SECURITY VIOLATION: Secret key detected in deployment manifest inputs!');
}

const manifest = {
schema_version: '1.0.0',
contract: {
id: contractId,
name: 'notify-chain-events',
wasm_hash: wasmHash,
},
network: {
name: network,
rpc_url: rpcUrl,
passphrase: networkPassphrase,
},
deployment: {
deployer_public_key: deployer,
timestamp: new Date().toISOString(),
git_commit: getGitCommit(),
},
};

return manifest;
}

function main() {
const outputPath = process.argv[2] || path.join(__dirname, '../deployments/deployment-manifest.json');
const dir = path.dirname(outputPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}

const manifest = generateDeploymentManifest();
fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
console.log(`✅ Deployment manifest generated successfully at: ${outputPath}`);
}

if (require.main === module) {
main();
}

module.exports = { generateDeploymentManifest };
Loading