# Migration Guide

## Overview

The latest version, v2, introduces significant architectural improvements over version v1.

### V1 Architecture

* **Prover Contracts** - Smart contracts acting as oracle interfaces (e.g., `IPublicProver`)
* **STARK Proving** - Cryptographic proofs generated using Cairo programs and Stakeware's stone prover
* **Prover Services** - Separate off-chain services that generated proofs
* **Escrow/Payment System** - Payment held in escrow until proof verification
* **Query Submission Flow** - Users submit queries → provers generate STARK proofs → proofs verified on-chain

### V2 Architecture

Significantly simplified:

* **Native Query Verification Precompile** - Direct on-chain verification at `0x0FD2`
* **No STARK Proofs Required** - Native verification without separate cryptographic proofs
* **No Prover Contracts** - Direct precompile calls, no need to deploy oracle interface contracts
* **No Escrow System** - Direct verification without payment escrow
* **Simplified Flow** - Direct verification calls with immediate results

{% hint style="info" %}
For architecture details, see [USC Architecture Overview](https://docs.creditcoin.org/usc/overview/usc-architecture-overview).
{% endhint %}

## Breaking Changes

### 1. From Prover Contracts to Native Precompile

#### V1 had:

* Prover contracts with escrow/payment system
* Off-chain STARK proof generation
* Query submission → wait → read results workflow

#### V2 has:

* Direct precompile calls to `0x0FD2`
* Native verification (no STARK proofs)
* Immediate verification results

### 2. New Precompile Addresses added

#### V1 had:

* `0x0FD1` (4049) - SubstrateTransferPrecompile
* `0x13B9` (5049) - SignatureVerifierPrecompile

#### V2 has:

* `0x0FD1` (4049) - SubstrateTransferPrecompile (unchanged)
* `0x13B9` (5049) - SignatureVerifierPrecompile (unchanged)
* `0x0FD2` (4050) - **BlockProverPrecompile** (New - Native Query Verifier)
* `0x0FD3` (4051) - **ChainInfoPrecompile** (New)

### 3. Interface Function Changes

#### V2 Functions:

* `verifyAndEmit()` - State-changing function that emits `TransactionVerified` events
* `verify()` - View function for read-only verification
* Batch verification overloads for both functions

#### New Event (optional; emitted with `verifyAndEmit()`):

```solidity
event TransactionVerified(
    uint64 indexed chainKey,
    uint64 indexed height,
    uint64 transactionIndex
);
```

{% hint style="info" %}
For more details on new interface see: [Universal Smart Contracts](https://docs.creditcoin.org/usc/dapp-builder-infrastructure/universal-smart-contracts)
{% endhint %}

## Network Endpoints

See [Quickstart](https://docs.creditcoin.org/usc/quickstart) for network endpoints and configuration.

## Smart Contract Migration

*In this section, we provide a brief overview of what all changes you will notice moving from v1 to v2.*

#### V1 Approach:

V1 uses prover contracts that store query results. The complete flow involves multiple steps across different files and toolchains:

**Step 1: Submit query with payment**

Off-chain code submits a query to the prover contract with payment (snippet from [submit\_query.ts](https://github.com/gluwa/ccnext-testnet-bridge-examples/blob/main/custom-contracts-bridging/scripts/submit_query.ts)):

```typescript
// Calculate query cost
const computedQueryCost = await ccNextPublicClient.readContract({
  address: proverContractAddress as `0x${string}`,
  abi: PROVER_ABI,
  functionName: 'computeQueryCost',
  args: [query],
});

// Submit query with payment
const { request } = await ccNextPublicClient.simulateContract({
  address: proverContractAddress as `0x${string}`,
  account: ccNextWalletClient.account,
  abi: PROVER_ABI,
  functionName: 'submitQuery',
  args: [query, ccNextWalletClient.account?.address!],
  value: computedQueryCost,
});

const txHash = await ccNextWalletClient.writeContract(request);
```

**Step 2: Wait for prover to generate STARK proof and submit**

The prover contract emits `QuerySubmitted` event. Off-chain provers listen to this event, generate STARK proofs, and submit them back to the contract. This step happens asynchronously off-chain.

**Step 3: Listen for** `QueryProofVerified` **event**

Off-chain code listens for the `QueryProofVerified` event indicating the proof was verified and results are stored (snippet from [submit\_query.ts](https://github.com/gluwa/ccnext-testnet-bridge-examples/blob/main/custom-contracts-bridging/scripts/submit_query.ts)):

```typescript
const queryProofVerified = PROVER_ABI.find(
  (abiElement) =>
    abiElement.type === 'event' && abiElement.name === 'QueryProofVerified'
);

const proverFilter = await ccNextPublicClient.createEventFilter({
  address: proverContractAddress as `0x${string}`,
  events: [queryProofVerified],
  fromBlock: BigInt(startBlock - BLOCK_LAG),
  toBlock: BigInt(currentBlock - BLOCK_LAG),
});

const proverLogs = await ccNextPublicClient.getFilterLogs({
  filter: proverFilter,
});

for (const log of proverLogs) {
  const decodedLog = decodeEventLog({
    abi: PROVER_ABI,
    data: log.data,
    topics: log.topics,
  });
  
  if (decodedLog.eventName === 'QueryProofVerified') {
    // Query proof verified, results are now available in contract storage
    console.log(`Query proving complete. QueryId: ${decodedLog.args.queryId}`);
  }
}
```

**Step 4: Read results from contract storage**

Contracts inherit from `UniversalSmartContract_Core` and read `ResultSegment[]` from prover contract storage (snippet from [MintableUSCBridge.sol](https://github.com/gluwa/CCNext-smart-contracts/blob/main/contracts/MintableUSCBridge.sol)):

```solidity
import {ICreditcoinPublicProver} from "@gluwa/creditcoin-public-prover/contracts/sol/Prover.sol";
import {UniversalSmartContract_Core} from "./UniversalSmartContract_Core.sol";

abstract contract MintableUSCBridge is UniversalSmartContract_Core, ERC20 {
    mapping(bytes32 => bool) public processedQueries;

    function mintFromQuery(
        address proverContractAddr,  // Prover contract address required
        bytes32 queryId              // Query ID from prover contract
    ) external {
        if (processedQueries[queryId]) revert QueryAlreadyProcessed();
        processedQueries[queryId] = true;

        // Read ResultSegment[] from prover contract storage
        (bytes32 functionSig, ResultSegment[] memory eventSegments) = 
            _processUSCQuery(proverContractAddr, queryId);

        // Extract data from segments (not full transaction receipts)
        if (bytes4(functionSig) != TRANSFER_EVENT_SIG) revert InvalidFunctionSignature();
        
        address from = address(uint160(uint256(bytes32(eventSegments[0].abiBytes))));
        address to = address(uint160(uint256(bytes32(eventSegments[1].abiBytes))));
        uint256 amount = uint256(bytes32(eventSegments[2].abiBytes));
        
        // Process extracted segments
        require(to == address(0), "Invalid burn address");
        _mint(from, amount);
        
        emit TokensMinted(address(this), from, amount, queryId);
    }
}
```

The `_processUSCQuery` function reads from prover contract storage (snippet from [UniversalSmartContract\_Core.sol](https://github.com/gluwa/CCNext-smart-contracts/blob/main/contracts/UniversalSmartContract_Core.sol)):

```solidity
function _processUSCQuery(
    address proverContractAddr,
    bytes32 queryId
) internal returns(bytes32 functionSignature, ResultSegment[] memory eventSegments) {
    ICreditcoinPublicProver prover = ICreditcoinPublicProver(proverContractAddr);
    QueryDetails memory queryDetails = prover.getQueryDetails(queryId);
    ResultSegment[] memory resultSegments = queryDetails.resultSegments;
    
    // Extract function signature and event segments from stored results
    require(resultSegments.length >= 8, "Invalid result length");
    functionSignature = resultSegments[4].abiBytes;
    
    uint256 resultLength = resultSegments.length;
    eventSegments = new ResultSegment[](resultLength - 5);
    for (uint256 i = 5; i < resultLength;) {
        eventSegments[i] = resultSegments[i];
        unchecked { ++i; }
    }
}
```

#### V2 Approach:

In v2 this process is really simplified by direct precompile calls with full transaction data (snippet from [SimpleMinterUSC.sol](https://github.com/gluwa/usc-testnet-bridge-examples/blob/main/contracts/sol/SimpleMinterUSC.sol)):

```solidity
import {EvmV1Decoder} from "./EvmV1Decoder.sol";
contract SimpleMinterUSC is ERC20 {
    INativeQueryVerifier public immutable VERIFIER = 
        INativeQueryVerifier(0x0000000000000000000000000000000000000FD2);
    
    mapping(bytes32 => bool) public processedQueries;

    function mintFromQuery(
        uint64 chainKey,
        uint64 blockHeight,
        bytes calldata encodedTransaction,
        bytes32 merkleRoot,
        INativeQueryVerifier.MerkleProofEntry[] calldata siblings,
        bytes32 lowerEndpointDigest,
        bytes32[] calldata continuityRoots
    ) external returns (bool) {
        //Calculate transaction index from merkle proof
        uint256 transactionIndex = _calculateTransactionIndex(siblings);
        bytes32 txKey = keccak256(abi.encodePacked(chainKey, blockHeight, transactionIndex));
        require(!processedQueries[txKey], "Query already processed");

        //Build proofs and verify directly
        INativeQueryVerifier.MerkleProof memory merkleProof =
            INativeQueryVerifier.MerkleProof({root: merkleRoot, siblings: siblings});
        INativeQueryVerifier.ContinuityProof memory continuityProof =
            INativeQueryVerifier.ContinuityProof({
                lowerEndpointDigest: lowerEndpointDigest,
                roots: continuityRoots
            });
        
        bool verified = VERIFIER.verifyAndEmit(
            chainKey, blockHeight, encodedTransaction, merkleProof, continuityProof
        );
        require(verified, "Verification failed");
        
        processedQueries[txKey] = true;

        //Decode and validate receipt status (full transaction receipt available)
        EvmV1Decoder.ReceiptFields memory receipt = 
            EvmV1Decoder.decodeReceiptFields(encodedTransaction);
        require(receipt.receiptStatus == 1, "Transaction did not succeed");

        //Extract Transfer events from full receipt logs
        bytes32 TRANSFER_EVENT_SIGNATURE = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
        EvmV1Decoder.LogEntry[] memory transferLogs =
            EvmV1Decoder.getLogsByEventSignature(receipt, TRANSFER_EVENT_SIGNATURE);
        require(transferLogs.length > 0, "No transfer events found");

        //Process transfer events from full receipt
        EvmV1Decoder.CommonTxFields memory txFields = 
            EvmV1Decoder.decodeCommonTxFields(encodedTransaction);
        bool found = _processTransferLogs(transferLogs, txFields.from);
        require(found, "No valid burn transfer found");

        _mint(msg.sender, MINT_AMOUNT);
        emit TokensMinted(address(this), msg.sender, MINT_AMOUNT, txKey);
        
        return true;
    }
}
```

## Code Migration Examples

To better understand how to use version v2, see [Universal Smart Contracts](https://docs.creditcoin.org/usc/dapp-builder-infrastructure/universal-smart-contracts) for in-depth explanation. Details example contracts are also available in the [examples](https://github.com/gluwa/usc-testnet-bridge-examples/tree/main) repository.

## Troubleshooting

### Common Issues

**1. "Proof not found" / 404 from Proof API Server**

**Problem:** Proof generation API server cannot find the transaction or block

**Solutions:**

* Check if proof server is running via: `/api/v1/health`
* Verify transaction hash/index is correct
* Check if block height exists on source chain and attestation block has been created

**2. "Attestation not found" / "Block not ready"**

**Problem:** Block hasn't been attested yet by the attestor network

**Solutions:**

* Wait for attestors to attest the block
* Verify attestor network is running and check attestation status on Creditcoin node

**3. "Attestations missing for chain {chain\_key}"**

**Problem:** No attestations exist for the specified `chainKey`

**Solutions:**

* Verify attestor network is running and configured for this chain
* Check if attestations exist on-chain and verify `chainKey` matches source chain configuration

**4. "Proof server down" / Connection refused**

**Problem:** Proof generation API server is not running or unreachable

**Solutions:**

* Check if proof server is running: `curl <http://localhost:3100/api/v1/health`>
* Check server logs and verify RPC connections for issues

**5. "No attestation or checkpoint found after block {height}"**

**Problem:** Continuity proof requires an attestation/checkpoint after the query block, but it doesn't exist yet

**Solutions:**

* Wait for next attestation to be created (check attestation interval, e.g., every 10 blocks)

**6. "Transaction hash not found"**

**Problem:** Proof API cannot find transaction by hash

**Solutions:**

* Try using block height and transaction index instead: `/api/v1/proof/{chainKey}/{height}/{txIndex}`

**7. "Query height out of range"**

**Problem:** Requested block height is invalid

**Solutions:**

* Verify height is between lower and upper attestation bounds (not at or outside them)
* Check if query height matches an attestation/checkpoint block (may need to adjust)
* Ensure attestations exist for the chain and query height is within attested range

**8. "Proof generation server returns 500 error for transactions"**

Problem: Proof generation API server returns HTTP 500 (Internal Server Error) when requesting proofs

Solutions:

* Check proof server logs for specific error details (database errors, RPC failures, Merkle proof generation issues)
* Verify the transaction block has been attested (see issue #2 above)
* Ensure source chain RPC is accessible and responding correctly
* Check database connectivity if proof server uses caching
* Verify the transaction exists on the source chain and is finalized
* Wait for attestation before requesting proof (see Worker Flow below)

**9. Worker Flow: Understanding `targetHeight`**

Problem: Confusion about when workers should request proofs and what height to wait for

Clarification:

* `targetHeight` is *not* the current source chain head
* `targetHeight = transactionBlockNumber + 1`
* Workers should wait until `latestAttestation >= targetHeight`
* This guarantees the transaction block itself is attested before proof generation

This matters because if a transaction is in block N, waiting for block N+1 to be attested ensures block N is attested. Requesting proofs before the block is attested will result in "Block not ready" or 500 errors. This is related to issue #8 above - premature proof requests cause server errors

**10. "Gas estimation failed: execution reverted"**

Problem: Gas estimation fails because it simulates the transaction execution, and during simulation the precompile may revert. This is a known issue with `pallet-evm`. Note that the gas estimation failure *does not* necessarily mean the transaction will fail.

Workaroun&#x64;**:** Catch gas estimation failures and calculate gas based on continuity proof size instead:

```typescript
async function computeGasLimit(
  provider: JsonRpcApiProvider,
  contract: Contract,
  data: string,
  from: string,
  continuityLength: number
): Promise<bigint> {
  console.log('⏳ Estimating gas...');

  let gasLimit;
  try {
    const estimatedGas = await provider.estimateGas({
      to: contract.getAddress(),
      data,
      from,
    });
    gasLimit = (estimatedGas * BigInt(135)) / BigInt(100); // Add 35% buffer
    console.log(`   Estimated gas: ${estimatedGas.toString()}, Gas limit with buffer: ${gasLimit.toString()}`);
  } catch (error: any) {
     // Calculate a reasonable estimate based on continuity proof size (matching Rust logic)
    // Base: 21000 (tx) + ~5000 per continuity block + ~20000 for merkle + overhead
    const calculatedGas = 21000 + continuityLength * 5000 + 20000;
    console.warn(`   Gas estimation failed: ${error.shortMessage || error.message}`);
    console.log(
      `   Using calculated gas limit based on proof size: ${calculatedGas} (${continuityLength} continuity blocks)`
    );
    gasLimit = BigInt(calculatedGas);
  }

  return gasLimit;
}
```

This workaround is already implemented in the `computeGasLimit()` function in the [examples repository.](https://github.com/gluwa/usc-testnet-bridge-examples/blob/main/utils/index.ts)

### Debugging Tips

* Check proof server logs for detailed error messages
* Verify all services are running: Creditcoin node, attestor network, proof server, database
* Use proof API health endpoint: `/api/v1/health`
* Check attestation status via Creditcoin RPC or `Polkadot.js`
* Enable verbose logging in proof server for more details

## Support

If you encounter issues during migration:

1. Check the troubleshooting section above
2. Read the docs carefully once, especially the[ DApp builder section](https://docs.creditcoin.org/usc/dapp-builder-infrastructure)&#x20;
3. Checkout the example code in [examples repository](https://github.com/gluwa/usc-testnet-bridge-examples)
4. Check the Creditcoin repositories and public channels for latest updates
