On ISBE (Spain's blockchain services infrastructure), you can't deploy a custom contract on your own: you build and test it on a local Besu network, pick one of three deployment modes and, after a technical review, ISBE deploys it to PRE and PRO through its governance infrastructure. This guide walks through each step with code.
What you need and who does what
If you need the background first, start with what ISBE is. The short version for this tutorial: ISBE is a set of public-permissioned networks. Third-party contracts go on the use-case network (the "Main" or "UseCase" network in the documentation, listed as "Red Case" in its explorer links), which runs on Hyperledger Besu and is EVM-compatible. The Bare network does not accept third-party contracts or token issuance, according to ISBE's ECO1020 policy.
The division of roles differs from a public chain:
- Your engineering team writes the contract, its tests and the review package. It can start today, before your company completes onboarding.
- Your company registers on the portal with a legal-representative certificate and signs the membership agreement. We cover it in how to join ISBE: onboarding, requirements and fees.
- ISBE staff review the contract and deploy it to PRE and PRO, in all three modes. You register the contract on the portal and ISBE's team orchestrates the deployment.
To follow along you need Node.js 18 or later, npm 9 or later, Git, a running Docker and the jq utility, plus working knowledge of Solidity and the command line.
ISBE network parameters as of October 16, 2026
These apply to the use-case network in its two public environments, according to the parameters page on docs.redisbe.com. The documentation itself warns that they can change between releases and asks you to confirm the chain ID with ISBE's infrastructure team before configuring your tools.
| Parameter | Value |
|---|---|
| ISBE Mainnet (PRO) chain ID | 33073 |
| ISBE Testnet (PRE) chain ID | 11073 |
| Client | Hyperledger Besu 26.7.1 (recommended version) |
| Consensus | QBFT, one block every 2 seconds |
| Active EVM forks | Shanghai and Cancun; Prague (Testnet, Aug 10, 2026; Mainnet, Aug 18, 2026); Osaka (Testnet, Aug 27, 2026; Mainnet, Sep 2, 2026), all at 08:00 UTC |
| Gas per transaction | Up to 16,777,216 (EIP-7825, since Osaka) |
| Gas per block | 30,000,000 |
| Minimum gas price | 1 Gwei, with no gas cost per transaction |
| Maximum contract size | 24,576 bytes |
| RPC access | HTTP only, through the ISBE Client's Filtering Proxy (ETH, NET and QBFT APIs); no WebSocket |
Two practical consequences. If your script sets a gasLimit above 16,777,216, the network rejects the transaction; the documentation suggests 10,000,000 as a starting point for complex contracts. And with a 24,576-byte size cap, large logic has to be split across several contracts or facets.
Step 1: Spin up a local ISBE network with Besu
ISBE publishes the isbe-clients-template repository. It ships the official @red-isbe/isbe-contracts library, Hardhat, OpenZeppelin and a local network in the isbe-network-case folder that mirrors the use-case network with four Besu nodes running QBFT in Docker. Once you have PRE access, you only change the RPC URL. ISBE's GitHub organization also hosts isbe-besu-local-deployer, but the developer guide starts from the template.
git clone https://github.com/red-isbe/isbe-clients-template my-contract
cd my-contract
npm install
cp .env_sample .env
# Local ISBE network: 4 Besu nodes with QBFT in Docker
cd isbe-network-case
./startNetwork.sh
docker ps
# Compile and deploy the bundled example (mode 1)
cd ..
npx hardhat compile
npx hardhat run scripts/hashtimestamp/deployHashTimestamp.ts --network isbeWhat to know about this environment:
- The local RPC listens on
http://localhost:8545, on the bootnode. - ISBE's governance Diamond is already in the genesis block, at
0x00000000000000000000000000000000000015BE. - The
.env_samplefile includes Hardhat account #0, funded and with admin permissions on the local network's Diamond. Its keys are public: never use them outside your machine. - The local network uses chain ID 11073, the same as ISBE Testnet.
- The
HashTimestampexample is a mode 1 contract. If it deploys without errors, your environment works. - As of October 16, 2026,
npx hardhat compilefails on the template as published (error HH600): theProjectFacetcontract in thecontracts/project-contractsskeleton does not implement_implementedInterfaces(), which the library requires. Implement it when you adapt the skeleton, asHashTimestamp.soldoes, or move that folder andcontracts/testwrapper/projectaside until you need them.
This is how the template configures the compiler and the isbe network in hardhat.config.ts (excerpt, without the KMS signing option):
solidity: {
version: '0.8.28',
settings: { evmVersion: 'istanbul', optimizer: { enabled: true, runs: 200 } },
},
networks: {
isbe: {
url: process.env.ISBE_URL ?? process.env.LOCALHOST_URL ?? 'http://localhost:8545',
chainId: process.env.CHAIN_ID ? Number(process.env.CHAIN_ID) : 11073,
accounts: process.env.ACCOUNT_PRIVATE_KEY ? [process.env.ACCOUNT_PRIVATE_KEY] : [],
},
},Step 2: Choose a deployment mode and check the PAUSER_ROLE
This decision shapes the rest of the work. The documentation describes three modes:
| Aspect | Mode 1: Proxy Diamond + ISBE library | Mode 2: approved custom contract | Mode 3: direct deployment |
|---|---|---|---|
| Use it for | Most use cases; ISBE recommends it | Highly specific business logic | Testing or use cases without demanding regulation |
| ISBE library | Required | Optional | Not applicable |
| Review | Automatic conformity, using already certified components | Approval at level A, B or C | Not required |
| ISBE's PAUSER_ROLE | Yes, irrevocable | Required for approval; the owner could revoke it, but that breaches the terms of use | Not guaranteed |
| Pricing | Preferential (no published figure) | Standard | Standard |
| Who deploys | ISBE staff | ISBE staff | ISBE staff |
The PAUSER_ROLE, mode by mode:
- Mode 1. You don't add it. Your contract becomes a facet of ISBE's Diamond and inherits pausing and access control from the library. The documentation requires
DidDocumentDetailedInternalinstead of OpenZeppelin's AccessControl and Pausable implementations. - Mode 2. It is mandatory. The contract must expose
hasRole,pause()andunpause(), and grant the PAUSER_ROLE to ISBE's governance address. Without that, there is no approval. - Mode 3. ISBE does not guarantee it holds the role. In an emergency, it would ask the service provider to pause and, as a last resort, filter the contract address at the network client.
One nuance worth knowing: ISBE's ECO1020 v1.0 policy, dated December 19, 2025, is stricter than the documentation. It requires the Diamond pattern and approval for all custom code, rejects deployment without approval and states that it prevails over the technical guides on contract requirements. Before planning around mode 3, confirm it with ISBE.
Step 3: Write the contract in Solidity with OpenZeppelin
The example is a mode 2 custom contract: an evidence registry that stores salted hashes of documents. It uses OpenZeppelin 4.9, the version the template pins, and meets what the documentation asks of a custom contract:
- role-based access control and
whenNotPausedon state-changing functions; pause()andunpause(), with the PAUSER_ROLE for ISBE governance;- unstructured storage, with a slot derived with keccak256;
- an event for every sensitive action;
- no personal data on-chain: only hashes combined with a secret random value (a "salt"), as the ECO1020 policy requires.
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.28;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
/// @title EvidenceRegistry (mode 2: approved custom contract)
/// @notice Stores salted hashes of documents. Never personal data.
contract EvidenceRegistry is AccessControl, Pausable {
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
// Unstructured storage: dedicated slot derived with keccak256
bytes32 private constant STORAGE_POSITION = keccak256("acme.evidence.storage");
struct EvidenceStorage {
mapping(bytes32 => uint256) registeredAt;
}
event EvidenceRegistered(bytes32 indexed saltedHash, address indexed operator);
error DuplicateEvidence(bytes32 saltedHash);
constructor(address admin, address isbeGovernance) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(OPERATOR_ROLE, admin);
_grantRole(PAUSER_ROLE, isbeGovernance); // required in mode 2
}
function register(bytes32 saltedHash) external onlyRole(OPERATOR_ROLE) whenNotPaused {
EvidenceStorage storage $ = _evidence();
if ($.registeredAt[saltedHash] != 0) revert DuplicateEvidence(saltedHash);
$.registeredAt[saltedHash] = block.timestamp;
emit EvidenceRegistered(saltedHash, msg.sender);
}
function registeredAt(bytes32 saltedHash) external view returns (uint256) {
return _evidence().registeredAt[saltedHash];
}
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
function unpause() external onlyRole(PAUSER_ROLE) {
_unpause();
}
function _evidence() private pure returns (EvidenceStorage storage $) {
bytes32 position = STORAGE_POSITION;
assembly {
$.slot := position
}
}
}The contract compiles with solc 0.8.28, evmVersion "istanbul" and OpenZeppelin 4.9.6 (checked on October 16, 2026). Treat it as a starting point, not a contract ready for approval. The documentation's basic implementation guide also requires a SUPER_ADMIN_ROLE that no one can revoke externally, and this example does not include it. ECO1020, for its part, requires inheriting from ISBE's "Common" base contract and bans state variables in the standard layout in facets, which is how OpenZeppelin 4.9's AccessControl and Pausable store their state. Check both points with ISBE before you submit.
If your use case fits mode 1, the starting point is different: the template's contracts/project-contracts folder, with its four-file pattern (interface, internal logic, external layer and facet), and a script that registers the facet in the Diamond through three transactions: deploy, setConfiguration and deployUseCase.
Step 4: Test with Hardhat and deploy to your local network
Tests run on Hardhat's in-memory network, which provides several test accounts; deployment runs on your local ISBE network. This test checks ISBE's role, pausing, access control and duplicates:
import { expect } from "chai";
import { ethers } from "hardhat";
describe("EvidenceRegistry", () => {
async function deploy() {
const [admin, isbe, other] = await ethers.getSigners();
const c = await ethers.deployContract("EvidenceRegistry", [admin.address, isbe.address]);
return { c, admin, isbe, other };
}
it("grants PAUSER_ROLE to ISBE governance", async () => {
const { c, isbe } = await deploy();
expect(await c.hasRole(await c.PAUSER_ROLE(), isbe.address)).to.equal(true);
});
it("registers a salted hash only once", async () => {
const { c } = await deploy();
const h = ethers.id("invoice-0001|random-salt");
await expect(c.register(h)).to.emit(c, "EvidenceRegistered");
await expect(c.register(h)).to.be.revertedWithCustomError(c, "DuplicateEvidence");
});
it("blocks accounts without OPERATOR_ROLE", async () => {
const { c, other } = await deploy();
await expect(c.connect(other).register(ethers.id("x"))).to.be.reverted;
});
it("lets ISBE pause, and the pause blocks writes", async () => {
const { c, isbe } = await deploy();
await c.connect(isbe).pause();
await expect(c.register(ethers.id("y"))).to.be.revertedWith("Pausable: paused");
});
});This script deploys the contract to the local network. It uses the genesis Diamond as the governance address: according to the documentation, that contract checks a custom contract's PAUSER_ROLE by running the pause. For PRE and PRO, confirm the address with ISBE.
import { ethers } from "hardhat";
// Governance Diamond in the local genesis. For PRE and PRO, confirm the address with ISBE.
const ISBE_GOVERNANCE = "0x00000000000000000000000000000000000015Be";
async function main() {
const [admin] = await ethers.getSigners();
const c = await ethers.deployContract("EvidenceRegistry", [admin.address, ISBE_GOVERNANCE]);
await c.waitForDeployment();
console.log("EvidenceRegistry:", await c.getAddress());
}
main().catch((e) => { console.error(e); process.exitCode = 1; });npx hardhat test
npx hardhat run scripts/deploy-evidence.ts --network isbeIf you prefer Foundry, the documentation accepts it: mode 1 deliverables are "a project (Hardhat or Foundry)." The official template uses Hardhat.
Step 5: Prepare the technical review
Once your company has portal access, you register the contract for technical review. For a custom contract, the documentation asks for a complete package:
- the repository with the source code and the exact commit or branch;
- documentation of the unstructured storage (slots and layout) and of the role scheme, including the PAUSER_ROLE;
- unit and integration tests, with a coverage report;
- static analysis with no open critical findings, using Slither, Mythril or similar tools;
- a reproducible build: exact Solidity version, flags,
metadata.jsonand dependencies with their lock file. The template includes annpm run standard-inputscript that extracts the compiler's standard input from the build; - complete deployment scripts.
| Conformity level | Typical profile | What it requires | Indicative timeline |
|---|---|---|---|
| C (minimum) | Simple contract, basic logic | Tests, static analysis, roles, pausing and a reproducible build | 2-4 weeks |
| B (advanced) | Complex contract | Level C plus property and invariant tests | 4-8 weeks |
| A (certified) | Regulated financial instrument | Level B plus an external security audit | 4-12 weeks |
An incomplete package restarts the clock. And be clear about the review's scope: ISBE checks minimum technical controls and conformity with its architecture, but it does not audit the full business logic. The contract's behavior remains the responsibility of whoever provides the use case.
Step 6: PRE, PRO and the public explorer
Once the contract is approved, ISBE handles the rest:
- PRE (ISBE Testnet, chain ID 11073). This is the pre-production testnet: ISBE deploys and you check that everything works. The portal's Faucet provides test tokens in this environment.
- PRO (ISBE Mainnet, chain ID 33073). This is the final deployment. ISBE returns the use-case address, the entry point for your applications.
- Commercial Services Catalog. This is the last onboarding step. Deploying does not list you there: it takes a separate request.
Your applications read and write through the RPC endpoint ISBE assigns you, with an API key from the portal's BaaS service or through your own execution node. For reading, anyone can also use the Blockscout explorer:
- PRO:
blockscout-main.portal.redisbe.com(Red Case) andblockscout-bare.portal.redisbe.com(Red Bare); - PRE:
blockscout-main.pre.portal.redisbe.comandblockscout-bare.pre.portal.redisbe.com.
The explorer shows blocks, transactions, events and contracts, plus the source code if the contract is verified; ask ISBE whether verification is enabled in your environment. Balances are simulated and have no economic value. It also exposes a read-only REST API that works without a session:
# ISBE Mainnet (PRO) statistics
curl -s https://blockscout-main.portal.redisbe.com/api/v2/stats
# Deployed and verified contracts
curl -s https://blockscout-main.portal.redisbe.com/api/v2/smart-contracts/counters
# A specific address: the governance Diamond
curl -s https://blockscout-main.portal.redisbe.com/api/v2/addresses/0x00000000000000000000000000000000000015BEThe first two calls return, among other data, total transactions and addresses, the average block time and how many contracts are deployed and verified. Run them when you need the figures: they change every day.
How Unknown Gravity approaches it
Unknown Gravity is a member of Alastria, the association that operates ISBE. We are not an ISBE node, operator or partner, and deployment to PRE and PRO always goes through ISBE's infrastructure. What we do is prepare the contract for the mode that fits your use case, write the tests and the technical review package, and support your company through onboarding. If the contract represents a transferable security, the network is not enough: Spain's Law 6/2023 requires the issuer to appoint the entity that keeps the register (Article 8.4). We cover that in how to tokenize securities on ISBE. For the full picture, see our page on building on ISBE.
Frequently asked questions
Can I test a contract on ISBE before completing onboarding?
Yes. The documentation lets you build and test on a local ISBE network with the @red-isbe/isbe-contracts library and Besu in Docker before onboarding. Once your company has PRE access, you only change the RPC URL.
Who deploys the contract to PRE and PRO?
According to ISBE's documentation, its staff do, in all three modes and after the technical review. You register the contract on the portal and ISBE's team orchestrates the deployment through the network's governance infrastructure. The ECO1020 policy adds that the transaction is signed by your company's verified identity (DID) and executed by ISBE's Factory. With the wizard for pre-certified templates, you sign and the contract is deployed immediately.
Do I have to give ISBE the PAUSER_ROLE?
It depends on the mode. In mode 1, ISBE already holds it and it is irrevocable. In mode 2, assigning it to ISBE's governance address is a condition for approval. In mode 3, ISBE does not guarantee it holds the role and, in an emergency, would ask for a pause or filter the address.
How long does approval of a custom contract take?
According to the documentation, 2 to 4 weeks at level C, 4 to 8 at level B and 4 to 12 at level A. The ECO1020 v1.0 policy sets an overall target of 25 business days for the procedure, with 10 days for you to fix findings.
Does it cost gas to deploy or use a contract on ISBE?
Gas has no economic cost: it only limits execution. For network use, the ADM1010 v1.0 policy of December 16, 2025 provides for a monthly subscription starting at €500 for 50,000 transactions; it is not public whether it is already being charged. We break it down in ISBE's fees.
Does deploying on ISBE make my token compliant with Law 6/2023 or MiCA?
No. MiCA and Spain's Law 6/2023 apply to the asset, not to the network. If the token is a transferable security, an ERIR and MiFID II come into play: the issuer must appoint, in the issuance document, the entity that keeps the register (Article 8.4 of Law 6/2023), and ISBE does not present itself as one.
Sources
- Build on ISBE (docs.redisbe.com, in Spanish)
- Set up your environment and start the local network (docs.redisbe.com)
- Your contract and its deployment (docs.redisbe.com)
- Deployment modes (docs.redisbe.com)
- Custom contract development and conformity levels (docs.redisbe.com)
- Network security and governance (docs.redisbe.com)
- Smart contracts FAQ (docs.redisbe.com)
- Basic implementation: RBAC and pausing (docs.redisbe.com)
- ISBE network technical parameters (docs.redisbe.com)
- Blockscout user guide (docs.redisbe.com)
- Onboarding process (docs.redisbe.com)
- isbe-clients-template (ISBE GitHub)
- @red-isbe/isbe-contracts library (npm)
- ECO1020 v1.0 policy on use cases and smart contracts (ISBE GitHub)
- ADM1010 v1.0 fees and compensation policy (ISBE GitHub)
- ISBE Mainnet Blockscout explorer
- Spain's Law 6/2023 on Securities Markets and Investment Services (BOE)
Framework verified as of October 16, 2026. Have a contract you want to bring to ISBE? Talk to us.
