@credora/on-chain-metrics-test v0.0.36
Credora On-Chain Metrics
Through the on-chain distribution of credit metrics, Credora enables DeFi applications to modify lending and borrowing parameters on the basis of a borrower’s credit information. This empowers borrowers to leverage their Credora credit assessment while engaging an expanding network of DeFi applications. Through on-chain credit metrics, Credora aims to further streamline the lending process, improve operational efficiency, and ensure transparency through the immutability of blockchain data.
This ReadMe presents the Credora Smart Contract describing the ways credit metrics can be relayed/retrieved to/from the blockchain.
Smart Contract Features
- Credit Metrics Storage: On-chain storage of credit metrics associated with Credora clients.
- Synchronous and Event-based Data Retrieval: Supports data retrieval using synchronous function calls or via event-based callbacks.
- Permissioned Data Access: Controls who can access credit metrics based on roles and specific permissions.
- ERC-20 Payment Compatibility: Allows payments with ERC-20 tokens for granting access to credit metrics or refreshing on-chain data.
- Integration with Chainlink: Uses Chainlink to process requests that require off-chain computation.
- Integration with Ethereum Attestation Service (EAS): Uses EAS to anchor credit metrics to an on-chain attestation and retrieve them.
Setup
Install dependencies by running:
npm install @credora/on-chain-metrics
Set the following environment variables either via export
:
export ETHEREUM_SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/{ALCHEMY_APIKEY}
export PRIVATE_KEY=the wallet private key
or via a secure tool provided by Chainlink npx env-enc
by first running:
npx env-enc set-pw
and then set the ENVs using npx env-enc set
.
Relaying Data to the Credora Smart Contract
Credora holds its data off-chain in a decentralized data warehouse, namely Space&Time. This is then leveraged to relay data to the blockchain using three possible approaches:
- Chainlink Functions
- Direct RPC raw requests
- Ethereum Attestation Service (EAS)
Chainlink Functions
Oracles such as Chainlink ensure reliable data feed thanks to a deep decentralized architecture. Such an architecture also guarantees high reliability of data sources. It is widely adopted in the DeFi ecosystem. Oracles provide answers to the following problems:
- How do we verify that the injected information was extracted from the correct source or hasn’t been tampered with?
- How do we ensure that this data is always available and updated regularly? The smart contract is designed to interact with Chainlink nodes using the Decentralized Oracle Network (DON). It facilitates the secure and verifiable execution of off-chain computation tasks and retrieves the results for on-chain use. The smart contract leverages Chainlink Functions to get live metric data from the Space&Time decentralized data warehouse.
The file query-space-and-time.js
contains the Chainlink Function code and Functions-request-config.js
the related configuration.
A tutorial on how to kick off Chainlink Function requests using HardHat is available here
Direct RPC Raw Requests
Direct RPC Raw requests are cheaper and allow to directly interact with blockchain nodes giving us full control over the data being pushed on-chain. In this case, there are some cons in terms of centralization and concerns in terms of security in particular with regard to the node endpoints and possible risks on vulnerabilities at the endpoint level. If the blockchain node receiving the RPC request is malicious, the data could get tampered.
To this end, the Credora Smart Contract exposes the setData
external function.
A usage example is in tasks/Setters/setData.js
, whose command is reported below:
npx hardhat setData --contract 0xEc97819AB80Aa06812B9C699403048BBc1826076 --entity credoraInc --score 64.65 --rae BB- --impliedpd 60 --nav 66 --network ethereumSepolia
Ethereum Attestation Service (EAS)
Alternatively, the last supported approach is based on the usage of the EAS attestation and retrieve the data from the attestation contract itself. In this case, we have the advantage of anchoring the data to a standardized attestation schema, which can be verified by third parties. Attestations provide a form of proof that can be independently verified, enhancing trust in the data. In terms of costs, the EAS solution lies in the middle between the Oracle and the Direct ones.
To do so:
Create an EAS Attestation Schema
Make an on-chain attestation and get the related attestation UID:
const eas = new EAS(easContractAddress);
const provider = ethers.getDefaultProvider(
"sepolia"
);
const signer = new ethers.Wallet(privateKey, provider);
eas.connect(signer);
// Initialize SchemaEncoder with the schema string
const schemaEncoder = new SchemaEncoder("bytes32 entity, uint256 score, uint256 nav, bytes8 rae, uint256 borrowCapacity, uint256 impliedPD, uint256 impliedPDTenor, string proofCommitment");
const encodedData = schemaEncoder.encodeData([
{ name: "entity", value: ethers.encodeBytes32String("credora-test"), type: "bytes32" },
{ name: "score", value: 39500000, type: "uint256" },
{ name: "nav", value: 0, type: "uint256" },
{ name: "rae", value: stringToBytes8("BB-"), type: "bytes8" },
{ name: "borrowCapacity", value: 2240000, type: "uint256" },
{ name: "impliedPD", value: 0, type: "uint256" },
{ name: "impliedPDTenor", value: 0, type: "uint256" },
{ name: "proofCommitment", value: "abcd", type: "string" },
]);
const tx = await eas.attest({
schema: schemaUID,
data: {
recipient: "0x98ADc891Efc9Ce18cA4A63fb0DfbC2864566b5Ab",
expirationTime: 0,
revocable: true, // Be aware that if your schema is not revocable, this MUST be false
data: encodedData,
},
});
- Refresh data in the Credora Smart Contract using the task under
tasks/EAS/refreshData.js
by passing in input the attestation UID and the contract address. The command is reported below:
npx hardhat refreshData --contract 0xf5c5b572502374b2e48f8ED3B39062Cb5F7f2A36 --uid 0xcf2e6171372e61e5dc294732ddf2e0b321524352da95e7deba025984a1b2abe9 --network ethereumSepolia
Retrieving Data from the Credora Smart Contract
Credora provides support for accessing credit data using direct and synchronous function calls or via callbacks that will be invoked by the related initiator function if metrics change over time.
Polling-based Data Retrieval
Credora allows to retrieve credit metrics stored on-chain by polling the contract and the related getter functions.
To do so, it is mandatory to be granted access permissions. Data consumers should share their wallet address with Credora, thus being able to read the stored data.
An example on how to get a credit metric is reported under tasks/Getters/getScore.js
.
npx hardhat getScore --contract 0xEc97819AB80Aa06812B9C699403048BBc1826076 --entity credoraInc --network ethereumSepolia
Event-based Data Retrieval
Credora provides a robust mechanism for retrieving data using a callback-based solution. This feature allows external entities to request data and receive the results asynchronously through a callback function.
The consuming smart contract should implement the desired callback belonging to the CredoraFunctions
abstract contract.
Callbacks will be called if a valid subscription is active and if credit metrics change.
Callbacks will be triggered only if the data consumer (e.g., a consumer contract) has been subscribed by Credora.
fulfillScore(bytes32 _entity, uint256 metric_data)
fulfillNAV(bytes32 _entity, uint256 metric_data)
fulfillRAE(bytes32 _entity, bytes8 metric_data)
fulfillBorrowCapacity(bytes32 _entity, uint256 metric_data)
fulfillImpliedPD(bytes32 _entity, uint256 metric_data)
fulfillImpliedPDtenor(bytes32 _entity, uint256 metric_data)
Available Credit Metrics
The credit metrics we hold in the smart contract are the following:
- Credit Score: A score out of 1000, granularly differentiating the creditworthiness of borrowers. Credit scores are outputs for specific methodologies
- RAE: A Rating Agency Equivalent is delivered as a Credit Assessment Metric, providing a way to compare Credora outputs to the big 3 US Credit Rating Agencies.
- Borrow Capacity: The output of a scenario analysis, considering the impact of additional unsecured debt in the context of the strategy, current leverage, and historical leverage.
- Implied PD: Implied probability of default for the borrower over a specified tenor. This is derived using the Rating Agency Equivalent and corresponding historical default rates.
- Implied PD Tenor: The tenor over which the borrower's Implied PD has been calculated, which is anchored to the average duration of their outstanding debt.
- Total NAV: NAV is typically calculated for investment funds. The formula is ((Total Assets - Total) / Outstanding Shares). Total NAV delivers the numerator for this equation.
Supported Networks and Addresses
The Credora Smart Contract is deployed and verified on ETHEREUM and SEPOLIA networks. It will also be available on other blockchains in the near future.
Usage Example
In the following, we report a usage example of the CredoraMetrics contract, showing a JavaScript sample code based on the HardHat development environment.
Prerequisites
Must have an Ethereum or Sepolia wallet. You can fund your Sepolia wallet here. Sufficient ETH for transaction fees. Node.js and npm installed.
Setup
Create a new app directory and install hardhat dependencies:
mkdir sample-consumer-app
cd sample-consumer-app
npm init -y
npm install --save-dev hardhat
npm install --save-dev @nomicfoundation/hardhat-toolbox
npm install --save-dev hardhat-contract-sizer
npm install --save-dev @credora/on-chain-metrics
Now, configure the hardhat project as follows:
require("@nomicfoundation/hardhat-toolbox")
require("hardhat-contract-sizer")
const { BigNumber } = require('ethers');
const { ethers } = require('ethers');
const SOLC_SETTINGS = {
optimizer: {
enabled: true,
runs: 1_000,
},
}
// Set EVM private keys (required)
const PRIVATE_KEY = process.env.PRIVATE_KEY
if (!PRIVATE_KEY) {
throw Error("Set the PRIVATE_KEY environment variable with your EVM wallet private key")
}
const accounts = []
if (PRIVATE_KEY) {
accounts.push(PRIVATE_KEY)
}
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
defaultNetwork: "localFunctionsTestnet",
gas: 1000000, // Set the gas limit for transactions (in wei)
solidity: {
compilers: [
{
version: "0.8.20",
settings: SOLC_SETTINGS,
},
],
},
networks: {
ethereum: {
url: process.env.ETHEREUM_RPC_URL || "UNSET",
gasPrice: undefined,
nonce: undefined,
accounts,
chainId: 1,
nativeCurrencySymbol: "ETH",
},
ethereumSepolia: {
url: process.env.ETHEREUM_SEPOLIA_RPC_URL || "UNSET",
gasPrice: undefined,
nonce: undefined,
accounts,
chainId: 11155111,
nativeCurrencySymbol: "ETH",
},
flowPreviewnet: {
url: process.env.FLOWPREVIEWNET_RPC_URL || "UNSET",,
gasPrice: undefined,
nonce: undefined,
accounts,
chainId: 646,
nativeCurrencySymbol: "ETH",
}
}
}
Get On-Chain Metrics Using Synchronous Getter Functions
Step 1: Add a new Task to the Hardhat Script
Extend the hardhat.config.js with a task that aims to fetch Credora data from the blockchain.
const divisionFactor = 18;
task("getScore", "Get Score data stored on-chain")
.addParam("contract", "The contract address")
.addParam("entity", "The entity name")
.setAction(async (taskArgs, hre) => {
const fs = require("fs");
const contractAddress = taskArgs.contract;
const entity = ethers.encodeBytes32String(taskArgs.entity);
const signer = (await hre.ethers.getSigners())[0];
// Get the ABI from the compiled JSON artifact
const artifactPath = "build/artifacts/contracts/CredoraMetrics.sol/CredoraMetrics.json";
const artifact = JSON.parse(fs.readFileSync(artifactPath, "utf-8"));
// Connect to the deployed contract
const contract = new ethers.Contract(contractAddress, artifact.abi, signer);
const result = await contract.getScore(entity,{ gasLimit: 1000000 });
let decimalResult = ethers.formatUnits(result, divisionFactor);
console.log('Score:', decimalResult);
});
The getScore task receives in input the entity name and the contract address to call the getScore smart contract function. The returned value is an unsigned integer (uint256) and needs to be divided by a factor of 10^18 to convert it to its final floating-point format.
Step 2: Configure Environment Variables
Export the following environment variables:
ETHEREUM_RPC_URL, ETHEREUM_SEPOLIA_RPC_URL, FLOWPREVIEWNET_RPC_URL (e.g. https://eth-sepolia.g.alchemy.com/v2/{ALCHEMY_APIKEY})
PRIVATE_KEY of the wallet
Step 3: Run
From the same directory, run the following by passing the address of the Credora contract and the name of the entity owning the data:
npx hardhat getNAV
--network {ethereum/ethereumSepolia/flowPreviewnet}
--contract {address of the Credora contract}
--entity {entity name}
Get On-Chain Metrics Using Callbacks
Step 1: Write the Consumer Smart Contract
Navigate to the contracts directory and create a new Solidity file, e.g., MetricsConsumer.sol pragma solidity ^0.8.20;
/**
* @title Metrics Consumer Contract
* @notice This contract implements the CredoraFunctions for managing financial asset metrics.
*/
import "@credora/on-chain-metrics/contracts/CredoraFunctions.sol";
contract MetricsConsumer is CredoraFunctions {
// Example storage for metrics
mapping(bytes32 => uint256) private scores;
mapping(bytes32 => uint256) private nav;
mapping(bytes32 => bytes8) private rae;
mapping(bytes32 => uint256) private borrowCapacities;
mapping(bytes32 => uint256) private impliedPDs;
mapping(bytes32 => uint256) private impliedPDTenors;
// Implementations of fulfill functions that update the state and emit events
function fulfillScore(bytes32 _entity, uint256 _metric_data) external override {
scores[_entity] = _metric; // Update the metric
}
function fulfillNAV(bytes32 _entity, uint256 _metric_data) external override {
nav[_entity] = _metric; // Update the metric
}
function fulfillRAE(bytes32 _entity, bytes8 _metric_data) external override {
rae[_entity] = _metric; // Update the metric
}
function fulfillBorrowCapacity(bytes32 _entity, uint256 _metric_data) external override {
borrowCapacities[_entity] = _metric; // Update the metric
}
function fulfillImpliedPD(bytes32 _entity, uint256 _metric_data) external override {
impliedPDs[_entity] = _metric; // Update the metric
}
function fulfillImpliedPDtenor(bytes32 _entity, uint256 _metric_data) external override {
impliedPDTenors[_entity] = _metric; // Update the metric
}
}
Step 2: Write a Deployment Script
Navigate to the scripts directory and create a new deployment script, e.g., deploy.js:
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
const MyContract = await ethers.getContractFactory("MetricsConsumer");
const myContract = await MyContract.deploy();
console.log("Contract deployed to address:", myContract.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Step 3: Compile the Contract
Compile the smart contract using Hardhat:
npx hardhat compile
#### Step 4: Deploy the Contract Deploy the smart contract:
npx hardhat run scripts/deploy.js
--network {ethereum/ethereumSepolia/flowPreviewnet}
You should see output similar to:
Deploying contracts with the account: 0xYourAccountAddress
Contract deployed to address: 0xYourContractAddress
Step 5: Test the callbacks
Once Credora has subscribed your contract, the callbacks can be self-tested by calling the testTriggerCallbacks function (only available on Testnets) on the Credora Smart Contract. To do so, you can move inside the Credora package and run the related hardhat task (available at node_modules/@credora/on-chain-metrics/tasks/Testers/testTriggerCallbacks.js), which provides a set of random metrics and triggers the callbacks of the subscribed contract.
cd node_modules/@credora/on-chain-metrics && \
npx hardhat compile && \
npx hardhat testTriggerCallbacks
--entity {subscribed entityname}
--contract {address of the Credora contract}
--thirdpartyaddr {address of the consuming contract}
--network {ethereum/ethereumSepolia/flowPreviewnet}