This repository is the source of truth for the infrastructure currently used to deploy partner multichain mainnet nodes.
This README replaces the external Google Doc and is intended to be the tracked, versioned deployment guide for provisioning a brand new partner mainnet node using the Terraform and helper scripts in this repository.
This guide walks through:
- GCP project setup
- required APIs and permissions
- Terraform state bucket creation
- key generation
- NEAR account coordination
- GCP Secret Manager population
- Terraform configuration and deployment
- DNS cutover and node verification
- redeploys and debugging
Before you begin, make sure you have:
- access to create and administer a GCP project
- permissions equivalent to
Owneron the project you will deploy into - a domain or subdomain you can manage in DNS
- a local machine with:
gitgcloudterraformrust/cargo
- access to the chain signatures team for:
- NEAR account creation
- shared RPC secret values
- network initialization
Relevant paths in this repository:
key_scripts/generate_keys/
terraform/partner-mainnet/
terraform/partner-mainnet/scripts/
terraform/partner-mainnet/resources.tf
terraform/partner-mainnet/variables.tf
terraform/partner-mainnet/terraform-mainnet-example.auto.tfvars
Create a new GCP project for the node deployment.
Use a simple project name without spaces or special characters, for example:
partner-multichain
You will need the project ID later.
You can also use the project search flow in the GCP header if you are switching between multiple projects:
Make sure the person performing the deployment is an admin for the org or at least has the Owner role on the project.
Search for IAM & Admin in the GCP console and confirm your user has the correct role.
Search for Secret Manager in the GCP console.
Enable Secret Manager API for the project if it is not already enabled.
Enable the Compute Engine API by opening VM instances in the GCP console and clicking Enable if required.
Search for Cloud Storage in the GCP console.
Create a Cloud Storage bucket for Terraform state.
Recommended bucket naming pattern:
multichain-terraform-<your-entity-name>
Recommended settings:
- region:
europe-west1 - storage class:
Standard - public access prevention: enabled
- access control:
Uniform - soft delete:
7 days
Inside the bucket, create:
state/mainnet
This is where Terraform will store the partner mainnet state.
Before deployment, decide what DNS name you want the node to use.
Recommended pattern:
multichain-mainnet-0.<your-domain>
You will point an A record for this hostname to the load balancer IP after Terraform finishes.
Example:
multichain-mainnet-0.example.com
You need to generate:
- cipher keypair
- sign keypair
- NEAR account keypair
- Ethereum account key / address
- Solana account key / address
On macOS or Linux:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
cargo --versiongit clone https://github.com/sig-net/mpc-infrastructure.git
cd mpc-infrastructure
git checkout main
git pullcd key_scripts/generate_keys
cargo runSave all generated keys securely.
Do not lose these keys. Treat them as production secrets.
Choose a new NEAR account name for the node.
Recommended pattern:
<company-name-variation-sig>.near
Make sure it does not conflict with an existing account. Check availability on Nearblocks.
Send the following to the chain signatures team:
- the NEAR public key generated in the previous step
- the NEAR account name you want created
The team will create the NEAR account for you so you do not need to fund a separate mainnet account just to create it.
After the account is created, verify it on Nearblocks.
Install the Google Cloud CLI.
For example on macOS:
brew install --cask google-cloud-sdkInstall Terraform using HashiCorp’s installation method for your platform.
gcloud auth login
gcloud auth application-default login
gcloud config set project <your_project_id>If prompted to set the quota project, also run:
gcloud auth application-default set-quota-project <your_project_id>This repository includes a helper script at:
terraform/partner-mainnet/scripts/upload_secrets.sh
cd terraform/partner-mainnet/scriptsCreate a file named:
secrets.txt
Do not commit this file.
The current partner mainnet deployment reads the following secrets from GCP Secret Manager:
multichain-account-sk-mainnet-0
multichain-cipher-sk-mainnet-0
multichain-sign-sk-mainnet-0
multichain-sk-share-mainnet-0
multichain-eth-account-sk-mainnet-0
multichain-eth-consensus-rpc-url-mainnet
multichain-eth-execution-rpc-url-mainnet
multichain-sol-account-sk-mainnet-0
multichain-sol-rpc-ws-url-mainnet
multichain-sol-rpc-http-url-mainnet
Use this exact initial format in secrets.txt:
multichain-account-sk-mainnet-0=<near account secret key>
multichain-cipher-sk-mainnet-0=<cipher private key>
multichain-sign-sk-mainnet-0=<sign private key>
multichain-sk-share-mainnet-0=1
multichain-eth-account-sk-mainnet-0=<eth account secret key>
multichain-eth-consensus-rpc-url-mainnet=<eth consensus rpc url>
multichain-eth-execution-rpc-url-mainnet=<eth execution rpc url>
multichain-sol-account-sk-mainnet-0=<sol account secret key>
multichain-sol-rpc-ws-url-mainnet=<sol rpc ws url>
multichain-sol-rpc-http-url-mainnet=<sol rpc http url>
multichain-sk-share-mainnet-0must initially be set to1. You will replace it with a placeholder JSON value in the next step.
./upload_secrets.sh -d <gcp-project-id> -f secrets.txtAfter the secrets are created, open Secret Manager in GCP, find:
multichain-sk-share-mainnet-0
Upload a new secret version with the placeholder value below.
Add a new version with this exact value:
{"hello":0,"private_share":"1111111111111111111111111111111111111111111111111111111111111111","public_key":"1111111111111111111111111111111111111111111111111111111111111111"}This is a placeholder until the later-generated key share is inserted.
If you do not already have the shared RPC values, request them from the chain signatures team for:
multichain-sol-rpc-ws-url-mainnet
multichain-sol-rpc-http-url-mainnet
multichain-eth-execution-rpc-url-mainnet
multichain-eth-consensus-rpc-url-mainnet
In Secret Manager, confirm that these 10 secrets exist and contain the correct values:
multichain-account-sk-mainnet-0
multichain-cipher-sk-mainnet-0
multichain-sign-sk-mainnet-0
multichain-sk-share-mainnet-0
multichain-eth-account-sk-mainnet-0
multichain-eth-consensus-rpc-url-mainnet
multichain-eth-execution-rpc-url-mainnet
multichain-sol-account-sk-mainnet-0
multichain-sol-rpc-http-url-mainnet
multichain-sol-rpc-ws-url-mainnet
Delete secrets.txt and clear it from trash.
cd terraform/partner-mainnetEdit resources.tf and update the backend bucket name:
terraform {
backend "gcs" {
bucket = "multichain-terraform-<your_entity_name>"
prefix = "state/mainnet"
}
}Make sure the bucket name exactly matches the Cloud Storage bucket you created earlier.
Review variables.tf and decide how networking should work for your deployment.
If you do not use a shared or custom GCP network:
- delete the default VPC that GCP created automatically for the project
- set
create_network = true - let Terraform create the network, router, NAT gateway, and firewall rules
If you do use a shared or custom VPC:
- keep
create_network = false - make sure your selected network/subnetwork has outbound internet access
If Terraform hits a
404while creating router or NAT resources during initial deployment, rerun Terraform. This is still good guidance for first-time bring-up.
Copy the example file:
cp terraform-mainnet-example.auto.tfvars terraform-mainnet.auto.tfvarsThen edit terraform-mainnet.auto.tfvars.
At minimum, set:
project_idaccountdomain
Example structure:
env = "mainnet"
project_id = "<your_project_id>"
network = "default"
subnetwork = "default"
image = "europe-west1-docker.pkg.dev/near-cs-mainnet/multichain-public/multichain-mainnet:latest"
region = "europe-west1"
zone = "europe-west1-b"
node_configs = [
{
account = "<your-near-account>"
account_sk_secret_id = "multichain-account-sk-mainnet-0"
cipher_sk_secret_id = "multichain-cipher-sk-mainnet-0"
sign_sk_secret_id = "multichain-sign-sk-mainnet-0"
sk_share_secret_id = "multichain-sk-share-mainnet-0"
domain = "<your-node-domain>"
eth_account_sk_secret_id = "multichain-eth-account-sk-mainnet-0"
eth_consensus_rpc_url_secret_id = "multichain-eth-consensus-rpc-url-mainnet"
eth_execution_rpc_url_secret_id = "multichain-eth-execution-rpc-url-mainnet"
sol_account_sk_secret_id = "multichain-sol-account-sk-mainnet-0"
sol_rpc_ws_url_secret_id = "multichain-sol-rpc-ws-url-mainnet"
sol_rpc_http_url_secret_id = "multichain-sol-rpc-http-url-mainnet"
}
]Notes:
project_idshould match the GCP project ID you createdaccountshould be the NEAR account ID created for this nodedomainshould be the final FQDN for the node, not includinghttps://regionandzonemay be changed if desired, buteurope-west1is the default and preferred region in the current example- use the current image/tag your team has approved for deployment
From the repository root:
git checkout main
git pull
cd terraform/partner-mainnet
terraform init
terraform plan
terraform applyTerraform will create the required resources for the node.
At the end of the apply, note the public IP / load balancer IP that should receive your DNS A record.
After apply finishes, rerun:
terraform planYou should see no unexpected changes.
Create an A record for your node hostname and point it at the IP from the Terraform output.
Example:
multichain-mainnet-0.example.com -> <load-balancer-ip>
After DNS propagates, validate the node:
curl https://<your-node-domain>/stateExpected result:
- the endpoint responds successfully
- the node state reports
running
If the endpoint does not come up immediately, allow time for:
- DNS propagation
- load balancer readiness
- SSL certificate provisioning
Once the node is up, send the following to the chain signatures team:
- NEAR account ID
- node domain name
- cipher public key
- sign public key
- ETH public key / address
- SOL public key
The chain signatures team uses this information to add your node to the network.
Monitor the shared Slack or Discord channels for mainnet release announcements.
For a container image update on the existing COS VM, use:
gcloud compute instances update-container multichain-mainnet-partner-0 \
--zone <your-zone> \
--container-image=europe-west1-docker.pkg.dev/near-cs-mainnet/multichain-public/multichain-mainnet:<current-tag>If your deployment uses a different zone than the example, make sure --zone matches your actual instance zone.
- Open VM instances in the GCP console
- Click the VM instance for the node
- Open Logging
SSH to the instance:
gcloud compute ssh multichain-mainnet-partner-0 --zone <your-zone>List running containers:
docker psUse the container ID shown in the output to inspect logs:
Find the container running the multichain image, then inspect logs:
docker logs <container_id>Search logs for a keyword:
docker logs <container_id> | grep "<keyword>"View the most recent lines:
docker logs <container_id> | tail -n 10Symptoms:
- secret creation commands fail
- Terraform cannot read secrets
Fix:
- enable Secret Manager API in the GCP project
Symptoms:
- Terraform fails when creating instances or related resources
Fix:
- enable Compute Engine / VM Instances API
Symptoms:
- instance cannot pull images
- health checks fail
- node cannot reach external RPCs
Fix:
- if using shared/custom VPC, make sure outbound internet exists
- if using Terraform-managed network, rerun apply if router/NAT resources were not ready on the first pass
Symptoms:
/stateendpoint does not resolve- HTTPS validation fails
Fix:
- wait for A record propagation
- verify the record points to the expected IP
Symptoms:
- HTTPS endpoint not available immediately after apply
Fix:
- allow time for managed certificate and load balancer readiness after DNS points correctly
Symptoms:
- container fails to start correctly
- Terraform data lookups fail
- application starts with missing config
Fix:
- verify all secret IDs in
terraform-mainnet.auto.tfvarsexactly match the Secret Manager names
- treat this repository as the source of truth for current deployment behavior
- prefer updating this README in git instead of maintaining an external setup doc
- do not commit plaintext secrets
- do not rely on stale release-specific instructions; always use the current repo state and the current team-approved image/tag
- keep DNS, zone, and secret naming consistent with your actual deployment
Recommended follow-up documentation improvements for this repo:
- split this README into:
README.mdfor overview and quickstartdocs/partner-mainnet.mdfor the full deployment guide
- add a sample
terraform-mainnet.auto.tfvars.examplewith comments trimmed to only user-editable fields - update
terraform/partner-mainnet/scripts/do-not-commit-example.txtso it reflects the current 10-secret layout - add a short architecture diagram for:
- GCP project
- VM / COS instance
- load balancer
- DNS
- Secret Manager
- Terraform state bucket
- add a post-deploy validation checklist
- add a release / upgrade runbook alongside this guide









