Smart Contract Deployment Guide#
Deploy and interact with smart contracts on Shell-Chain.
See also: Quickstart Guide · JSON-RPC API Reference · Testnet Operator Guide · PQ Crypto Guide
Overview#
Shell-Chain is fully EVM-compatible (Cancun spec). Any contract written in Solidity or Vyper that compiles to EVM bytecode will work on Shell-Chain without modification. Standard tooling — Hardhat, Foundry, Remix — all work out of the box.
Prerequisites#
- Node.js 18+ (for Hardhat)
- Hardhat or Foundry installed
- A running shell-chain node (see Quickstart)
- A funded account (pre-allocated in genesis or received via transfer)
Connecting to Shell Chain#
| Network | RPC URL | Chain ID |
|---|---|---|
| Local | http://localhost:8545 | 1337 |
| Alpha Testnet | http://testnet.shell.xyz | 1337 |
The local endpoint is the default JSON-RPC server started by shell-node run. The alpha testnet endpoint is served via nginx reverse proxy (see Testnet Operator Guide).
Hardhat Setup#
Install Hardhat#
mkdir my-shell-project && cd my-shell-project
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npx hardhat init
Configure networks#
// hardhat.config.js
module.exports = {
solidity: "0.8.26",
networks: {
shell: {
url: "http://localhost:8545",
chainId: 1337,
},
shellAlpha: {
url: "http://testnet.shell.xyz",
chainId: 1337,
}
}
};
Deploy to a local node:
npx hardhat run scripts/deploy.js --network shell
Deploy to the alpha testnet:
npx hardhat run scripts/deploy.js --network shellAlpha
Foundry Setup#
Install Foundry#
curl -L https://foundry.paradigm.xyz | bash
foundryup
Deploy with Foundry#
forge create --rpc-url http://localhost:8545 --chain-id 1337 src/Counter.sol:Counter
For the alpha testnet:
forge create --rpc-url http://testnet.shell.xyz --chain-id 1337 src/Counter.sol:Counter
Example: Deploy a Counter Contract#
1. Write the contract#
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Counter {
uint256 public count;
event CountChanged(uint256 newCount);
function get() public view returns (uint256) {
return count;
}
function increment() public {
count += 1;
emit CountChanged(count);
}
function decrement() public {
require(count > 0, "Counter: cannot decrement below zero");
count -= 1;
emit CountChanged(count);
}
function reset() public {
count = 0;
emit CountChanged(count);
}
}
2. Deploy with Hardhat#
Create scripts/deploy.js:
const hre = require("hardhat");
async function main() {
const Counter = await hre.ethers.getContractFactory("Counter");
const counter = await Counter.deploy();
await counter.waitForDeployment();
console.log("Counter deployed to:", await counter.getAddress());
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
npx hardhat run scripts/deploy.js --network shell
3. Deploy with Foundry#
forge create \
--rpc-url http://localhost:8545 \
--chain-id 1337 \
src/Counter.sol:Counter
Interacting with a Deployed Contract#
Read calls (no gas required)#
Use eth_call to read state without submitting a transaction:
# Call the get() function (selector: 0x6d4ce63c)
curl -s http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"method":"eth_call",
"params":[{
"to":"0xYOUR_CONTRACT_ADDRESS",
"data":"0x6d4ce63c"
},"latest"],
"id":1
}'
Or with Hardhat:
const counter = await hre.ethers.getContractAt("Counter", "0xYOUR_CONTRACT_ADDRESS");
const count = await counter.get();
console.log("Current count:", count.toString());
Write calls (submits a transaction)#
Use eth_sendRawTransaction or shell_sendTransaction to modify state:
# Increment the counter (selector: 0xd09de08a)
curl -s http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"method":"eth_sendRawTransaction",
"params":["0x...signed_tx_bytes..."],
"id":1
}'
Or with Hardhat:
const counter = await hre.ethers.getContractAt("Counter", "0xYOUR_CONTRACT_ADDRESS");
const tx = await counter.increment();
await tx.wait();
console.log("Incremented! New count:", (await counter.get()).toString());
Using PQ Signatures for Deployment#
Shell-Chain uses post-quantum Dilithium3 signatures instead of ECDSA. To deploy contracts using PQ signatures, use the shell_sendTransaction RPC method:
# Sign the deployment transaction with the shell-node CLI
shell-node tx deploy \
--code 0x608060405234801561001057600080fd5b50... \
--keystore my-key.json \
--rpc-url http://127.0.0.1:8545
# Or submit via JSON-RPC
curl -s http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"method":"shell_sendTransaction",
"params":[{
"from": "0xYOUR_ADDRESS",
"data": "0x608060405234801561001057600080fd5b50...",
"gas": "0x100000",
"maxFeePerGas": "0x3b9aca00",
"maxPriorityFeePerGas": "0x0",
"nonce": "0x0",
"pqSignature": "0x...dilithium3_signature...",
"pqPubkey": "0x...dilithium3_pubkey..."
}],
"id":1
}'
Note: Standard Ethereum wallets (MetaMask, etc.) use ECDSA signatures. For full PQ security, use the
shell-nodeCLI or PQ-aware SDKs. See PQ Crypto Guide for details.
Verifying Contracts with debug_traceTransaction#
After deploying a contract, use debug_traceTransaction to inspect the execution trace:
curl -s http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"method":"debug_traceTransaction",
"params":["0xYOUR_TX_HASH"],
"id":1
}' | python3 -m json.tool
The trace shows the full call tree including:
CREATE/CREATE2frames for contract deployment- Gas consumption per opcode
- Storage reads and writes
- Internal calls between contracts
Note: The
debugnamespace must be enabled on the node with--rpc-api eth,net,web3,shell,debug.
EVM Compatibility Notes#
Shell-Chain implements the Cancun EVM specification. Key compatibility details:
Supported Cancun opcodes#
| Opcode | EIP | Description |
|---|---|---|
TSTORE / TLOAD | EIP-1153 | Transient storage (cleared after each tx) |
MCOPY | EIP-5656 | Efficient memory copy |
BLOBHASH | EIP-4844 | Access blob versioned hashes |
BLOBBASEFEE | EIP-7516 | Read blob base fee |
Signature behavior#
ecrecoveralways returnsaddress(0)— Shell-Chain uses post-quantum Dilithium3 signatures, not ECDSA. Contracts that rely onecrecoverfor signature verification will not work as expected. Use the PQ signature scheme instead.
Transaction types supported#
| Type | EIP | Description |
|---|---|---|
| Legacy (type 0) | — | Traditional transactions |
| Access list (type 1) | EIP-2930 | Transactions with access lists for gas savings |
| EIP-1559 (type 2) | EIP-1559 | Dynamic fee transactions with base fee + priority fee |
| Blob (type 3) | EIP-4844 | Blob-carrying transactions for data availability |
Gas model#
Shell-Chain uses the EIP-1559 gas model:
baseFeePerGasadjusts per-block based on gas utilizationmaxPriorityFeePerGasis always0x0on this PoA chain- Use
eth_gasPriceto get the current base fee - Use
eth_feeHistoryfor historical fee data
Gas Estimation Tips#
-
Use
eth_estimateGasbefore submitting transactions. The estimate includes a 20% buffer (gas_used × 1.2) with a minimum of 21,000. -
Check the base fee with
eth_gasPrice. SetmaxFeePerGas≥ the base fee or the transaction will be rejected. -
Access lists save gas for contracts that touch many storage slots. Use
eth_createAccessListto generate one:curl -s http://localhost:8545 \ -H "Content-Type: application/json" \ -d '{ "jsonrpc":"2.0", "method":"eth_createAccessList", "params":[{ "to":"0xYOUR_CONTRACT", "data":"0x..." },"latest"], "id":1 }' -
Transient storage (
TSTORE/TLOAD) is cheaper than regular storage for data only needed within a single transaction. -
Gas limit is set in genesis (default: 30,000,000). Check with
eth_getBlockByNumber.
Further Reading#
- JSON-RPC API Reference — Full list of all 61 RPC methods
- PQ Crypto Guide — Post-quantum signature details
- Testnet Operator Guide — Running testnet nodes
- Quickstart Guide — Get a node running in 5 minutes
Last updated: 2025