vlayer docs
Examples

Binance Data on Ethereum

Under Development: This API is currently in development. Documentation and endpoints may change. For production use, please contact our team.

Binance Data on Ethereum

This example demonstrates how to put Binance API data on-chain using ZK proofs. The goal is to create a verifiable, on-chain record of Binance exchange data with cryptographic guarantees that prevent data manipulation attacks.

Overview

The workflow consists of three main steps:

  1. Generate Web Proof - Create a cryptographic proof of Binance API data
  2. Generate ZK Proof - Convert the Web Proof into a succinct ZK proof with extracted price data
  3. Deploy Smart Contract - Create a secure on-chain contract that verifies and stores the price data

Step 1: Generate Web Proof

First, we need a Web Proof of Binance API data. This step is detailed in our Binance Web Proof example. The Web Proof contains the complete HTTP request/response data from the Binance API.

curl -X POST https://web-prover.vlayer.xyz/api/v1/prove \
  -H "Content-Type: application/json" \
  -H "x-client-id: 4f028e97-b7c7-4a81-ade2-6b1a2917380c" \
  -H "Authorization: Bearer jUWXi1pVUoTHgc7MOgh5X0zMR12MHtAhtjVgMc2DM3B3Uc8WEGQAEix83VwZ" \
  -d '{
    "url": "https://data-api.binance.vision/api/v3/ticker/price?symbol=ETHUSDC",
    "headers": []
  }'

Response:

{
  "data": "014000000000000000ee32d73a6a70e406a31ffa683416b7376...",
  "version": "0.1.0-alpha.12",
  "meta": {
    "notaryUrl": "https://test-notary.vlayer.xyz/v0.1.0-alpha.12"
  }
}

This Web Proof contains the ETH/USDC price data from Binance.

Step 2: Generate ZK Proof

Next, we use the ZK Prover Server to convert the Web Proof into a ZK proof, extracting the specific price data we want to put on-chain. Pass the entire Web Proof object from Step 1 as the presentation parameter:

curl -X POST https://zk-prover.vlayer.xyz/api/v0/compress-web-proof \
  -H "Content-Type: application/json" \
  -d '{
    "presentation": {
      "data": "014000000000000000ee32d73a6a70e406a31ffa683416b7376...",
      "version": "0.1.0-alpha.12",
      "meta": {
        "notaryUrl": "https://test-notary.vlayer.xyz/v0.1.0-alpha.12"
      }
    },
    "extraction": {
      "response.body": {
        "jmespath": ["price", "symbol"]
      }
    }
  }'

Response:

{
  "success": true,
  "data": {
    "zkProof": "0xffffffffa1b2c3d4e5f6...",
    "journalDataAbi": "0xa7e62d7f17aa7a22c26bdb93b7ce9400e826ffb2c6f54e54d2ded015677499af..."
  }
}

The journalDataAbi contains ABI-encoded public outputs that will be decoded in your smart contract. This includes the notary key fingerprint, HTTP method, URL, TLS timestamp, extraction hash, and your extracted values (price and symbol).

Fetch decode helpers via a separate call:

curl -X POST https://zk-prover.vlayer.xyz/api/v0/debug/journal-decode-helper \
  -H "Content-Type: application/json" \
  -d '{
    "presentation": {
      "data": "014000000000000000ee32d73a6a70e406a31ffa683416b7376...",
      "version": "0.1.0-alpha.12",
      "meta": {
        "notaryUrl": "https://test-notary.vlayer.xyz/v0.1.0-alpha.12"
      }
    },
    "extraction": {
      "response.body": {
        "jmespath": ["price", "symbol"]
      }
    }
  }'

This returns journalTypes, journalValues, and a solidityCodeSnippet to decode journalDataAbi.

Step 3: Smart Contract Implementation

Now we'll create a secure Solidity smart contract that verifies the ZK proof and stores the price data on-chain. This contract implements critical security features:

  1. Notary Key Fingerprint Validation - Ensures the proof comes from a trusted notary
  2. Extraction Hash Validation - Ensures the correct fields were extracted (prevents field substitution attacks)
  3. ZK Proof Verification - Cryptographically proves the extraction was correct
  4. Timestamp Verification - Uses the notarized timestamp from the Web Proof, not block time
  5. ABI Decoding Pattern - Uses abi.decode to extract structured data from the journalDataAbi (RISC Zero's journal format)

This contract uses the RISC Zero Verifier Router for on-chain verification.

import {IRiscZeroVerifier} from "risc0/contracts/IRiscZeroVerifier.sol";

contract BinancePriceOracle {
    IRiscZeroVerifier public immutable verifier;
    
    /// @notice RISC Zero guest program ID for ZK proof verification
    /// @dev Get the latest guest ID from /api/v0/guest-id endpoint: https://zk-prover.vlayer.xyz/api/v0/guest-id
    bytes32 public constant IMAGE_ID = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef;
    
    bytes32 public immutable expectedNotaryKeyFingerprint;
    
    /// @notice Expected extraction hash - validates correct fields are extracted
    /// @dev Computed from: keccak256(abi.encodePacked("response.body", "jmespath", "price", "response.body", "jmespath", "symbol"))
    bytes32 public immutable expectedExtractionHash;
    
    string public immutable expectedUrl;
    
    struct PriceData {
        string symbol;
        string price;
        uint256 timestamp;
        uint256 blockNumber;
    }
    
    mapping(string => PriceData) public prices;
    
    constructor(
        address _verifier,
        bytes32 _expectedNotaryKeyFingerprint,
        bytes32 _expectedExtractionHash,
        string memory _expectedUrl
    ) {
        verifier = IRiscZeroVerifier(_verifier);
        expectedNotaryKeyFingerprint = _expectedNotaryKeyFingerprint;
        expectedExtractionHash = _expectedExtractionHash;
        expectedUrl = _expectedUrl;
    }
    
    function updatePrice(
        bytes calldata journalData,
        bytes calldata seal
    ) external {
        // Decode journalDataAbi - must match the order from the ZK prover
        (
            bytes32 notaryKeyFingerprint,
            string memory method,
            string memory url,
            uint256 tlsTimestamp,
            bytes32 extractionHash,
            string memory price,
            string memory symbol
        ) = abi.decode(
            journalData,
            (bytes32, string, string, uint256, bytes32, string, string)
        );
        
        if (notaryKeyFingerprint != expectedNotaryKeyFingerprint) {
            revert InvalidNotaryKeyFingerprint();
        }
        
        if (extractionHash != expectedExtractionHash) {
            revert InvalidExtractionHash();
        }
        
        if (keccak256(bytes(url)) != keccak256(bytes(expectedUrl))) {
            revert InvalidUrl();
        }
        
        // Verify the ZK proof using RISC Zero verifier
        try verifier.verify(seal, IMAGE_ID, sha256(journalData)) {
            // Proof verified successfully
        } catch {
            revert ZKProofVerificationFailed();
        }
        
        // Store the verified price data
        prices[symbol] = PriceData({
            symbol: symbol,
            price: price,
            timestamp: tlsTimestamp,
            blockNumber: block.number
        });
    }

    function getPrice(string memory symbol) 
        external 
        view 
        returns (PriceData memory) 
    {
        return prices[symbol];
    }
}

Step 4: Deploy and Interact with the Contract

Here's how to deploy the contract and submit the ZK proof using viem:

import { createWalletClient, createPublicClient, http, decodeAbiParameters, parseAbiParameters } from 'viem';
import { sepolia } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount('0x...'); // Your private key

const walletClient = createWalletClient({
  account,
  chain: sepolia,
  transport: http()
});

const publicClient = createPublicClient({
  chain: sepolia,
  transport: http()
});

async function deployContract() {
  // RISC Zero Verifier Router address (example for Sepolia testnet)
  const verifierAddress = '0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187';
  
  // Expected notary key fingerprint from your trusted notary
  const expectedNotaryKeyFingerprint = '0xb593a6f7ea2ec684e47589b1a4dfe3490a0000000000000000000000010000000000000027';
  
  // Expected extraction hash from your extraction configuration
  // This should match: keccak256(abi.encodePacked("response.body", "jmespath", "price", "response.body", "jmespath", "symbol"))
  const expectedExtractionHash = '0xabc123def456789012345678901234567890123456789012345678901234abcd';
  
  // The URL we expect to validate against
  const expectedUrl = 'https://data-api.binance.vision/api/v3/ticker/price?symbol=ETHUSDC';
  
  const hash = await walletClient.deployContract({
    abi: CONTRACT_ABI,
    bytecode: BYTECODE,
    args: [verifierAddress, expectedNotaryKeyFingerprint, expectedExtractionHash, expectedUrl]
  });
  
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  console.log('Contract deployed at:', receipt.contractAddress);
  
  return receipt.contractAddress!;
}

async function updatePrice(contractAddress: `0x${string}`, zkProofResponse: any) {
  const { zkProof, journalDataAbi } = zkProofResponse.data;
  

  const hash = await walletClient.writeContract({
    address: contractAddress,
    abi: CONTRACT_ABI,
    functionName: 'updatePrice',
    args: [journalDataAbi, zkProof]
  });
  
  await publicClient.waitForTransactionReceipt({ hash });
  console.log('Price updated successfully!');
  
  const priceData = await publicClient.readContract({
    address: contractAddress,
    abi: CONTRACT_ABI,
    functionName: 'getPrice',
    args: [symbol]
  }) as any;
  
  console.log('Stored price data:', {
    symbol: priceData.symbol,
    price: priceData.price,
    timestamp: priceData.timestamp.toString(),
    blockNumber: priceData.blockNumber.toString()
  });
}

// Example usage with the response from Step 2
const zkProofResponse = {
  success: true,
  data: {
    zkProof: '0xffffffffa1b2c3d4e5f6...',
    journalDataAbi: '0xa7e62d7f17aa7a22c26bdb93b7ce9400e826ffb2c6f54e54d2ded015677499af...'
  }
};

// Deploy and use
const contractAddress = await deployContract();
await updatePrice(contractAddress, zkProofResponse);