diff --git a/deployments/deployment-manifest.example.json b/deployments/deployment-manifest.example.json new file mode 100644 index 00000000..a0d7f810 --- /dev/null +++ b/deployments/deployment-manifest.example.json @@ -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" + } +} diff --git a/docs/CONTRACT_DEPLOYMENT_MANIFEST.md b/docs/CONTRACT_DEPLOYMENT_MANIFEST.md new file mode 100644 index 00000000..55f519f6 --- /dev/null +++ b/docs/CONTRACT_DEPLOYMENT_MANIFEST.md @@ -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": "", + "name": "notify-chain-events", + "wasm_hash": "" + }, + "network": { + "name": "testnet", + "rpc_url": "https://soroban-testnet.stellar.org", + "passphrase": "Test SDF Network ; September 2015" + }, + "deployment": { + "deployer_public_key": "", + "timestamp": "2026-08-29T12:00:00.000Z", + "git_commit": "" + } +} +``` + +--- + +## 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 +``` diff --git a/scripts/generate-deployment-manifest.js b/scripts/generate-deployment-manifest.js new file mode 100755 index 00000000..c9b4d947 --- /dev/null +++ b/scripts/generate-deployment-manifest.js @@ -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 };