Universal Smart Contracts
What is the Universal Smart Contract
Universal Smart Contracts (USC) act as an adapter which extends existing smart contracts with crosschain capabilities, allowing contracts to query, retrieve, and verify data from external blockchains via the Creditcoin Decentralized Oracle.
Unlike traditional omnichain or crosschain solutions that focus narrowly on token transfers or specific assets, USC provides a general-purpose execution layer. This enables contracts to act on externally verified data without needing to rewrite core logic. By adopting USC into their tech stack, developers can transform their contracts into universal components powered by seamless crosschain data, allowing for novel patterns of interoperability across multiple blockchains.
Universal Smart Contract (USC) Components
Creditcoin USC capabilities consist of two main components:
The core USC contract, which allows users and other contracts alike to query and retrieve verified cross-chain data in a trust-minimized and protocol-agnostic way.
USC Extension contracts, which are contracts built and deployed by builders with their own custom logic. These are built as extensions to the core contract, adapting its capabilities to specific use cases. They make it easy for builders to integrate USC functionality seamlessly into a wide range of decentralized applications.
Core USC Contract
The Core Universal Smart Contract includes functions to read data from prover contract after it has been checked by the verifier contract. The core functions of USC Contract are as follows:
function isQueryUsed(
address user,
bytes32 queryId
) public view returns (bool) {
// ...
}
type: Public
Checks if a given queryId
from a specific user has already been processed. Duplicate queries will be rejected to avoid replay attacks.
function _markQueryUsed(address user, bytes32 queryId) internal {
// ...
}
type: Internal
Marks a queryId
as used for a given user. This ensures that once a query has been processed, it cannot be executed again.
function _processUSCQuery(
address proverContractAddr,
bytes32 queryId
) internal returns(
bytes32 functionSignature,
ResultSegment[] memory eventSegments
) {
// ...
}
type: Internal
Core function responsible for processing queries:
Validates that the
queryId
has not been used before.Retrieves query details from the Prover contract.
Extracts the function signature and event data (
ResultSegment[]
).Prepares the parsed data for further handling by the implementation contract.
Calls
_onQueryValidated()
hook for the implementation contract's custom logic.
function _onQueryValidated(
bytes32 queryId,
bytes32 functionSignature,
ResultSegment[] memory eventSegments
) internal virtual;
A hook function that implementation contracts can use to include their own business logic. This is where application-specific logic is applied to the validated query results (e.g., executing transactions, updating state, or triggering events).
Contract Code
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
import "@gluwa/creditcoin-public-prover/contracts/sol/Types.sol";
import {ICreditcoinPublicProver} from "@gluwa/creditcoin-public-prover/contracts/sol/Prover.sol";
abstract contract UniversalSmartContract_Core {
bytes32 private constant USCStorageLocation =
0x6873a84df95308b16f5c8aa284ac06f406edc8315b9626bdacc9b42f5ee1d200;
struct USCStorage {
mapping(address => mapping(bytes32 => bool)) usedQueryId;
}
function _getUSCStorage() internal pure returns (USCStorage storage $) {
assembly {
$.slot := USCStorageLocation
}
}
function isQueryUsed(
address user,
bytes32 queryId
) public view returns (bool) {
return _getUSCStorage().usedQueryId[user][queryId];
}
function _markQueryUsed(address user, bytes32 queryId) internal {
_getUSCStorage().usedQueryId[user][queryId] = true;
}
/// @notice Processes a query from the prover and extracts data, deferring
/// action to child contract
function _processUSCQuery(
address proverContractAddr,
bytes32 queryId
) internal returns(
bytes32 functionSignature,
ResultSegment[] memory eventSegments
) {
USCStorage storage $ = _getUSCStorage();
require(
!$.usedQueryId[proverContractAddr][queryId],
"QueryId already used"
);
ICreditcoinPublicProver prover = ICreditcoinPublicProver(
proverContractAddr
);
QueryDetails memory queryDetails = prover.getQueryDetails(queryId);
ResultSegment[] memory resultSegments = queryDetails.resultSegments;
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;
}
}
// Hook validation logic for implementation contract to use
_onQueryValidated(queryId, functionSignature, eventSegments);
}
/// @dev Must be implemented by child contract to handle validated data
function _onQueryValidated(
bytes32 queryId,
bytes32 functionSignature,
ResultSegment[] memory eventSegments
) internal virtual;
}
Query Processing Flow
When a contract inherits from the Universal Smart Contract (USC
), either directly through the Core contract or via USC
extension contracts, it gains the ability to query and retrieve verified data from other blockchains. This data is returned in a standardized format as ResultSegment[]
. The contract can then use these Result Segments to decide what actions to take on the current chain (destination chain) based on its own business logic.

Result Segments
A ResultSegment
is a unit of ABI-encoded data representing a specific piece of information returned by a USC
query. Result Segments are stored collectively in an array that describes in a structured way the full transaction or event the query is targeting.
Key characteristics:
ABI-encoded: each segment contains a raw
bytes32
that encodes a Solidity type (e.g.address
,uint256
,bool
). Users can convert thatbytes32
back to the target data type to retrieve its value.Single value: each segment encodes a single piece of data (e.g., the sender address, receiver address, token amount, or event signature) and not an entire transaction or event.
Sequence of related data: result segments are stored in an array where their position in the array determines the meaning of their data.
ℹ️ A
ResultSegment[]
can have different length depending on the event or transaction which is being queried by theUSC
.
Deterministic: for each
ResultSegments[]
array, there is always an event signature (before event data), which will be used by the implementation contract to determine correct actions when consuming theResultSegments[]
Example Structure
The following are the Result Segments for a query which returns an ERC-20
burn transaction event (transfer to address(0)
):
0x0000000000000000000000000000000000000000000000000000000000000001
000000000000000000000000016e7bfe4a7213e18516ca0cb84cf2750d360b33
0000000000000000000000008928f05a197215ad7fffec1f0e4e9159e8c4c403
0000000000000000000000008928f05a197215ad7fffec1f0e4e9159e8c4c403
ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
000000000000000000000000016e7bfe4a7213e18516ca0cb84cf2750d360b33
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000032
They are interpreted by the Prover contract as follows:
resultSegments[0]
0x0000000000000000000000000000000000000000000000000000000000000001
Original transaction status.
resultSegments[1]
0x000000000000000000000000016e7bfe4a7213e18516ca0cb84cf2750d360b33
Sender address.
resultSegments[2]
0x0000000000000000000000008928f05a197215ad7fffec1f0e4e9159e8c4c403
Target contract address.
resultSegments[3]
0x0000000000000000000000008928f05a197215ad7fffec1f0e4e9159e8c4c403
Final contract address (which emit the event). Note: the final contract address and the target contract address can be different when the target contract interacts with other contract(s) to execute the transaction.
resultSegments[4]
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
The event signature which will be used by the implementation contract to determine action. In this case it is Transfer(address,address,address).
resultSegments[5]
0x000000000000000000000000016e7bfe4a7213e18516ca0cb84cf2750d360b33
The from
address in the transfer event.
resultSegments[6]
0x0000000000000000000000000000000000000000000000000000000000000000
The to
address in the transfer event.
resultSegments[7]
0x0000000000000000000000000000000000000000000000000000000000000032
The amount
in the transfer event.
USC Extension Contracts
To ensure USC
can be easily integrated into existing contracts, we provide a suite of extension contracts built on top of the USC Core. These extensions adapt USC
’s capabilities to various real-world use cases, allowing builders to seamlessly embed cross-chain data into their logic. Common use cases include, but are not limited to:
Burn-and-Mint: Mint a new token on one chain by reading a burn event from another, enabling cross-chain asset transfers.
Lock-and-Mint: Mint a token on a destination chain based on a lock event on the source chain.
Cross-Chain Swap: Swap token A for token B across different blockchains using verified event data.
Multi-Chain Loan Aggregation: Aggregate repayment data across chains for credit scoring and underwriting.
ℹ️ builders can also work directly with the Core USC contract if they wish to fully customize behaviors and integrate USC capabilities more tightly into their own business logic.
Architecture

Example
MintableUSCBridge
is an extension of a USC
Core contract that can be used by other ERC-20
token to enable the crosschain movement of funds:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
import "@gluwa/creditcoin-public-prover/contracts/sol/Types.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {UniversalSmartContract_Core} from "./UniversalSmartContract_Core.sol";
abstract contract MintableUSCBridge is UniversalSmartContract_Core, ERC20 {
event TokensMinted(
address indexed token,
address indexed recipient,
uint256 amount,
bytes32 indexed queryId
);
error InvalidFunctionSignature();
error InvalidSegmentLength();
error ZeroAmount();
error InvalidBurnAddress();
error InvalidRecipient();
error QueryAlreadyProcessed();
bytes4 private constant TRANSFER_EVENT_SIG = 0xddf252ad;
mapping(bytes32 => bool) public processedQueries;
/// @notice Executes USC query processing and mints based on extracted
/// Transfer event
function mintFromQuery(
address proverContractAddr,
bytes32 queryId
) external {
if (processedQueries[queryId]) revert QueryAlreadyProcessed();
processedQueries[queryId] = true;
// Get function signature and event data
(
bytes32 functionSig,
ResultSegment[] memory eventSegments
) = _processUSCQuery(proverContractAddr, queryId);
if (bytes4(functionSig) != TRANSFER_EVENT_SIG)
revert InvalidFunctionSignature();
if (eventSegments.length < 3) revert InvalidSegmentLength();
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));
if (amount == 0) revert ZeroAmount();
if (to != address(0)) revert InvalidBurnAddress();
if (from == address(0)) revert InvalidRecipient();
// Mint tokens on destination chain
_mint(from, amount);
emit TokensMinted(address(this), from, amount, queryId);
}
function _toAddress(bytes memory data) internal pure returns (address) {
return abi.decode(data, (address));
}
function _toUint256(bytes memory data) internal pure returns (uint256) {
return abi.decode(data, (uint256));
}
function _onQueryValidated(
bytes32,
bytes32,
ResultSegment[] memory
) internal virtual override {
// More function below...
}
// More function below...
}
Builders can then inherit from MintableUSCBridge
in their contracts to leverage the full capabilities of USC
.
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./MintableUSCBridge.sol";
contract YourToken is ERC20, MintableUSCBridge {
constructor(
string memory name_,
string memory symbol_
) ERC20(name_, symbol_) {
// Your constructor business logic will be here...
}
function _onQueryValidated(
bytes32,
bytes32,
ResultSegment[] memory
) internal override {
// Your business logic will be here...
}
// More logic below...
}
Next Steps
Check out this tutorial for an example of how to use the Creditcoin stack to set up a decentralized trustless bridge.
Last updated