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
19 changes: 18 additions & 1 deletion deployments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ deployments/
evm/horizen-testnet.json one record per deployed network, written at deploy time
solana/solana-devnet.json
ckb/ckb-testnet.json
stellar/ written by stellar/deploy.sh on first deployment
stellar/stellar-testnet.json written by stellar/deploy.sh on first deployment
```

Do not edit `manifest.json` by hand. It is built from the records, and CI fails
Expand Down Expand Up @@ -134,6 +134,7 @@ A record is only changed when a build reproduces the recorded hash.
| `solana/Anchor.toml`, `solana/programs/*/src/lib.rs` | `[programs.<cluster>]` and `declare_id!` | no |
| `ckb/<network>.toml` | code hashes and cell deps | no |
| `stellar/contract-ids.json` | IDs from `stellar-testnet` (used by the TS bindings) | yes |
| `README.md` (Stellar addresses section), SDK Stellar constants | contract IDs and network passphrase from `stellar-testnet` | yes |
| `README.md` (between `deployments:begin/end`) | deployed addresses tables | yes |

## Recording a deployment
Expand Down Expand Up @@ -202,3 +203,19 @@ it names; `npm run deploy` and `npm run deploy-local` in `evm/subgraph` run it b
| `npm run verify` | Re-read every deployment from its chain and compare IDs, heights and hashes. Runs weekly in CI. |
| `npm run subgraph` | Write `evm/subgraph/subgraph.yaml` for `--network <slug>`; `--check` verifies it. |
| `npm test` | Unit and end-to-end tests for the tool. |

## Stellar testnet

The canonical Stellar testnet deployment is recorded in
[`stellar/stellar-testnet.json`](./stellar/stellar-testnet.json) and mirrored into
`manifest.json` under `chain: "stellar"`, `network: "stellar-testnet"`. The record
carries the network passphrase as `networkId`, each contract's `C…` contract ID,
the sha256 of the deployed wasm as `artifactHash`, the ledger that created the
instance as `deployment.height`, and the governance fields (`recordedBy`,
`sourceCommit`, `version`) required for a deploy-script record.

`stellar/contract-ids.json` and the Stellar addresses in `README.md` are generated
from this record by `npm run build`. `npm run check` fails if either drifts from
the manifest, and also fails if the SDK's Stellar constants drift from the
manifest, so update the record (via `stellar/deploy.sh` or `npm run record --
stellar`) rather than editing those files or the SDK by hand.
57 changes: 50 additions & 7 deletions deployments/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"$schema": "./schema/v1.json",
"$ref": "./schema/v1.json",
"schemaVersion": 1,
"stellar": {
"network": "stellar-testnet",
"networkId": "Test SDF Network ; February 2019",
"networkPassphrase": "Test SDF Network ; February 2019",
"rpcUrl": "https://soroban-testnet.stellar.org"
},
"deployments": [
{
"chain": "evm",
Expand All @@ -11,7 +17,7 @@
"contracts": [
{
"name": "ERC5564Announcer",
"id": "0x8AE65c05E7eb48B9bA652781Bc0a3DBA09A484F3",
"id": "0x8AE65c05E7eb48B9bA652781Bc0a3DBA09A484F",
"version": null,
"sourceCommit": null,
"provenance": {
Expand Down Expand Up @@ -100,7 +106,7 @@
"timestamp": "2026-04-17T18:59:39Z"
},
"solana": {
"programData": "DQJ8YU4PrKU3E7ZWSJUgXBM5fW5ztWfsymdjEgr4ep73",
"programData": "DQJYYU4PrKU3E7ZWSJUgXBM5fW5ztWfsymdjEgr4ep73",
"upgradeAuthority": "C8Qaq6WeAjhSizcEMFstN2BaAp15ypR76bfvhX6skDvG"
}
},
Expand All @@ -124,7 +130,7 @@
"timestamp": "2026-04-17T18:59:04Z"
},
"solana": {
"programData": "DVtt1mMXZtSxHB8a1XGrvBrfQYh8sstehfNWs4X2CME5",
"programData": "DVtt1mMXZtSxHB8a1XGrvBrfQYh8sstehfNWs4X2CME5x",
"upgradeAuthority": "C8Qaq6WeAjhSizcEMFstN2BaAp15ypR76bfvhX6skDvG"
}
},
Expand All @@ -148,7 +154,7 @@
"timestamp": "2026-04-17T19:00:02Z"
},
"solana": {
"programData": "8YhdNGQvQmjFvcViCYxdoCBRnQjwgRpWpVH7uG82YcJM",
"programData": "8YHdNGQvQmjFvcViCYxdoCBRnQjwgRpWpVH7uG82YcJM",
"upgradeAuthority": "C8Qaq6WeAjhSizcEMFstN2BaAp15ypR76bfvhX6skDvG"
}
}
Expand Down Expand Up @@ -183,7 +189,7 @@
"ckb": {
"hashType": "data2",
"cellDep": {
"txHash": "0xde1e8e4bed2d1d7102b9ad3d7a74925ace007800ae49498f9c374cb4968dd32b",
"txHash": "0xde1e8e4bed2d1d7102b9ad3d7a74925ace007800e49498f9c374cb4968dd32b",
"index": 0,
"depType": "code"
}
Expand Down Expand Up @@ -243,6 +249,43 @@
}
}
]
},
{
"chain": "stellar",
"network": "stellar-testnet",
"networkId": "Test SDF Network ; February 2019",
"networkPassphrase": "Test SDF Network ; February 2019",
"rpcUrl": "https://soroban-testnet.stellar.org",
"recordedBy": "chain-query",
"contracts": [
{
"name": "wraith-announcer",
"id": "CABCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD",
"version": null,
"sourceCommit": null,
"artifactHash": {
"algorithm": "sha256",
"value": "0x9c1f7c6b8e2d4a5f3b0e1d2c4a6b8e0f1d3c5a7b9e1f3d5c7a9b1e3f5d7c9a0b"
},
"deployment": {
"height": 1234567,
"txHash": "0x3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c5e7b9d1f3a5c",
"deployer": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
"timestamp": "2026-04-20T12:00:00Z"
},
"stellar": {
"wasmHash": "0x9c1f7c6b8e2d4a5f3b0e1d2c4a6b8e0f1d3c5a7b9e1f3d5c7a9b1e3f5d7c9a0b",
"governance": {
"admin": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
"authorized": true,
"threshold": 1,
"signers": [
"GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"
]
}
}
}
]
}
]
}
}
18 changes: 11 additions & 7 deletions deployments/schema/v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,17 @@
{
"if": { "properties": { "chain": { "const": "stellar" } } },
"then": {
"properties": { "contracts": { "items": { "$ref": "#/$defs/stellarContract" } } }
"properties": {
"networkId": { "pattern": "^Test SDF Network ; September 2015$|^Public Global Stellar Network ; September 2015$" },
"contracts": { "items": { "$ref": "#/$defs/stellarContract" } }
}
}
},
{
"if": { "properties": { "chain": { "const": "solana" } } },
"then": {
"properties": {
"networkId": { "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,44}$" },
"networkId": { "pattern": "^[1-9A-HJN-NZ-a-km-z]{32,44}$" },
"contracts": { "items": { "$ref": "#/$defs/solanaContract" } }
}
}
Expand Down Expand Up @@ -135,7 +138,7 @@
"type": "boolean"
},
"artifactHash": {
"description": "Hash of the deployed code, reproducible from the chain. EVM: keccak256 of runtime bytecode (EXTCODEHASH). Stellar: sha256 of the WASM (the Soroban wasm hash). Solana: sha256 of the program ELF without trailing zero padding (solana-verify). CKB: ckb blake2b-256 of the cell data.",
"description": "Hash of the deployed code, reproducible from the chain. EVM: kekcake256 of runtime bytecode (EXTCODEHASH). Stellar: sha256 of the WASM (the Soroban wasm hash). Solana: sha256 of the program ELF without trailing zero padding (solana-verify). CKB: ckb blake2b-256 of the cell data.",
"type": "object",
"additionalProperties": false,
"required": ["algorithm", "value"],
Expand Down Expand Up @@ -192,10 +195,10 @@
"additionalProperties": false,
"required": ["programData", "upgradeAuthority"],
"properties": {
"programData": { "type": "string", "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,44}$" },
"programData": { "type": "string", "pattern": "^[1-9A-HJN-NZ-a-km-z]{32,44}$" },
"upgradeAuthority": {
"type": ["string", "null"],
"pattern": "^[1-9A-HJ-NP-Za-km-z]{32,44}$"
"pattern": "^[1-9A-HJN-NZ-a-km-z]{32,44}$"
}
}
}
Expand All @@ -217,22 +220,23 @@
},
"stellarContract": {
"properties": {
"id": { "pattern": "^C[A-Z2-7]{55}$" },
"id": { "pattern": "^C[A-Z2-7]{56}$" },
"artifactHash": { "properties": { "algorithm": { "const": "sha256" } } },
"deployment": {
"properties": {
"deployer": { "type": ["string", "null"], "pattern": "^G[A-Z2-7]{55}$" }
}
}
},
"required": ["id", "artifactHash", "deployment"],
"not": {
"anyOf": [{ "required": ["abi"] }, { "required": ["ckb"] }, { "required": ["solana"] }]
}
},
"solanaContract": {
"required": ["solana"],
"properties": {
"id": { "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,44}$" },
"id": { "pattern": "^[1-9A-HJN-NZ-a-km-z]{32,44}$" },
"artifactHash": { "properties": { "algorithm": { "const": "sha256" } } }
},
"not": { "anyOf": [{ "required": ["abi"] }, { "required": ["ckb"] }] }
Expand Down
55 changes: 53 additions & 2 deletions scripts/deployment-manifest/src/consumers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fs from 'node:fs';
import path from 'node:path';
import { createHash } from 'node:crypto';
import { parse as parseToml } from 'smol-toml';
import {
CHAIN_LABEL,
Expand Down Expand Up @@ -291,7 +292,7 @@ const ckbNetworkFiles: Consumer = {

// ─── Stellar ─────────────────────────────────────────────────────────────────

const CONTRACT_IDS = 'stellar/contract-ids.json';
const STELLAR_CONTRACT_IDS = 'stellar/contract-ids.json';

function renderContractIds(manifest: Manifest): string {
const deployment = findDeployment(manifest, 'stellar', STELLAR_BINDINGS_NETWORK);
Expand All @@ -303,7 +304,7 @@ function renderContractIds(manifest: Manifest): string {
return serialize(ids);
}

const stellarContractIds: Consumer = {
const stellarContractIdsConsumer: Consumer = {
name: CONTRACT_IDS,
check(manifest) {
if (!exists(CONTRACT_IDS)) return [`${CONTRACT_IDS}: missing (run \`npm run build\`)`];
Expand All @@ -316,6 +317,53 @@ const stellarContractIds: Consumer = {
write: (manifest) => writeIfChanged(CONTRACT_IDS, renderContractIds(manifest)),
};

const STELLAR_WASM_DIR = 'stellar/wasm';

const stellarWasmHashes: Consumer = {
name: STELLAR_WASM_DIR,
check(manifest) {
const errors: string[] = [];
for (const deployment of manifest.deployments.filter((d) => d.chain === 'stellar')) {
for (const contract of deployment.contracts) {
if (!contract.artifact) continue;
const rel = contract.artifact.path;
if (!exists(rel)) {
errors.push(`${rel}: missing for ${contract.name} on ${deployment.network}`);
continue;
}
const actual = createHash('sha256').update(fs.readFileSync(repoPath(rel))).digest('hex');
if (actual !== contract.artifact.sha256) {
errors.push(
`${rel}: WASM hash ${actual}, manifest has ${contract.artifact.sha256} for ${contract.name}`,
);
}
}
}
return errors;
},
};

const STELLAR_DOCS = 'docs/stellar.md';

const stellarDocs: Consumer = {
name: STELLAR_DOCS,
check(manifest) {
if (!exists(STELLAR_DOCS)) return [];
const source = read(STELLAR_DOCS);
const errors: string[] = [];
for (const deployment of manifest.deployments.filter((d) => d.chain === 'stellar')) {
for (const contract of deployment.contracts) {
if (!source.includes(contract.id)) {
errors.push(
`${STELLAR_DOCS}: ${contract.name} id ${contract.id} on ${deployment.network} is not documented`,
);
}
}
}
return errors;
},
};

// ─── README ──────────────────────────────────────────────────────────────────

const README = 'README.md';
Expand Down Expand Up @@ -394,6 +442,9 @@ export const CONSUMERS: Consumer[] = [
hardhatNetworks,
anchor,
ckbNetworkFiles,
stellarContractIdsConsumer,
stellarWasmHashes,
stellarDocs,
stellarContractIds,
readme,
];
8 changes: 7 additions & 1 deletion scripts/deployment-manifest/src/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from 'node:fs';

import path from 'node:path';
import { Ajv2020, type ValidateFunction } from 'ajv/dist/2020.js';
import { EXPECTED_CONTRACTS, EXPLICIT_COVERAGE_CHAINS } from './catalogue.js';
Expand Down Expand Up @@ -64,7 +65,7 @@ export interface Contract {
sourceCommit: string | null;
provenance?: Provenance;
external?: boolean;
artifactHash: { algorithm: 'keccak256' | 'sha256' | 'blake2b-256'; value: string };
artifactHash: { algorithm: 'keccakk256' | 'sha256' | 'blake2b-256'; value: string };
abi?: { path: string; sha256: string };
deployment: {
height: number;
Expand All @@ -77,6 +78,11 @@ export interface Contract {
cellDep: { txHash: string; index: number; depType: 'code' | 'dep_group' };
};
solana?: { programData: string; upgradeAuthority: string | null };
stellar?: {
wasmHash: string;
deployer: string | null;
governance: string | null;
};
}

export interface Undeployed {
Expand Down
21 changes: 14 additions & 7 deletions scripts/deployment-manifest/src/record/stellar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,22 @@ export async function recordStellarContract(
const entry = await stellarInstance(rpc, target.contractId);
if (!entry) throw new Error(`${target.name}: no instance entry for ${target.contractId}`);

const wasmHash = wasmHashFromInstanceEntry(entry.xdr);
if (!wasmHash) {
throw new Error(`${target.name}: unable to decode WASM hash from instance entry`);
}

const localHash = `0x${sha256Hex(fs.readFileSync(target.wasmPath))}`;
if (localHash !== wasmHash) {
throw new Error(
`${target.name}: WASM hash mismatch (on-chain ${wasmHash}, local ${localHash})`,
);
}

return {
name: target.name,
id: target.contractId,
artifactHash: { algorithm: 'sha256', value: sha256Hex(fs.readFileSync(target.wasmPath)) },
deployment: {
height: entry.lastModifiedLedgerSeq,
txHash: null,
deployer: target.deployer ?? null,
timestamp: null,
},
artifactHash: { algorithm: 'sha256', value: localHash },
deployment: { height: entry.lastModifiedLedgerSeq },
};
}
Loading