The DoubleZero Fee Conversion System consists of both on-chain (Solana blockchain) and off-chain (AWS cloud) components. This guide provides step-by-step instructions to build and deploy the complete system.
This system consists of following components.
- On-chain programs
- Converter Program - Core On-chain program written in anchor, to handle the functionalities of the system.
- Mock Double Zero Transfer Program - On-chain program written in anchor to mock the transfer functionality. It provides CPIs which is used by converter program to simulate actual transfer.
- Off-chain components
- Swap-oracle-service
- Indexer-service
- Matrix Api
- CLI Tools
- Admin CLI - CLI interface for admins to control the system.
- User CLI - CLI interface for user to interact with the system.
- Testing
- E2E Test Suite - End-to-end tests for the system using the solana local test validator.
Ensure you have the following software versions installed:
- Node.js: v22.17.0
- Rust: 1.88.0
- Solana CLI: 2.2.20
- Anchor Framework: 0.31.1
- TypeScript: ^5.9.2
- Python: 3.12.3
- Terraform: v1.12.2
- AWS CLI: v2.18.12
- Docker: v28.3.3
- jq: 1.7
# Solana CLI
sh -c "$(curl -sSfL https://release.anza.xyz/v2.2.20/install)"
# Anchor (requires avm - Anchor Version Manager)
avm use 0.31.1
# Rust
rustup default 1.88.0
# Configure Solana for local development
solana config set -ulexport AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
export AWS_ACCESS_KEY_ID=
export AWS_SECRET_ACCESS_KEY=
./provision.sh [OPTIONS]deployment- Deploy infrastructure componentson-chain- Deploy on-chain programsadmin-cli- Build admin CLIuser-cli- Build user CLIintegration-cli- Build integration CLImock-double-zero-program- Deploy mock programrun-tests- Execute test suites
# Start local validator (if not running)
solana-test-validator
# Subscribe to logs (separate terminal)
solana logs -ul# Create keys directory
mkdir -p on-chain/.keys
mkdir -p mock-double-zero-program/.keys
# Generate keypair for converter program
solana-keygen new -o on-chain/.keys/converter-program-keypair.json
# Generate keypair for mock double zero program
solana-keygen new -o mock-double-zero-program/.keys/mock-double-zero-program-keypair.json
# Install Python dependencies
pip install solders base58
# Generate new keypair
cd deployment/script
python3 script.py
# Or load existing keypair
python3 script.py <keypair.json>This Script give
- Public Key
- Base58 Secret Key
Update the oracle_pubkey with Public key in config.json
This keypair is used for signing price data in the swap-oracle service.
Note
Base58 secret Key used by swap-oracle service we have to update it in parameter store when environment setup done.
Create config.json at the project root with the following structure:
{
"rpc_url": "http://127.0.0.1:8899",
"program_id": "YrQk4TE5Bi6Hsi4u2LbBNwjZUWEaSUaCDJdapJbCE4z",
"double_zero_program_id": "8S2TYzrr1emJMeQ4FUgKhsLyux3vpMhMojMTNKzPebww",
"oracle_pubkey": "3FsydTFGUYNQJH7hx97wJiVYhtiDK3gx4ujXNyf1t8Rj",
"sol_quantity": 25000000000,
"slot_threshold": 134,
"price_maximum_age": 324,
"price_oracle_end_point": "https://clic19jsil.execute-api.us-east-1.amazonaws.com/dev4/api/v1/swap-rate",
"coefficient": 4500,
"max_discount_rate": 5000,
"min_discount_rate": 500
}- program_id: Public key of the main converter program (which is created by earlier step Generate Programs key )
- double_zero_program_id: Public key of the mock transfer program (which is created by earlier step Generate Programs key )
- sol_quantity: Amount of SOL per transaction (in Lamports)
- coefficient: Discount calculation curve coefficient (see formula below)
- price_oracle_end_point: swap-oracle-service endpoint which is created after environment creation( used by User cli to get swap rate. So doesnt need to be set at deployment to blockchain)
Where:
-
$N$ : Desired number of slots between trades where the discount rate changes from$D_{min}$ to$D_{max}$ -
$D_{max}$ : Maximum discount rate (basis points) -
$D_{min}$ : Minimum discount rate (basis points)
Example:
$$ \gamma = \frac{5000 - 500}{432000} \approx 0.01042 $$ The coefficient is then multiplied by 10,000 to get the final coefficient. This is done to avoid floating point precision issues and preserve the precision of coefficient.
In the above example, considering that the
Note
Before deploy the program make sure you configure anchor correctly in both convertor program and mock double zero program. Refer.
check the Anchor.toml
[programs.localnet]
converter_program = "YrQk4TE5Bi6Hsi4u2LbBNwjZUWEaSUaCDJdapJbCE4z" # update public key of the program
[provider]
cluster = "localnet" # point to correct cluster (Devnet, Mainnet)
wallet = "~/.config/solana/id.json" # Check this file point to actual network
[programs.localnet]
mock_transfer_program = "8S2TYzrr1emJMeQ4FUgKhsLyux3vpMhMojMTNKzPebww" # update public key of the program
[registry]
url = "https://api.apr.dev"
[provider]
cluster = "localnet" # point to correct cluster (Devnet, Mainnet)
wallet = "~/.config/solana/id.json" # Check this file point to actual network
# Deploy converter program
anchor build && anchor deploy --program-name converter-program --program-keypair <keypair file path>
# Deploy mock double zero program
anchor build && anchor deploy --program-name mock-double-zero-program --program-keypair <keypair file path>Ensure you have generated the keypair in the .keys folder. See Solana Keypair Management
# Deploy on-chain workspace
./provision.sh -w on-chain --restart-validator
# Deploy mock program
./provision.sh -w mock-double-zero-program --restart-validatorNote: Only use --restart-validator flag for local test environments. For devnet or mainnet deployments, omit this flag as you cannot restart public network validators.
# Export your wallet private key as environment variable
export PRIVATE_KEY=226,222,1,3...Note: Ensure this account has sufficient SOL for transactions.
Off-chain components use the config npm module where the environment name and config file name must match exactly.
- Environment:
dev1-test→ Config file:dev1-test.json - Environment:
production→ Config file:production.json
Note
If there is no configuration for the environment application, take default.json as the configuration file..
Create config file: indexer-service/config/{env-name}.json
{
"applicationPort": 8080,
"rpcUrl": "https://api.devnet.solana.com",
"programId": "program_id",
"concurrency": 8,
"logLevel": "WARN"
}Note Update PROGRAM_ID with public key of the convertor program Refer.
Create config file: swap-oracle-service/config/{env-name}.json
{
"applicationPort": 8080,
"priceCacheTTLSeconds": 30,
"logLevel": "INFO",
"maxConfidenceRatio": 0.007,
"pricingServices": [
{
"name": "pyth",
"type": "pyth",
"endpoint": "https://hermes.pyth.network",
"priceFeedIds": {
"SOL/USD": "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d",
"2Z/USD": "0x879551021853eec7a7dc827578e8e69da7e4fa8148339aa0d3d5296405be4b1a"
}
}
]
}Before create any resources, configure the AWS foundation:
# Navigate to the script directory
cd deployment/script
# Run one-time setup for your target region
./one_time_setup.sh us-east-1What this does:
- Creates Terraform state S3 bucket:
doublezero-terraform-state-{account-id} - Configures bucket security settings (versioning, KMS encryption, access blocking)
- Sets up IAM policies and resource tagging for governance
When using --workspace deployment, specify one of:
account- Manage account-level resourcesregional- Manage regional resourcesenvironment- Manage environment-specific resourcesrelease- Manage releases and artifacts
create- Create new resourcesdestroy- Destroy existing resourcespublish-artifacts- Build and publish artifactsupgrade- Upgrade existing environment with new release tagpublish-and-upgrade- Publish artifacts then upgrade environmenthelp- Show command-specific help
--region- AWS region (e.g.,us-east-1,us-west-1,us-west-2)--env- Environment name (e.g.,dev1-test,staging,production)--release-tag- Release tag for artifacts (e.g.,dev1-test-v5.2.1,v1.0.0)-h, --help- Show help information
./provision.sh -w deployment -sc account -a create --region <region>Creates: IAM roles, shared resources across all regions/environments
./provision.sh -w deployment -sc regional -a create --region <region>Creates:
- VPC and networking
- Security groups
- Load balancers
- ECR repositories
- CloudWatch log groups
./provision.sh -w deployment -sc release -a publish-artifacts --region <region> --release-tag <release-tag>Actions:
- Builds Docker images for all services
- Tags images with release tag
- Pushes to ECR repositories
- Creates deployment artifacts
./provision.sh -w deployment -sc environment -a create --env <env> --region <region> --release-tag <release-tag>Creates:
- ECS/EC2 instances
- Environment databases
- Application load balancers
- Auto Scaling Groups
- Deploys applications with specified release tag
Note
Update AWS Parameter Store value of <env>/double-zero/oracle-pricing-key with the Base58 encoded secret key. Refer.
./provision.sh -w deployment -sc release -a publish-and-upgrade --env <env> --region <region> --release-tag <release-tag>Process:
- Publishes new artifacts
- Updates launch templates
- Triggers rolling deployment
- Validates deployment success
# Destroy environment
./provision.sh -w deployment -sc environment -a destroy --env <env> --region <region>
# Destroy regional resources
./provision.sh -w deployment -sc regional -a destroy --region <region>The Admin CLI provides comprehensive system management capabilities. All commands should be run from the project root directory.
Initializes the system by creating the configuration registry, fills_registry, deny_list_registry and program state account.
cargo run -p admin-cli -- initDisplays current configuration registry contents.
cargo run -p admin-cli -- view-configUpdates the configuration of the system. The command reads the config.json file and updates the configuration according to the values in the file.
cargo run -p admin-cli -- update-configDisplays current system state.
cargo run -p admin-cli -- view-system-stateControls system operation state. When paused, no new trades can be executed.
# Activate the system
cargo run -p admin-cli -- toggle-system-state --activate
# Pause the system
cargo run -p admin-cli -- toggle-system-state --pauseSets the admin of the system. Only the program deployer can set/change the admin.
cargo run -p admin-cli -- set-admin -a <ADMIN_ACCOUNT>-a: Admin account public key
Sets the deny authority of the system.
cargo run -p admin-cli -- set-deny-authority -a <DENY_AUTHORITY_ACCOUNT>-a: Deny authority account public key
Sets fill consumer public key in the configuration registry.
cargo run -p admin-cli -- set-fills-consumer -a <FILL_CONSUMER_ACCOUNT>-a: Fill consumer's public key
Adds an address to the deny list registry.
cargo run -p admin-cli -- add-to-deny-list -a <USER_ACCOUNT>-a: User account public key
Removes an address from the deny list registry.
cargo run -p admin-cli -- remove-from-deny-list -a <USER_ACCOUNT>-a: User account public key
Displays all addresses in the deny list registry.
cargo run -p admin-cli -- view-deny-listViews the Fills Registry, which tracks individual fill records and overall aggregate statistics.
cargo run -p admin-cli -- view-fill-registryInitializes Mock token Program Accounts
cargo run -p admin-cli -- init-mock-programSends a specified amount of SOL to Mock Journal
cargo run -p admin-cli -- airdrop-to-mock-journal -a <AMOUNT>AMOUNT: SOL amount to be airdropped.
Mints Mock 2Z token to a specified address.
cargo run -p admin-cli -- mock-token-mint -a <AMOUNT> -t <DESTINATION_TOKEN_ACCOUNT>-a: Token Amount to be minted-t: Destination token account address. (Optional, If not specified, defaults to signer's Associated Token Account)
Mints specified amount of Mock 2Z token to protocol Treasury Account
cargo run -p admin-cli -- mint-to-mock-protocol-treasury -a <AMOUNT>-a: 2Z Token amount to be minted.
Calculates the current discount rate and estimates the ask price (in 2Z tokens) for the given SOL quantity.
cargo run -p user-cli -- get-priceDisplays the current SOL quantity that can be purchased by spending 2Z tokens.
cargo run -p user-cli -- get-quantityInitiates SOL purchase. Trade executes at bid price if ask price ≤ bid price; otherwise cancels.
cargo run -p user-cli -- buy-sol -p <bid_price> -f <SOURCE_ACCOUNT>-p: User's maximum acceptable purchase price-f: Source token account address. (Optional, If not specified, defaults to signer's Associated Token Account)
View Fills Registry, which tracks individual fill records and overall aggregate statistics
cargo run -p user-cli -- get-fills-info Dequeues fills up to specified SOL amount. Returns total SOL and 2Z amounts processed. Only callable by authorized integrating contracts.
cargo run -p integration-cli -- dequeue-fills -a <max_sol_amount>-a: Maximum SOL amount to dequeue in this operation
The DoubleZero system exposes metrics through RESTful APIs. Below are sample API calls to retrieve system metrics.
Replace the INVOKE_URL based on your deployment environment. You can get it from AWS Console > API Gateway > Stages.
Retrieve bucketed buy transaction counts for a date range:
# Get daily buys for August 2025
curl -X GET "<INVOKE_URL>/api/v1/metrics/buys?from=2025-08-01&to=2025-08-02" -H "Accept: application/json"
# Response example:
{
"from": "2025-08-01",
"to": "2025-08-02",
"data": [
{
"bucket": "2025-08-01",
"start": "2025-08-01T00:00:00Z",
"end": "2025-08-02T00:00:00Z",
"buys": 19
},
{
"bucket": "2025-08-02",
"start": "2025-08-02T00:00:00Z",
"end": "2025-08-03T00:00:00Z",
"buys": 14
}
]
}Retrieve dequeue event counts and SOL amount aggregates:
# Get daily dequeue metrics for August 2025
curl -X GET "<INVOKE_URL>/api/v1/metrics/dequeues?from=2025-08-01&to=2025-08-02"
-H "Accept: application/json"./provision.sh -w run-tests --test-type unit./provision.sh -w run-tests --test-type e2e
# Or manual setup
cd e2e
npm install
./test_runner.sh --test-type e2egraph TD
A[Install Dependencies] --> B[Setup On-Chain Environment]
B --> C[Configure config.json]
C --> D[Deploy On-Chain Programs]
D --> E[Create AWS Account Resources]
E --> F[Create Regional Resources]
F --> G[Configure Off-Chain Components]
G --> H[Publish Artifacts]
H --> I[Create Environment]
I --> J[Initialize System via Admin CLI]
J --> K[System Ready for Users]
K --> L[Upgrade Process]
L --> K
- Keypair Issues: Ensure keypairs are properly generated and funded
- Configuration Mismatches: Verify environment names match config file names exactly
- AWS Permissions: Ensure AWS CLI has proper permissions for Terraform operations
- Version Conflicts: Use exact versions specified in dependencies
- Check Solana logs:
solana logs -ul - Review AWS CloudWatch logs for off-chain components
- Validate configurations before deployment
- Test in local environment before deploying to AWS
-
Define new state structs
Create new structs for your upgraded accounts (e.g.,NewConfigurationRegistry,NewProgramState) and update the program code to use them. -
Add new seed definitions
Create a new file underon-chain/programs/converter-program/src/common/seeds/, similar toseed_prefix_v1.rs, and define your new seeds there.
- If an account needs changes, introduce a new seed prefix.
- If the account is unchanged, reuse the existing seed.
-
Update default seed profile
Modifyon-chain/programs/converter-program/src/common/seeds/seed_prefixes.rsto point the default profile to the new version. -
Implement migration logic
Add a new migration handler (e.g.,on-chain/programs/converter-program/src/migration/migrate_v1_to_v2.rs) to define the migration rules between versions. -
Run the migration
Execute the migration instruction from your off-chain client to upgrade on-chain accounts. -
(Optional) Define rollback
If rollback support is required, implement a reverse migration function as well.