cURL
curl --request POST \
--url 'https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/' \
--header 'Content-Type: application/json' \
--data '
{
"id": "test-account",
"jsonrpc": "2.0",
"method": "getCompressedAccountBalance",
"params": {
"address": null,
"hash": "11111111111111111111111111111111"
}
}
'import requests
url = "https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/"
payload = {
"id": "test-account",
"jsonrpc": "2.0",
"method": "getCompressedAccountBalance",
"params": {
"address": None,
"hash": "11111111111111111111111111111111"
}
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
id: 'test-account',
jsonrpc: '2.0',
method: 'getCompressedAccountBalance',
params: {address: null, hash: '11111111111111111111111111111111'}
})
};
fetch('https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'id' => 'test-account',
'jsonrpc' => '2.0',
'method' => 'getCompressedAccountBalance',
'params' => [
'address' => null,
'hash' => '11111111111111111111111111111111'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/"
payload := strings.NewReader("{\n \"id\": \"test-account\",\n \"jsonrpc\": \"2.0\",\n \"method\": \"getCompressedAccountBalance\",\n \"params\": {\n \"address\": null,\n \"hash\": \"11111111111111111111111111111111\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/")
.header("Content-Type", "application/json")
.body("{\n \"id\": \"test-account\",\n \"jsonrpc\": \"2.0\",\n \"method\": \"getCompressedAccountBalance\",\n \"params\": {\n \"address\": null,\n \"hash\": \"11111111111111111111111111111111\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"id\": \"test-account\",\n \"jsonrpc\": \"2.0\",\n \"method\": \"getCompressedAccountBalance\",\n \"params\": {\n \"address\": null,\n \"hash\": \"11111111111111111111111111111111\"\n }\n}"
response = http.request(request)
puts response.read_body{
"context": {
"slot": 100
},
"value": 100
}{
"error": "<string>"
}{
"error": "<string>"
}JSON RPC Methods
Getvalidityproof
Retrieve a single ZK Proof used by the compression program to verify that the given accounts are valid and the new addresses can be created. RPC method guide with use cases, tips and examples.
POST
/
cURL
curl --request POST \
--url 'https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/' \
--header 'Content-Type: application/json' \
--data '
{
"id": "test-account",
"jsonrpc": "2.0",
"method": "getCompressedAccountBalance",
"params": {
"address": null,
"hash": "11111111111111111111111111111111"
}
}
'import requests
url = "https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/"
payload = {
"id": "test-account",
"jsonrpc": "2.0",
"method": "getCompressedAccountBalance",
"params": {
"address": None,
"hash": "11111111111111111111111111111111"
}
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
id: 'test-account',
jsonrpc: '2.0',
method: 'getCompressedAccountBalance',
params: {address: null, hash: '11111111111111111111111111111111'}
})
};
fetch('https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'id' => 'test-account',
'jsonrpc' => '2.0',
'method' => 'getCompressedAccountBalance',
'params' => [
'address' => null,
'hash' => '11111111111111111111111111111111'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/"
payload := strings.NewReader("{\n \"id\": \"test-account\",\n \"jsonrpc\": \"2.0\",\n \"method\": \"getCompressedAccountBalance\",\n \"params\": {\n \"address\": null,\n \"hash\": \"11111111111111111111111111111111\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/")
.header("Content-Type", "application/json")
.body("{\n \"id\": \"test-account\",\n \"jsonrpc\": \"2.0\",\n \"method\": \"getCompressedAccountBalance\",\n \"params\": {\n \"address\": null,\n \"hash\": \"11111111111111111111111111111111\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://mainnet.helius-rpc.com/?api-key=%3Capi_key%3E/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"id\": \"test-account\",\n \"jsonrpc\": \"2.0\",\n \"method\": \"getCompressedAccountBalance\",\n \"params\": {\n \"address\": null,\n \"hash\": \"11111111111111111111111111111111\"\n }\n}"
response = http.request(request)
puts response.read_body{
"context": {
"slot": 100
},
"value": 100
}{
"error": "<string>"
}{
"error": "<string>"
}The
Common Use Cases
Example: Generate Validity Proof
Generate a validity proof for compressed accounts.
Example: Proof Generation for Token Transfer
Generate a validity proof for a compressed token transfer operation:
getValidityProof RPC method generates zero-knowledge proofs to verify that the given accounts are valid or the new addresses can be created. This proof is required for any operation on compressed accounts (transfer, approve, decompress, etc.) for on-chain verification of compressed state.
You can test this method via the OpenAPI example or custom examples below.
- Proof limits per request are:
hashes: 1, 2, 3, 4, or 8newAddressesWithTrees: 1, 2
- The
newAddressesparam field is supported but deprecated. Please usenewAddressesWithTreesinstead.
- Token Transfers: Generate proofs required for transferring compressed tokens.
- Account Operations: Create proofs needed for any compressed account modification.
- Batch Processing: Generate proofs for multiple accounts in a single call.
- State Tree Verification: Prove account inclusion in the current state tree.
- Transaction Building: Obtain proof data needed for compressed transaction instructions.
- Program Integration: Get validity proofs for custom program operations on compressed accounts.
hashes(BN254[], required): Array of BN254 objects representing compressed account hashes to generate proofs for.newAddresses(BN254[], optional): Array of BN254 objects representing new addresses to include in the proof for address tree verification.
compressedProof(ValidityProof | null): The compressed validity proof object for zero-knowledge verificationroots(BN[]): Array of merkle tree roots used in proof generationrootIndices(number[]): Array of indices of the roots in the state treeleafIndices(number[]): Array of indices of the leaves being provenleaves(BN[]): Array of leaf values in the merkle treetreeInfos(TreeInfo[]): Array of information about the state trees usedproveByIndices(boolean[]): Array indicating whether to prove by indices for each element
- Fetch Accounts First: Always get compressed accounts before generating proofs - you need existing account hashes
- Batch Processing: You can generate proofs for multiple accounts in a single call. Allowed counts: 1, 2, 3, 4, or 8 accounts
- Proof Usage: The proof is required for any operation that modifies compressed accounts (transfers, burns, etc.)
- Caching: Validity proofs are only valid for the current state - don’t cache them for long periods
- Error Handling: If accounts don’t exist or have been modified, proof generation will fail
- Integration: Use
proof.compressedProofandproof.rootIndiceswhen building transactions - State Changes: After any transaction, previously generated proofs become invalid for those accounts
Dependencies & Setup
Dependencies & Setup
npm install @lightprotocol/stateless.js @solana/web3.js
- TypeScript
- cURL
- Rust
import { Rpc, createRpc, bn } from '@lightprotocol/stateless.js';
import { PublicKey } from '@solana/web3.js';
async function generateValidityProof(): Promise<void> {
const connection: Rpc = createRpc(
'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY',
'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY'
);
try {
// Get compressed accounts
const owner = new PublicKey('OWNER_PUBLIC_KEY_HERE');
const accounts = await connection.getCompressedAccountsByOwner(owner);
if (accounts.items.length === 0) {
console.log('No compressed accounts found');
return;
}
// Generate validity proof
const hashes = accounts.items.slice(0, 2).map(acc => bn(acc.hash));
const validityProof = await connection.getValidityProof(hashes);
console.log('Validity Proof Generated:');
console.log(\` Accounts: ${hashes.length}\`);
console.log(\` Roots: ${validityProof.roots.length}\`);
console.log(\` Root Indices: ${validityProof.rootIndices.length} elements\`);
console.log(\` Leaf Indices: ${validityProof.leafIndices.length} elements\`);
// Ready for use in transactions
console.log('\nProof ready for compressed transactions');
} catch (error: unknown) {
console.error('Error generating validity proof:', error);
}
}
generateValidityProof();
# Use account hashes from getCompressedAccountsByOwner
curl -X POST https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getValidityProof",
"params": {
"hashes": [
"ACCOUNT_HASH_1",
"ACCOUNT_HASH_2"
]
}
}'
Rust Client:
light-client v0.14.0 is available on crates.io. Use LightClient for the current stable API.use light_client::rpc::LightClient;
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
use anyhow::Result;
#[tokio::main]
async fn main() -> Result<()> {
let client = LightClient::new("https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY".to_string()).await?;
// Get compressed accounts first
let owner = "OWNER_PUBLIC_KEY_HERE";
let accounts = client.get_compressed_accounts_by_owner(owner).await?;
if accounts.items.is_empty() {
println!("No compressed accounts found");
return Ok(());
}
// Generate validity proof for the accounts
let hashes: Vec<String> = accounts.items.iter()
.take(2)
.map(|acc| acc.hash.clone())
.collect();
let validity_proof = client.get_validity_proof(hashes.clone()).await?;
println!("Validity Proof Generated:");
println!(" Accounts: {}", hashes.len());
println!(" Roots: {}", validity_proof.roots.len());
println!(" Root Indices: {}", validity_proof.root_indices.len());
Ok(())
}
JavaScript
import { Rpc, createRpc, bn } from '@lightprotocol/stateless.js';
import { PublicKey } from '@solana/web3.js';
async function generateProofForTransfer() {
const connection = createRpc(
'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY',
'https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY'
);
try {
// Get compressed token accounts
const owner = new PublicKey('OWNER_PUBLIC_KEY_HERE');
const mint = new PublicKey('TOKEN_MINT_HERE');
const tokenAccounts = await connection.getCompressedTokenAccountsByOwner(
owner,
{ mint }
);
if (tokenAccounts.items.length === 0) {
console.log('No compressed token accounts found');
return;
}
// Generate proof for the token accounts (limit to allowed numbers: 1, 2, 3, 4, or 8)
const limitedAccounts = tokenAccounts.items.slice(0, 4);
const hashes = limitedAccounts.map(acc => bn(acc.compressedAccount.hash));
const validityProof = await connection.getValidityProof(hashes);
console.log('Validity Proof for Transfer:');
console.log(\` Token accounts: ${hashes.length}\`);
console.log(\` Roots: ${validityProof.roots.length}\`);
// This proof is now ready to use with CompressedTokenProgram.transfer()
console.log('\nProof ready for token transfer instruction');
console.log('Use this with:');
console.log(' recentValidityProof: validityProof.compressedProof');
console.log(' recentInputStateRootIndices: validityProof.rootIndices');
} catch (error) {
console.error('Error generating proof:', error);
}
}
generateProofForTransfer();
Body
application/json
An ID to identify the request.
Available options:
test-account The version of the JSON-RPC protocol.
Available options:
2.0 The name of the method to invoke.
Available options:
getCompressedAccountBalance Request for compressed account data
Show child attributes
Show child attributes
Example:
{
"address": null,
"hash": "11111111111111111111111111111111"
}