# 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](/usc/overview/usc-architecture-overview.md).
{% 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](/usc/dapp-builder-infrastructure/universal-smart-contracts.md)
{% endhint %}

## Network Endpoints

See [Quickstart](/usc/quickstart/environments.md) 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](/usc/dapp-builder-infrastructure/universal-smart-contracts.md) 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](/usc/dapp-builder-infrastructure/infrastructure-overview.md)&#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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.creditcoin.org/usc/usc-v1/migration-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
