Smart Contract Integration
Overview
This document outlines the smart contract architecture for the SKYOCEAN platform MVP. Smart contracts serve as the backbone for executing and enforcing trade agreements, managing document verification, facilitating financier participation, and maintaining immutable records of transaction histories. These contracts work in tandem with the Knowledge Assets stored in the DKG to provide a complete solution.
Smart Contract Architecture
Reusable Contract Architecture
For the SKYOCEAN platform, we employ a reusable contract architecture rather than deploying new contracts for each transaction, which would be prohibitively expensive. Our approach utilizes factory patterns and registry contracts that can handle multiple transactions of various types (CAD, on-chain deposits, partial payments, etc.).
Core Contract Components
The platform uses the following core contracts:
- Trade Agreement Contract - Creates and manages transaction records within a single contract
- Document Verification Registry - Handles document submission and verification for all transactions
- Token Management System - Manages the staking and release of financier tokens across all trades
- Knowledge Asset Registry - Maps blockchain transaction IDs to DKG Knowledge Assets
Contract Roles Explained
Below is a detailed explanation of each core contract:
Trade Agreement Contract:
This contract is responsible for creating and managing trade transactions on the blockchain. It assigns unique transaction IDs, orchestrates state transitions (such as financing completion, payment initiation, and transaction finalization), and emits events that trigger further processing by other system components.
Document Verification Registry:
Even though document verification is performed off-chain by the DKG, this contract records on-chain events related to document submission and verification. It captures the status updates once the DKG confirms that all required documents have been received and verified, ensuring an immutable record that triggers subsequent actions in the transaction flow.
Token Management System:
This contract manages all token-related operations, including staking, releasing tokens with profit shares upon successful completion, and returning tokens in case of transaction cancellations. It works closely with the Trade Agreement Contract to ensure that financier participation is securely recorded.
Knowledge Asset Registry:
Acting like a specialized oracle, this contract bridges the on-chain and off-chain worlds by linking blockchain transaction IDs to their corresponding Knowledge Assets stored in the DKG. It provides a secure reference to rich, verified off-chain data while leaving the actual document verification to the DKG.
Transaction Flow Approach
Rather than deploying new contracts per transaction, our system:
- Generates a unique transaction ID for each trade
- Registers the transaction in the appropriate registries
- Maps the transaction ID to the Knowledge Asset UAL
- Maintains transaction state through mappings and structs
- Coordinates state changes through events and controlled access
Trade Agreement Contract
The Trade Agreement Contract creates and manages all trade transactions within a single contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract TradeAgreementContract {
// Platform administration
address public platform;
address public documentVerificationRegistry;
address public tokenManagementSystem;
// Transaction states
enum TransactionState {
Created,
FinancingSecured,
DocumentsSubmitted,
DocumentsVerified,
PaymentInitiated, // Generic payment initiated state
PaymentConfirmed, // Generic payment confirmed state
TokensReleased,
Completed,
Disputed,
Cancelled
}
// Payment methods
enum PaymentMethod {
CAD, // Cash Against Documents
DepositWithBalance, // Deposit with remaining balance at destination
LetterOfCredit, // Traditional LC
FullPaymentUpfront, // 100% payment before shipment
OnChainEscrow // On-chain escrow payment
}
// Financier structure
struct Financier {
address wallet;
uint256 stakedAmount;
uint256 expectedReturn;
bool hasWithdrawn;
}
// Transaction structure
struct Transaction {
string knowledgeAssetUAL;
string buyerOffChainId; // Off-chain ID for CAD or can be empty if buyer has wallet
address buyerWallet; // Wallet address for on-chain payments or empty for CAD
address seller;
PaymentMethod paymentMethod;
uint256 targetFinancingAmount;
uint256 financierRewardRate; // in basis points (e.g., 350 = 3.5%)
uint256 totalStaked;
TransactionState state;
uint256 createdAt;
uint256 completedAt;
mapping(address => bool) isFinancier;
uint256 financierCount;
}
// Transaction storage - mapping from transaction ID to Transaction
mapping(bytes32 => Transaction) public transactions;
// Array of financiers for each transaction
mapping(bytes32 => Financier[]) public transactionFinanciers;
// Active transaction IDs
bytes32[] public activeTransactionIds;
// Transaction counter for generating IDs
uint256 private transactionCounter;
// Events
event TransactionCreated(bytes32 transactionId, string knowledgeAssetUAL, PaymentMethod paymentMethod);
event FinancierAdded(bytes32 transactionId, address financier, uint256 amount, uint256 expectedReturn);
event FinancingCompleted(bytes32 transactionId, uint256 totalAmount);
event DocumentsSubmitted(bytes32 transactionId);
event DocumentsVerified(bytes32 transactionId);
event PaymentInitiated(bytes32 transactionId);
event PaymentConfirmed(bytes32 transactionId, uint256 amount);
event TokensReleased(bytes32 transactionId, uint256 totalAmount);
event TransactionCompleted(bytes32 transactionId);
event DisputeRaised(bytes32 transactionId, string reason);
event TransactionCancelled(bytes32 transactionId);
// Modifiers
modifier onlyPlatform() {
require(msg.sender == platform, "Only the platform can call this function");
_;
}
modifier onlyDocVerification() {
require(msg.sender == documentVerificationRegistry, "Only the document verification registry can call this function");
_;
}
modifier validTransaction(bytes32 transactionId) {
require(transactions[transactionId].createdAt > 0, "Transaction does not exist");
_;
}
modifier inState(bytes32 transactionId, TransactionState state) {
require(transactions[transactionId].state == state, "Invalid transaction state");
_;
}
constructor(address _platform) {
platform = _platform;
transactionCounter = 0;
}
function setDocumentVerificationRegistry(address _registry) external onlyPlatform {
documentVerificationRegistry = _registry;
}
function setTokenManagementSystem(address _system) external onlyPlatform {
tokenManagementSystem = _system;
}
// Generate a unique transaction ID
function generateTransactionId(string memory knowledgeAssetUAL) private returns (bytes32) {
bytes32 transactionId = keccak256(abi.encodePacked(knowledgeAssetUAL, block.timestamp, transactionCounter));
transactionCounter++;
return transactionId;
}
// Create a new transaction
function createTransaction(
string memory knowledgeAssetUAL,
string memory buyerOffChainId,
address buyerWallet,
address seller,
uint8 paymentMethodCode,
uint256 targetFinancingAmount,
uint256 financierRewardRate
) external onlyPlatform returns (bytes32) {
// Generate transaction ID
bytes32 transactionId = generateTransactionId(knowledgeAssetUAL);
// Validate payment method
require(paymentMethodCode <= uint8(PaymentMethod.OnChainEscrow), "Invalid payment method");
PaymentMethod paymentMethod = PaymentMethod(paymentMethodCode);
// For CAD payments, buyer off-chain ID is required
if (paymentMethod == PaymentMethod.CAD) {
require(bytes(buyerOffChainId).length > 0, "Buyer off-chain ID required for CAD payment");
}
// For on-chain payments, buyer wallet is required
if (paymentMethod == PaymentMethod.OnChainEscrow ||
paymentMethod == PaymentMethod.FullPaymentUpfront ||
paymentMethod == PaymentMethod.DepositWithBalance) {
require(buyerWallet != address(0), "Buyer wallet required for on-chain payment");
}
// Create transaction
Transaction storage newTx = transactions[transactionId];
newTx.knowledgeAssetUAL = knowledgeAssetUAL;
newTx.buyerOffChainId = buyerOffChainId;
newTx.buyerWallet = buyerWallet;
newTx.seller = seller;
newTx.paymentMethod = paymentMethod;
newTx.targetFinancingAmount = targetFinancingAmount;
newTx.financierRewardRate = financierRewardRate;
newTx.totalStaked = 0;
newTx.state = TransactionState.Created;
newTx.createdAt = block.timestamp;
newTx.financierCount = 0;
// Add to active transactions
activeTransactionIds.push(transactionId);
emit TransactionCreated(transactionId, knowledgeAssetUAL, paymentMethod);
return transactionId;
}
// Add a financier to a transaction
function addFinancier(
bytes32 transactionId,
address financierWallet,
uint256 amount
) external onlyPlatform validTransaction(transactionId) inState(transactionId, TransactionState.Created) {
Transaction storage transaction = transactions[transactionId];
require(amount > 0, "Staked amount must be greater than 0");
require(!transaction.isFinancier[financierWallet], "Financier already added");
// Calculate expected return based on reward rate
uint256 expectedReturn = amount + (amount * transaction.financierRewardRate / 10000);
// Add financier to the transaction
Financier memory newFinancier = Financier({
wallet: financierWallet,
stakedAmount: amount,
expectedReturn: expectedReturn,
hasWithdrawn: false
});
transactionFinanciers[transactionId].push(newFinancier);
transaction.isFinancier[financierWallet] = true;
transaction.financierCount++;
transaction.totalStaked += amount;
emit FinancierAdded(transactionId, financierWallet, amount, expectedReturn);
// Check if financing target is reached
if (transaction.totalStaked >= transaction.targetFinancingAmount) {
transaction.state = TransactionState.FinancingSecured;
emit FinancingCompleted(transactionId, transaction.totalStaked);
}
}
// Document verification state management functions
function documentsSubmitted(bytes32 transactionId)
external
onlyDocVerification
validTransaction(transactionId)
inState(transactionId, TransactionState.FinancingSecured)
{
transactions[transactionId].state = TransactionState.DocumentsSubmitted;
emit DocumentsSubmitted(transactionId);
}
function documentsVerified(bytes32 transactionId)
external
onlyDocVerification
validTransaction(transactionId)
inState(transactionId, TransactionState.DocumentsSubmitted)
{
transactions[transactionId].state = TransactionState.DocumentsVerified;
emit DocumentsVerified(transactionId);
}
// Payment handling functions based on payment method
// For on-chain payments
function depositPayment(bytes32 transactionId)
external
payable
validTransaction(transactionId)
inState(transactionId, TransactionState.DocumentsVerified)
{
Transaction storage transaction = transactions[transactionId];
// Only buyer can deposit for on-chain methods
require(msg.sender == transaction.buyerWallet, "Only buyer can deposit");
// Payment method must be on-chain
require(
transaction.paymentMethod == PaymentMethod.OnChainEscrow ||
transaction.paymentMethod == PaymentMethod.FullPaymentUpfront ||
transaction.paymentMethod == PaymentMethod.DepositWithBalance,
"Invalid payment method for on-chain deposit"
);
// Value must match target amount for full payments
if (transaction.paymentMethod == PaymentMethod.OnChainEscrow ||
transaction.paymentMethod == PaymentMethod.FullPaymentUpfront) {
require(msg.value == transaction.targetFinancingAmount, "Incorrect payment amount");
}
transaction.state = TransactionState.PaymentConfirmed;
emit PaymentInitiated(transactionId);
emit PaymentConfirmed(transactionId, msg.value);
}
// For off-chain payments (CAD)
function confirmOffChainPayment(bytes32 transactionId)
external
onlyPlatform
validTransaction(transactionId)
inState(transactionId, TransactionState.DocumentsVerified)
{
Transaction storage transaction = transactions[transactionId];
// Payment method must be CAD or LC
require(
transaction.paymentMethod == PaymentMethod.CAD ||
transaction.paymentMethod == PaymentMethod.LetterOfCredit,
"Invalid payment method for off-chain confirmation"
);
transaction.state = TransactionState.PaymentConfirmed;
emit PaymentConfirmed(transactionId, transaction.targetFinancingAmount);
}
// Release tokens to financiers after payment confirmation
function releaseTokens(bytes32 transactionId)
external
onlyPlatform
validTransaction(transactionId)
inState(transactionId, TransactionState.PaymentConfirmed)
{
Transaction storage transaction = transactions[transactionId];
require(tokenManagementSystem != address(0), "Token management system not set");
transaction.state = TransactionState.TokensReleased;
// Call the token management system to release tokens to financiers
ITokenManagementSystem(tokenManagementSystem).releaseTokensToFinanciers(
transactionId,
transactionFinanciers[transactionId]
);
emit TokensReleased(transactionId, transaction.totalStaked);
}
// Complete the transaction
function completeTransaction(bytes32 transactionId)
external
onlyPlatform
validTransaction(transactionId)
inState(transactionId, TransactionState.TokensReleased)
{
Transaction storage transaction = transactions[transactionId];
transaction.state = TransactionState.Completed;
transaction.completedAt = block.timestamp;
emit TransactionCompleted(transactionId);
}
// Dispute and cancellation functions
function raiseDispute(bytes32 transactionId, string memory reason)
external
validTransaction(transactionId)
{
Transaction storage transaction = transactions[transactionId];
require(
msg.sender == transaction.seller ||
msg.sender == platform,
"Only seller or platform can raise disputes"
);
require(
transaction.state != TransactionState.Completed &&
transaction.state != TransactionState.Cancelled,
"Transaction already finalized"
);
transaction.state = TransactionState.Disputed;
emit DisputeRaised(transactionId, reason);
}
function cancelTransaction(bytes32 transactionId)
external
onlyPlatform
validTransaction(transactionId)
{
Transaction storage transaction = transactions[transactionId];
require(transaction.state != TransactionState.Completed, "Transaction already completed");
// Return tokens to financiers if they have staked
if (transaction.state == TransactionState.FinancingSecured ||
transaction.state == TransactionState.DocumentsSubmitted ||
transaction.state == TransactionState.DocumentsVerified) {
ITokenManagementSystem(tokenManagementSystem).returnStakedTokens(
transactionId,
transactionFinanciers[transactionId]
);
}
transaction.state = TransactionState.Cancelled;
emit TransactionCancelled(transactionId);
}
// Getter functions for transaction data
function getTransactionState(bytes32 transactionId) external view returns (TransactionState) {
return transactions[transactionId].state;
}
function getTransactionPaymentMethod(bytes32 transactionId) external view returns (PaymentMethod) {
return transactions[transactionId].paymentMethod;
}
function getTransactionFinanciers(bytes32 transactionId) external view returns (uint256) {
return transactions[transactionId].financierCount;
}
function getTransactionKnowledgeAssetUAL(bytes32 transactionId) external view returns (string memory) {
return transactions[transactionId].knowledgeAssetUAL;
}
function getActiveTransactionsCount() external view returns (uint256) {
return activeTransactionIds.length;
}
// Get summary of transaction details
function getTransactionSummary(bytes32 transactionId)
external
view
validTransaction(transactionId)
returns (
string memory knowledgeAssetUAL,
string memory buyerOffChainId,
address buyerWallet,
address seller,
uint8 paymentMethod,
uint256 targetFinancingAmount,
uint256 totalStaked,
uint8 state,
uint256 createdAt,
uint256 completedAt,
uint256 financierCount
)
{
Transaction storage txn = transactions[transactionId];
return (
txn.knowledgeAssetUAL,
txn.buyerOffChainId,
txn.buyerWallet,
txn.seller,
uint8(txn.paymentMethod),
txn.targetFinancingAmount,
txn.totalStaked,
uint8(txn.state),
txn.createdAt,
txn.completedAt,
txn.financierCount
);
}
}
interface ITokenManagementSystem {
function releaseTokensToFinanciers(bytes32 transactionId, TradeAgreementContract.Financier[] calldata financiers) external;
function returnStakedTokens(bytes32 transactionId, TradeAgreementContract.Financier[] calldata financiers) external;
}
Document Verification Registry
The Document Verification Registry handles documents for all transactions:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface ITradeAgreementContract {
function documentsSubmitted(bytes32 transactionId) external;
function documentsVerified(bytes32 transactionId) external;
}
contract DocumentVerificationRegistry {
// Platform administration
address public platform;
address public tradeAgreementContract;
// Document structure
struct Document {
string documentType;
string documentHash;
string ipfsHash;
uint256 timestamp;
bool verified;
}
// Document storage by transaction ID and document type
mapping(bytes32 => mapping(string => Document)) public documents;
// Required documents for each transaction
mapping(bytes32 => string[]) public requiredDocuments;
// Document submission and verification tracking
mapping(bytes32 => mapping(string => bool)) public documentSubmitted;
mapping(bytes32 => mapping(string => bool)) public documentVerified;
// Events
event DocumentSubmitted(bytes32 transactionId, string documentType, string documentHash, string ipfsHash);
event DocumentVerified(bytes32 transactionId, string documentType, string documentHash);
event AllDocumentsVerified(bytes32 transactionId);
// Modifiers
modifier onlyPlatform() {
require(msg.sender == platform, "Only the platform can call this function");
_;
}
modifier validTransaction(bytes32 transactionId) {
require(requiredDocuments[transactionId].length > 0, "Transaction not registered");
_;
}
constructor(address _platform) {
platform = _platform;
}
function setTradeAgreementContract(address _contract) external onlyPlatform {
tradeAgreementContract = _contract;
}
// Register a new transaction with its required documents
function registerTransaction(bytes32 transactionId, string[] memory _requiredDocuments) external onlyPlatform {
require(requiredDocuments[transactionId].length == 0, "Transaction already registered");
require(_requiredDocuments.length > 0, "Required documents list cannot be empty");
requiredDocuments[transactionId] = _requiredDocuments;
}
// Submit a document for a transaction
function submitDocument(
bytes32 transactionId,
string memory documentType,
string memory documentHash,
string memory ipfsHash
) external onlyPlatform validTransaction(transactionId) {
require(isRequiredDocument(transactionId, documentType), "Document type not required for this transaction");
require(!documentSubmitted[transactionId][documentType], "Document already submitted");
documents[transactionId][documentType] = Document({
documentType: documentType,
documentHash: documentHash,
ipfsHash: ipfsHash,
timestamp: block.timestamp,
verified: false
});
documentSubmitted[transactionId][documentType] = true;
emit DocumentSubmitted(transactionId, documentType, documentHash, ipfsHash);
if (allDocumentsSubmitted(transactionId)) {
ITradeAgreementContract(tradeAgreementContract).documentsSubmitted(transactionId);
}
}
// Verify a document for a transaction
function verifyDocument(bytes32 transactionId, string memory documentType)
external
onlyPlatform
validTransaction(transactionId)
{
require(documentSubmitted[transactionId][documentType], "Document not submitted");
require(!documentVerified[transactionId][documentType], "Document already verified");
documentVerified[transactionId][documentType] = true;
documents[transactionId][documentType].verified = true;
emit DocumentVerified(transactionId, documentType, documents[transactionId][documentType].documentHash);
if (allDocumentsVerified(transactionId)) {
ITradeAgreementContract(tradeAgreementContract).documentsVerified(transactionId);
emit AllDocumentsVerified(transactionId);
}
}
// Check if a document type is required for a transaction
function isRequiredDocument(bytes32 transactionId, string memory documentType) public view returns (bool) {
string[] memory docs = requiredDocuments[transactionId];
for (uint i = 0; i < docs.length; i++) {
if (keccak256(bytes(docs[i])) == keccak256(bytes(documentType))) {
return true;
}
}
return false;
}
// Check if all required documents have been submitted for a transaction
function allDocumentsSubmitted(bytes32 transactionId) public view returns (bool) {
string[] memory docs = requiredDocuments[transactionId];
for (uint i = 0; i < docs.length; i++) {
if (!documentSubmitted[transactionId][docs[i]]) {
return false;
}
}
return true;
}
// Check if all submitted documents have been verified for a transaction
function allDocumentsVerified(bytes32 transactionId) public view returns (bool) {
string[] memory docs = requiredDocuments[transactionId];
for (uint i = 0; i < docs.length; i++) {
if (!documentVerified[transactionId][docs[i]]) {
return false;
}
}
return true;
}
// Get document details
function getDocumentDetails(bytes32 transactionId, string memory documentType)
external
view
returns (
string memory,
string memory,
string memory,
uint256,
bool
)
{
Document memory doc = documents[transactionId][documentType];
return (
doc.documentType,
doc.documentHash,
doc.ipfsHash,
doc.timestamp,
doc.verified
);
}
// Get all required documents for a transaction
function getRequiredDocuments(bytes32 transactionId) external view returns (string[] memory) {
return requiredDocuments[transactionId];
}
}
Token Management System
The Token Management System handles token operations for all transactions:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract TokenManagementSystem {
// Platform administration
address public platform;
address public tradeAgreementContract;
address public tokenAddress;
// Transaction token accounting
mapping(bytes32 => uint256) public transactionStakedAmount;
mapping(bytes32 => mapping(address => uint256)) public financierStakedAmount;
mapping(bytes32 => address[]) public transactionFinanciers;
// Events
event TokensStaked(bytes32 transactionId, address financier, uint256 amount);
event TokensReleased(bytes32 transactionId, address financier, uint256 amount, uint256 profit);
event TokensReturned(bytes32 transactionId, address financier, uint256 amount);
// Modifiers
modifier onlyPlatform() {
require(msg.sender == platform, "Only the platform can call this function");
_;
}
modifier onlyTradeAgreementContract() {
require(msg.sender == tradeAgreementContract, "Only the trade agreement contract can call this function");
_;
}
constructor(address _platform, address _tokenAddress) {
platform = _platform;
tokenAddress = _tokenAddress;
}
function setTradeAgreementContract(address _contract) external onlyPlatform {
tradeAgreementContract = _contract;
}
// Stake tokens for a transaction
function stakeTokens(bytes32 transactionId, address financier, uint256 amount) external onlyPlatform {
require(amount > 0, "Amount must be greater than 0");
// Transfer tokens from financier to this contract
bool success = IERC20(tokenAddress).transferFrom(financier, address(this), amount);
require(success, "Token transfer failed");
// Record staking
if (financierStakedAmount[transactionId][financier] == 0) {
transactionFinanciers[transactionId].push(financier);
}
financierStakedAmount[transactionId][financier] += amount;
transactionStakedAmount[transactionId] += amount;
emit TokensStaked(transactionId, financier, amount);
}
// Release tokens with profit after successful transaction
function releaseTokensToFinanciers(
bytes32 transactionId,
TradeAgreementContract.Financier[] calldata financiers
) external onlyTradeAgreementContract {
for (uint i = 0; i < financiers.length; i++) {
TradeAgreementContract.Financier memory financier = financiers[i];
if (!financier.hasWithdrawn && financier.stakedAmount > 0) {
// Transfer original amount + profit to financier
bool success = IERC20(tokenAddress).transfer(financier.wallet, financier.expectedReturn);
require(success, "Token release failed");
uint256 profit = financier.expectedReturn - financier.stakedAmount;
emit TokensReleased(transactionId, financier.wallet, financier.stakedAmount, profit);
}
}
}
// Return staked tokens in case of cancellation
function returnStakedTokens(
bytes32 transactionId,
TradeAgreementContract.Financier[] calldata financiers
) external onlyTradeAgreementContract {
for (uint i = 0; i < financiers.length; i++) {
TradeAgreementContract.Financier memory financier = financiers[i];
if (!financier.hasWithdrawn && financier.stakedAmount > 0) {
// Return only the staked amount, no profit
bool success = IERC20(tokenAddress).transfer(financier.wallet, financier.stakedAmount);
require(success, "Token return failed");
emit TokensReturned(transactionId, financier.wallet, financier.stakedAmount);
}
}
}
// Get transaction total staked amount
function getTransactionStakedAmount(bytes32 transactionId) external view returns (uint256) {
return transactionStakedAmount[transactionId];
}
// Get financier staked amount for a transaction
function getFinancierStakedAmount(bytes32 transactionId, address financier) external view returns (uint256) {
return financierStakedAmount[transactionId][financier];
}
// Get contract token balance
function getContractBalance() external view returns (uint256) {
return IERC20(tokenAddress).balanceOf(address(this));
}
}
// Interface for accessing Financier struct from Trade Agreement Contract
interface TradeAgreementContract {
struct Financier {
address wallet;
uint256 stakedAmount;
uint256 expectedReturn;
bool hasWithdrawn;
}
}
Knowledge Asset Registry
The Knowledge Asset Registry handles the bidirectional link between blockchain transaction IDs and DKG Knowledge Assets:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract KnowledgeAssetRegistry {
address public platform;
// Mapping from transaction ID to Knowledge Asset UAL
mapping(bytes32 => string) public transactionToKnowledgeAsset;
// Mapping from Knowledge Asset UAL to transaction ID
mapping(string => bytes32) public knowledgeAssetToTransaction;
// Events
event KnowledgeAssetLinked(bytes32 indexed transactionId, string knowledgeAssetUAL);
event KnowledgeAssetUpdated(string knowledgeAssetUAL, string updateType, string details);
// Modifiers
modifier onlyPlatform() {
require(msg.sender == platform, "Only the platform can call this function");
_;
}
constructor(address _platform) {
platform = _platform;
}
function linkKnowledgeAsset(bytes32 transactionId, string memory knowledgeAssetUAL) external onlyPlatform {
transactionToKnowledgeAsset[transactionId] = knowledgeAssetUAL;
knowledgeAssetToTransaction[knowledgeAssetUAL] = transactionId;
emit KnowledgeAssetLinked(transactionId, knowledgeAssetUAL);
}
function recordKnowledgeAssetUpdate(string memory knowledgeAssetUAL, string memory updateType, string memory details) external onlyPlatform {
require(bytes(transactionToKnowledgeAsset[knowledgeAssetToTransaction[knowledgeAssetUAL]]).length > 0, "Knowledge Asset not linked");
emit KnowledgeAssetUpdated(knowledgeAssetUAL, updateType, details);
}
function getTransactionForKnowledgeAsset(string memory knowledgeAssetUAL) external view returns (bytes32) {
return knowledgeAssetToTransaction[knowledgeAssetUAL];
}
function getKnowledgeAssetForTransaction(bytes32 transactionId) external view returns (string memory) {
return transactionToKnowledgeAsset[transactionId];
}
function isKnowledgeAssetLinked(string memory knowledgeAssetUAL) external view returns (bool) {
return knowledgeAssetToTransaction[knowledgeAssetUAL] != bytes32(0);
}
}
API Integration
To interact with these smart contracts and integrate them with the Knowledge Assets in the DKG:
Transaction Creation API
// Create a new trade transaction
async function createTransaction(
knowledgeAssetUAL,
buyerInfo, // Either off-chain ID or wallet address depending on payment method
sellerAddress,
paymentMethod, // 0: CAD, 1: DepositWithBalance, 2: LC, 3: FullPayment, 4: OnChainEscrow
targetFinancingAmount,
financierRewardRate
) {
try {
// 1. Determine buyer details based on payment method
let buyerOffChainId = "";
let buyerWallet = "0x0000000000000000000000000000000000000000";
if (paymentMethod === 0 || paymentMethod === 2) { // CAD or LC
buyerOffChainId = buyerInfo;
} else { // On-chain payment methods
buyerWallet = buyerInfo;
}
// 2. Create transaction in the Trade Agreement Contract
const factory = await getTradeAgreementContract();
const tx = await factory.createTransaction(
knowledgeAssetUAL,
buyerOffChainId,
buyerWallet,
sellerAddress,
paymentMethod,
targetFinancingAmount,
financierRewardRate
);
const receipt = await tx.wait();
// 3. Get transaction ID from event logs
const event = receipt.events.find(e => e.event === 'TransactionCreated');
const transactionId = event.args.transactionId;
// 4. Get required documents from the Knowledge Asset
const requiredDocs = await getRequiredDocumentsFromKA(knowledgeAssetUAL);
// 5. Register transaction in Document Verification Registry
const docRegistry = await getDocumentVerificationRegistry();
await docRegistry.registerTransaction(transactionId, requiredDocs);
// 6. Link Knowledge Asset in the registry
const kaRegistry = await getKnowledgeAssetRegistry();
await kaRegistry.linkKnowledgeAsset(transactionId, knowledgeAssetUAL);
// 7. Update the Knowledge Asset with transaction ID
await updateKnowledgeAssetWithTransactionId(
knowledgeAssetUAL,
transactionId
);
return {
transactionId,
knowledgeAssetUAL,
paymentMethod,
success: true
};
} catch (error) {
console.error("Error creating transaction:", error);
return {
error: error.message,
success: false
};
}
}
Financier Participation API
// Add a financier to a transaction
async function addFinancierToTransaction(
transactionId,
financierWalletAddress,
stakeAmount
) {
try {
// 1. Get contract instances
const factory = await getTradeAgreementContract();
const tokenSystem = await getTokenManagementSystem();
// 2. First stake the tokens in the token management system
const tx1 = await tokenSystem.stakeTokens(transactionId, financierWalletAddress, stakeAmount);
await tx1.wait();
// 3. Then add the financier to the transaction in the factory
const tx2 = await factory.addFinancier(transactionId, financierWalletAddress, stakeAmount);
await tx2.wait();
// 4. Get the Knowledge Asset UAL
const kaRegistry = await getKnowledgeAssetRegistry();
const knowledgeAssetUAL = await kaRegistry.getKnowledgeAssetForTransaction(transactionId);
// 5. Update the Knowledge Asset with financier information
await updateKnowledgeAssetWithFinancier(
knowledgeAssetUAL,
financierWalletAddress,
stakeAmount
);
// 6. Check if financing is completed
const state = await factory.getTransactionState(transactionId);
const isFinancingCompleted = state === 1; // FinancingSecured state
if (isFinancingCompleted) {
await updateKnowledgeAssetWithFinancingCompleted(
knowledgeAssetUAL
);
}
return {
transactionId,
financierAddress: financierWalletAddress,
stakedAmount: stakeAmount,
financingCompleted: isFinancingCompleted,
success: true
};
} catch (error) {
console.error("Error adding financier:", error);
return {
error: error.message,
success: false
};
}
}
Document Submission and Verification API
// Submit a document for a transaction
async function submitDocument(
transactionId,
documentType,
documentFile
) {
try {
// 1. Calculate document hash
const documentHash = await calculateFileHash(documentFile);
// 2. Upload document to IPFS
const ipfsHash = await uploadToIPFS(documentFile);
// 3. Get the Knowledge Asset UAL
const kaRegistry = await getKnowledgeAssetRegistry();
const knowledgeAssetUAL = await kaRegistry.getKnowledgeAssetForTransaction(transactionId);
// 4. Update the Knowledge Asset with document information in DKG
await updateKnowledgeAssetWithDocument(
knowledgeAssetUAL,
documentType,
documentHash,
ipfsHash
);
// 5. After DKG update, submit document reference to the registry
const docRegistry = await getDocumentVerificationRegistry();
const tx = await docRegistry.submitDocument(
transactionId,
documentType,
documentHash,
ipfsHash
);
await tx.wait();
return {
transactionId,
documentType,
documentHash,
ipfsHash,
success: true
};
} catch (error) {
console.error("Error submitting document:", error);
return {
error: error.message,
success: false
};
}
}
// Record document verification after DKG verification process
async function recordDocumentVerification(
transactionId,
documentType,
verificationResult,
verifierDetails
) {
try {
// 1. Get the Knowledge Asset UAL
const kaRegistry = await getKnowledgeAssetRegistry();
const knowledgeAssetUAL = await kaRegistry.getKnowledgeAssetForTransaction(transactionId);
// 2. Verification already happened in DKG - update the Knowledge Asset with verification details
await updateKnowledgeAssetWithVerificationDetails(
knowledgeAssetUAL,
documentType,
verificationResult,
verifierDetails
);
// 3. Now record this verification in the blockchain
const docRegistry = await getDocumentVerificationRegistry();
const tx = await docRegistry.verifyDocument(
transactionId,
documentType
);
await tx.wait();
// 4. Check if all documents verified
const allVerified = await docRegistry.allDocumentsVerified(transactionId);
// 5. If all documents are verified, update the Knowledge Asset accordingly
if (allVerified) {
await updateKnowledgeAssetWithAllDocumentsVerified(
knowledgeAssetUAL
);
}
return {
transactionId,
documentType,
allVerified,
success: true
};
} catch (error) {
console.error("Error recording document verification:", error);
return {
error: error.message,
success: false
};
}
}
Document Status Monitoring API
// Listen for document status changes in DKG and update blockchain
async function monitorDocumentStatus(knowledgeAssetUAL) {
try {
// 1. Set up listener for document status changes in DKG
const edgeNode = await getEdgeNodeConnection();
edgeNode.on('documentStatusChanged', async (documentEvent) => {
// 2. When status changes in DKG, update the blockchain
if (documentEvent.knowledgeAssetUAL === knowledgeAssetUAL) {
// 3. Get transaction ID from KA registry
const kaRegistry = await getKnowledgeAssetRegistry();
const transactionId = await kaRegistry.getTransactionForKnowledgeAsset(knowledgeAssetUAL);
// 4. Update blockchain based on document type and new status
if (documentEvent.status === 'VERIFIED') {
await recordDocumentVerification(
transactionId,
documentEvent.documentType,
documentEvent.verificationResult,
documentEvent.verifierDetails
);
}
}
});
return {
knowledgeAssetUAL,
monitoring: true,
success: true
};
} catch (error) {
console.error("Error setting up document status monitoring:", error);
return {
error: error.message,
success: false
};
}
}
Payment Processing API
// Process on-chain payment
async function processOnChainPayment(
transactionId,
buyerWalletAddress,
paymentAmount
) {
try {
// 1. Get contract instances
const factory = await getTradeAgreementContract();
// 2. Get transaction details to verify payment
const txDetails = await factory.getTransactionSummary(transactionId);
const paymentMethod = txDetails[4]; // PaymentMethod
// 3. Verify payment method is on-chain
if (paymentMethod < 1 || paymentMethod > 4 || paymentMethod === 2) {
throw new Error("Invalid payment method for on-chain payment");
}
// 4. Process the payment
const tx = await factory.depositPayment(transactionId, {
from: buyerWalletAddress,
value: paymentAmount
});
await tx.wait();
// 5. Get the Knowledge Asset UAL
const kaRegistry = await getKnowledgeAssetRegistry();
const knowledgeAssetUAL = await kaRegistry.getKnowledgeAssetForTransaction(transactionId);
// 6. Update the Knowledge Asset with payment information
await updateKnowledgeAssetWithPayment(
knowledgeAssetUAL,
paymentAmount,
"on-chain"
);
return {
transactionId,
paymentAmount,
paymentMethod: "on-chain",
success: true
};
} catch (error) {
console.error("Error processing on-chain payment:", error);
return {
error: error.message,
success: false
};
}
}
// Confirm off-chain payment (CAD or LC)
async function confirmOffChainPayment(
transactionId,
paymentAmount,
paymentReference
) {
try {
// 1. Get contract instances
const factory = await getTradeAgreementContract();
// 2. Get transaction details to verify payment method
const txDetails = await factory.getTransactionSummary(transactionId);
const paymentMethod = txDetails[4]; // PaymentMethod
// 3. Verify payment method is off-chain
if (paymentMethod !== 0 && paymentMethod !== 2) {
throw new Error("Invalid payment method for off-chain confirmation");
}
// 4. Confirm the off-chain payment
const tx = await factory.confirmOffChainPayment(transactionId);
await tx.wait();
// 5. Get the Knowledge Asset UAL
const kaRegistry = await getKnowledgeAssetRegistry();
const knowledgeAssetUAL = await kaRegistry.getKnowledgeAssetForTransaction(transactionId);
// 6. Update the Knowledge Asset with payment information
await updateKnowledgeAssetWithPayment(
knowledgeAssetUAL,
paymentAmount,
paymentMethod === 0 ? "CAD" : "LC",
paymentReference
);
return {
transactionId,
paymentAmount,
paymentMethod: paymentMethod === 0 ? "CAD" : "LC",
paymentReference,
success: true
};
} catch (error) {
console.error("Error confirming off-chain payment:", error);
return {
error: error.message,
success: false
};
}
}
Token Release API
// Release tokens to financiers
async function releaseTokensToFinanciers(
transactionId
) {
try {
// 1. Get contract instances
const factory = await getTradeAgreementContract();
// 2. Release the tokens
const tx = await factory.releaseTokens(transactionId);
await tx.wait();
// 3. Get the Knowledge Asset UAL
const kaRegistry = await getKnowledgeAssetRegistry();
const knowledgeAssetUAL = await kaRegistry.getKnowledgeAssetForTransaction(transactionId);
// 4. Update the Knowledge Asset with token release information
await updateKnowledgeAssetWithTokenRelease(
knowledgeAssetUAL,
transactionId
);
return {
transactionId,
success: true
};
} catch (error) {
console.error("Error releasing tokens:", error);
return {
error: error.message,
success: false
};
}
}
Transaction Completion API
// Complete a transaction
async function completeTransaction(
transactionId
) {
try {
// 1. Get contract instances
const factory = await getTradeAgreementContract();
// 2. Complete the transaction
const tx = await factory.completeTransaction(transactionId);
await tx.wait();
// 3. Get the Knowledge Asset UAL
const kaRegistry = await getKnowledgeAssetRegistry();
const knowledgeAssetUAL = await kaRegistry.getKnowledgeAssetForTransaction(transactionId);
// 4. Update the Knowledge Asset with completion information
await updateKnowledgeAssetWithCompletion(
knowledgeAssetUAL,
transactionId
);
return {
transactionId,
completedAt: Date.now(),
success: true
};
} catch (error) {
console.error("Error completing transaction:", error);
return {
error: error.message,
success: false
};
}
}
// Handle transaction disputes
async function raiseDispute(
transactionId,
reason,
disputeRaiserAddress
) {
try {
// 1. Get contract instances
const factory = await getTradeAgreementContract();
// 2. Raise the dispute
const tx = await factory.raiseDispute(transactionId, reason, {
from: disputeRaiserAddress
});
await tx.wait();
// 3. Get the Knowledge Asset UAL
const kaRegistry = await getKnowledgeAssetRegistry();
const knowledgeAssetUAL = await kaRegistry.getKnowledgeAssetForTransaction(transactionId);
// 4. Update the Knowledge Asset with dispute information
await updateKnowledgeAssetWithDispute(
knowledgeAssetUAL,
transactionId,
reason,
disputeRaiserAddress
);
return {
transactionId,
reason,
disputeRaiser: disputeRaiserAddress,
disputedAt: Date.now(),
success: true
};
} catch (error) {
console.error("Error raising dispute:", error);
return {
error: error.message,
success: false
};
}
}
// Cancel a transaction
async function cancelTransaction(
transactionId
) {
try {
// 1. Get contract instances
const factory = await getTradeAgreementContract();
// 2. Cancel the transaction
const tx = await factory.cancelTransaction(transactionId);
await tx.wait();
// 3. Get the Knowledge Asset UAL
const kaRegistry = await getKnowledgeAssetRegistry();
const knowledgeAssetUAL = await kaRegistry.getKnowledgeAssetForTransaction(transactionId);
// 4. Update the Knowledge Asset with cancellation information
await updateKnowledgeAssetWithCancellation(
knowledgeAssetUAL,
transactionId
);
return {
transactionId,
cancelledAt: Date.now(),
success: true
};
} catch (error) {
console.error("Error cancelling transaction:", error);
return {
error: error.message,
success: false
};
}
}
DKG Integration
The integration with the Decentralized Knowledge Graph (DKG) is achieved through the Knowledge Asset Registry and API functions that update Knowledge Assets based on blockchain events.
Bidirectional Flow Between DKG and Blockchain
The integration follows a bidirectional flow as outlined in the system architecture:
- DKG → Blockchain Flow:
- Edge Node processes document uploads and verifications
- When document status changes (e.g., verification completed) in DKG, the blockchain is updated
- Smart contracts reflect the verification status but do not perform verification
- Blockchain → DKG Flow:
- Smart contracts emit events for state changes (e.g., payments)
- Web application listens for these events and updates DKG Knowledge Assets
- The DKG always maintains the latest state synchronized with blockchain events
Knowledge Asset Update Functions
// Update Knowledge Asset with transaction ID
async function updateKnowledgeAssetWithTransactionId(
knowledgeAssetUAL,
transactionId
) {
try {
// 1. Get the Knowledge Asset from the DKG
const knowledgeAsset = await fetchKnowledgeAsset(knowledgeAssetUAL);
// 2. Update the Knowledge Asset with transaction information
knowledgeAsset.blockchainMetadata = {
...knowledgeAsset.blockchainMetadata,
transactionId: transactionId,
status: "Created",
createdAt: Date.now()
};
// 3. Save the updated Knowledge Asset
await saveKnowledgeAsset(knowledgeAsset);
// 4. Record the update in the blockchain
const kaRegistry = await getKnowledgeAssetRegistry();
await kaRegistry.recordKnowledgeAssetUpdate(
knowledgeAssetUAL,
"TransactionCreated",
JSON.stringify({ transactionId })
);
return true;
} catch (error) {
console.error("Error updating Knowledge Asset:", error);
return false;
}
}
// Update Knowledge Asset with document verification from DKG
async function updateKnowledgeAssetWithVerificationDetails(
knowledgeAssetUAL,
documentType,
verificationResult,
verifierDetails
) {
try {
// 1. Get the Knowledge Asset from the DKG
const knowledgeAsset = await fetchKnowledgeAsset(knowledgeAssetUAL);
// 2. Find the document in the Knowledge Asset
const documentIndex = knowledgeAsset.documents.findIndex(doc => doc.type === documentType);
if (documentIndex >= 0) {
// 3. Update the document verification status
knowledgeAsset.documents[documentIndex].verificationStatus = 'VERIFIED';
knowledgeAsset.documents[documentIndex].verificationResult = verificationResult;
knowledgeAsset.documents[documentIndex].verifierDetails = verifierDetails;
knowledgeAsset.documents[documentIndex].verifiedAt = Date.now();
}
// 4. Save the updated Knowledge Asset
await saveKnowledgeAsset(knowledgeAsset);
return true;
} catch (error) {
console.error("Error updating Knowledge Asset with verification:", error);
return false;
}
}
// Other Knowledge Asset update functions follow similar patterns
// These functions ensure that the DKG Knowledge Assets reflect
// the current state of blockchain transactions, providing a
// complete solution that combines on-chain enforcement with
// off-chain data and document management
Blockchain Event Listening
// Listen for blockchain events and update DKG
function listenForBlockchainEvents() {
// Get contract instances
const factory = getTradeAgreementContract();
// Listen for document verification events
factory.on("DocumentsVerified", async (transactionId) => {
// 1. Get the Knowledge Asset UAL
const kaRegistry = await getKnowledgeAssetRegistry();
const knowledgeAssetUAL = await kaRegistry.getKnowledgeAssetForTransaction(transactionId);
// 2. Update the Knowledge Asset with verification status
await updateKnowledgeAssetWithAllDocumentsVerified(knowledgeAssetUAL);
});
// Listen for payment events
factory.on("PaymentConfirmed", async (transactionId, amount) => {
// 1. Get the Knowledge Asset UAL
const kaRegistry = await getKnowledgeAssetRegistry();
const knowledgeAssetUAL = await kaRegistry.getKnowledgeAssetForTransaction(transactionId);
// 2. Update the Knowledge Asset with payment information
await updateKnowledgeAssetWithPayment(
knowledgeAssetUAL,
amount,
"CONFIRMED"
);
});
// Add more event listeners as needed
}
Conclusion
The SKYOCEAN smart contract architecture provides a scalable, cost-effective solution for managing trade agreements, document verification, financier participation, and transaction execution. By using a reusable contract design rather than deploying new contracts for each transaction, the platform dramatically reduces gas costs while maintaining the security and immutability benefits of blockchain technology.
The integration between smart contracts and the DKG Knowledge Assets creates a powerful system where:
- Smart contracts handle the enforcement of transaction rules and financial flows
- Knowledge Assets store the rich, detailed data associated with each transaction, including document verification
- The Knowledge Asset Registry provides the link between on-chain and off-chain components
- API functions enable seamless interaction with both systems
Key points to understand about this architecture:
- Document verification happens in the DKG system, not in the smart contracts
- Smart contracts record the verification status after being notified of changes in the DKG
- This bidirectional flow ensures that both systems remain synchronized
- The Web Application orchestrates the communication between DKG and blockchain
This architecture enables the SKYOCEAN platform to handle complex trade financing scenarios while providing transparency, security, and efficiency to all participants in the ecosystem.
Note on Solidity Syntax Highlighting:
If Solidity code blocks appear as plain text, ensure your markdown renderer is configured to highlight Solidity. For example, in Jekyll, verify that your _config.yml is set up to use Rouge (or PrismJS with a solidity plugin) for syntax highlighting.