TestNet is a production-like testing network. Joining TestNet requires approval to join MainNet by the Tokenomics Committee, your validator egress IP must be allowlisted, and you need an onboarding secret from your SV sponsor. TestNet resets every 3–6 months and receives upgrades after DevNet but before MainNet.
Use TestNet for long-running app testing before MainNet.
Official reference requirements say a low-activity production validator needs around 2 CPUs and 8GB RAM, and an app provider with moderate activity may need around 2 CPUs, 16GB RAM, and 100GB database size.
Recommended for TestNet:
2 vCPU minimum
8GB RAM minimum
16GB RAM preferred for application providers
100GB disk preferred
Static public egress IP
Add swap if the VPS has low RAM:
fallocate -l 8G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
grep -q '^/swapfile ' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
free -h
sudo apt update
sudo apt install -y curl jq tar ca-certificates
curl -fsSL https://get.docker.com | sh
docker compose version
curl --version
jq --version
Docker Compose must be version 2.26.0 or newer.
Optional for sequencer checking:
sudo apt install -y wget
wget -O /tmp/grpcurl.deb https://github.com/fullstorydev/grpcurl/releases/download/v1.9.3/grpcurl_1.9.3_linux_amd64.deb
sudo apt install -y /tmp/grpcurl.deb
grpcurl --version
Use your sponsor-provided SV URL if different.
NETWORK_NAME="testnet"
INFO_URL="https://docs.test.global.canton.network.sync.global/info"
SEED_SCAN_URL="https://scan.sv-1.test.global.canton.network.sync.global"
SPONSOR_SV_URL="https://sv.sv-1.test.global.canton.network.sync.global"
Your sponsor’s SV URL should be an SV app URL of the form https://sv..., not a Scan URL.
TestNet requires:
1. Tokenomics Committee approval
2. Static validator egress IP
3. IP allowlist by SV operators
4. Onboarding secret from your SV sponsor
TestNet requires approval, allowlisting, and an onboarding secret from your sponsor.
Check your IP:
curl -sSL http://checkip.amazonaws.com
Send this IP to your sponsor. Official docs say one IP may be provided per network and it must be distinct from the IP used for the other networks.
Run from the validator server:
CURL='curl -fsS -m 5 --connect-timeout 5'
echo "Egress IP:"
curl -sSL http://checkip.amazonaws.com
echo "Checking TestNet Scan endpoints..."
for url in $($CURL "$SEED_SCAN_URL/api/scan/v0/scans" | jq -r '.scans[].scans[].publicUrl'); do
echo -n "$url: "
$CURL "$url/api/scan/version" | jq -r '.version' || echo "FAILED"
done
Check sequencers:
CURL='curl -fsS -m 5 --connect-timeout 5'
echo "Checking TestNet Sequencer endpoints..."
for url in $($CURL "$SEED_SCAN_URL/api/scan/v0/dso-sequencers" | jq -r '.domainSequencers[].sequencers[].url | sub("https://"; "")'); do
echo -n "$url: "
grpcurl --max-time 10 "$url:443" grpc.health.v1.Health/Check || echo "FAILED"
done
Sequencers that are functional and have allowlisted your IP return "status": "SERVING"; the default configuration requires access to at least 2/3 of SVs for both Scan and Sequencers.
you can use script below:
curl -sSL http://checkip.amazonaws.com
OK=0
FAIL=0
CURL='curl -4 -fsS -m 5 --connect-timeout 5'
for url in $($CURL https://scan.sv-1.test.global.canton.network.sync.global/api/scan/v0/scans | jq -r '.scans[].scans[].publicUrl'); do
echo -n "$url: "
if version=$($CURL "$url/api/scan/version" | jq -r '.version' 2>/dev/null); then
echo "OK version=$version"
OK=$((OK+1))
else
echo "FAIL"
FAIL=$((FAIL+1))
fi
done
echo
echo "Scan OK: $OK"
echo "Scan FAIL: $FAIL"
cd /tmp
GRPCURL_VERSION="1.9.3"
ARCH="$(dpkg --print-architecture)"
case "$ARCH" in
amd64) GRPCURL_ARCH="x86_64" ;;
arm64) GRPCURL_ARCH="arm64" ;;
*) echo "Unsupported arch: $ARCH"; exit 1 ;;
esac
curl -L -o grpcurl.tar.gz \
"https://github.com/fullstorydev/grpcurl/releases/download/v${GRPCURL_VERSION}/grpcurl_${GRPCURL_VERSION}_linux_${GRPCURL_ARCH}.tar.gz"
sudo tar -xzf grpcurl.tar.gz -C /usr/local/bin grpcurl
grpcurl -version
OK=0
FAIL=0
CURL='curl -4 -fsS -m 5 --connect-timeout 5'
for host in $($CURL https://scan.sv-1.test.global.canton.network.sync.global/api/scan/v0/dso-sequencers | jq -r '.domainSequencers[].sequencers[].url | sub("https://"; "")'); do
echo -n "$host: "
if grpcurl --max-time 10 "$host:443" grpc.health.v1.Health/Check 2>/tmp/canton_seq_error.txt | grep -q SERVING; then
echo "OK SERVING"
OK=$((OK+1))
else
echo "FAIL $(cat /tmp/canton_seq_error.txt | head -c 250)"
FAIL=$((FAIL+1))
fi
done
echo
echo "Sequencer OK: $OK"
echo "Sequencer FAIL: $FAIL"
sudo tee -a /etc/hosts >/dev/null <<'EOF'
127.0.0.1 json-ledger-api.localhost
127.0.0.1 grpc-ledger-api.localhost
127.0.0.1 validator.localhost
127.0.0.1 app-provider.localhost
127.0.0.1 participant.localhost
127.0.0.1 wallet.localhost
127.0.0.1 ans.localhost
127.0.0.1 keycloak.localhost
127.0.0.1 host.docker.internal
EOF
mkdir -p ~/canton-testnet
cd ~/canton-testnet
SPLICE_VERSION="$(curl -fsSL "$INFO_URL" | jq -r '.synchronizer.active.version')"
MIGRATION_ID="$(curl -fsSL "$INFO_URL" | jq -r '.synchronizer.active.migration_id')"
echo "SPLICE_VERSION=$SPLICE_VERSION"
echo "MIGRATION_ID=$MIGRATION_ID"
curl -fL -o "${SPLICE_VERSION}_splice-node.tar.gz" \
"https://github.com/digital-asset/decentralized-canton-sync/releases/download/v${SPLICE_VERSION}/${SPLICE_VERSION}_splice-node.tar.gz"
tar xzf "${SPLICE_VERSION}_splice-node.tar.gz"
cd splice-node/docker-compose/validator
export IMAGE_TAG="$SPLICE_VERSION"
On TestNet, you cannot self-generate the onboarding secret. Your SV sponsor must generate and send you the secret. The official docs state that TestNet and MainNet secrets must be provided manually by the SV sponsor, are one-time use, and expire after 48 hours.
Ask your sponsor for:
SPONSOR_SV_URL
ONBOARDING_SECRET
MIGRATION_ID, if they want you to use a specific one
Then set:
SPONSOR_SV_URL="https://sv.sv-1.test.global.canton.network.YOUR_SPONSOR_DOMAIN"
ONBOARDING_SECRET="PASTE_SECRET_FROM_SPONSOR"
PARTY_HINT="yourcompany-validator-1"
The party hint becomes part of your validator administrator Party ID and cannot be changed later.
cd ~/canton-testnet/splice-node/docker-compose/validator
SPLICE_VERSION="$(curl -fsSL "$INFO_URL" | jq -r '.synchronizer.active.version')"
MIGRATION_ID="$(curl -fsSL "$INFO_URL" | jq -r '.synchronizer.active.migration_id')"
export IMAGE_TAG="$SPLICE_VERSION"
echo "SPLICE_VERSION=$SPLICE_VERSION"
echo "MIGRATION_ID=$MIGRATION_ID"
echo "Secret length: ${#ONBOARDING_SECRET}"
./start.sh \
-s "$SPONSOR_SV_URL" \
-o "$ONBOARDING_SECRET" \
-p "$PARTY_HINT" \
-m "$MIGRATION_ID" \
-w
docker compose ps
docker inspect splice-validator-participant-1 \
--format 'PARTICIPANT RestartCount={{.RestartCount}} OOMKilled={{.State.OOMKilled}} Status={{.State.Status}} Health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}'
docker inspect splice-validator-validator-1 \
--format 'VALIDATOR RestartCount={{.RestartCount}} OOMKilled={{.State.OOMKilled}} Status={{.State.Status}} Health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}'
Expected:
participant healthy
validator healthy
postgres healthy
nginx healthy
wallet UI healthy
ans UI healthy
If not healthy:
docker compose logs --tail=200 validator
docker compose logs --tail=200 participant
Common TestNet issues:
Secret expired:
Ask sponsor for a new TestNet onboarding secret.
403 / PermissionDenied:
IP is not fully allowlisted.
Sequencer timeout:
SV connectivity not stable yet.
participant:5002 connection refused:
participant restarted or is unstable.
OOM / low RAM:
add swap or upgrade server.
Use SSH tunnel:
ssh -N -L 8080:127.0.0.1:80 root@YOUR_SERVER_IP
Open:
http://wallet.localhost:8080
Username:
administrator