# Stablecoins integration (mainnet design)

Konnex is a robot‑native network where every task, escrow, penalty, and reward settles in stablecoins by default. Stablecoins provide a predictable payment rail for machine commerce, while KNX remains the network token for validator security, governance, and fees.

## Stablecoin‑Native Flow (overview)

![Stablecoin settlement flow](/files/YYo9amfceFHHjMnODARc)

The flow shows:

* Payer tops up (optionally via card on‑/off‑ramp) and locks stablecoins into the task escrow.
* Executor submits PoPW evidence to Validators; a ScoreRoot is posted.
* Escrow settles: payouts or refunds (and penalties) are released; a small fee can be routed to a treasury/buyback in KNX.

## Role Split: Stablecoins vs KNX

* Stablecoins — settlement currency for escrows, payouts, penalties; supports fiat on/off‑ramps and cross‑chain liquidity.
* KNX — staking, governance, and network fees; a small fee from stablecoin flows is auto‑swapped to KNX (buyback/treasury).

## Contract Settlement Types (Stablecoin‑Native)

* Fixed‑price — escrow a single stablecoin amount and release on success.
* Metered — usage‑based debits (seconds, meters, frames) from escrow.
* Milestone — staged stablecoin releases across checkpoints.

Minimum task fields: `rewardStable`, `stakeStable`, `deadline`, `penaltyStable`, `PoPW` requirement bound to JobID.

## PoPW‑Linked Settlement & Penalties

1. Lock — payer funds on‑chain stablecoin escrow.
2. Perform — executor records sensor evidence (GPS, camera, IMU, torque, temperature).
3. Prove — submit PoPW bundle referencing JobID and deadline.
4. Verify — validators publish a ScoreRoot (pass/fail + metrics).
5. Settle — on pass, release stablecoins; on fail, apply `penaltyStable` and refunds (fees in KNX).

## Multi‑Token Staking & Robotic Bonds

* KNX stake — aligns executors with validator security and enables slashing.
* Stablecoin bond — compensates counterparties for non‑performance.

Third‑party stakers can supply stablecoins to robotic bond pools and earn yield, with risk priced by reputation. Required stablecoin stake roughly scales as `S = kappa / sqrt(T)` where `T` is trust score.

## Cross‑Chain Liquidity

Stablecoins from other chains can be bridged into Konnex via approved routes. Tasks are chain‑agnostic to source funds.

## Card On/Off‑Ramp

1. Charge — pay with card in app.
2. Mint — processor converts fiat to stablecoins to the payer’s Konnex address.
3. Escrow — stablecoins locked in the task contract.
4. Payout — after PoPW verification, stablecoins are released.
5. Redeem/Spend — spend via card, redeem to bank, or reuse on‑chain.

## Compliance by Design

* Optional KYC/AML gates (market/DAO configurable).
* Full audit trail of packets, PoPW, and stablecoin flows.

## Developer Surface (API Sketch)

* `lock(jobId, rewardStable, stakeStable)`
* `prove(jobId, PoPWBundleRef)`
* `settle(jobId)`
* `bond(jobId, amountStable)`

## Economics & Fee Flywheel

* Stablecoins for users, KNX for the network.
* Auto‑buyback: small fee on stablecoin flows swaps to KNX for treasury/burn.

## Key Metrics

* Latency: prove → stablecoin release (target single‑block after validation).
* Disputes: <1% and auto‑resolved.
* Velocity: avg stablecoin tx/robot/day.
* Card share: tasks funded via on‑ramp.
* Collateral efficiency: required `stakeStable` falls as trust `T` rises.

## Solidity Contracts

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

interface IScoreRegistry {
    // Returns (pass, score, scoreRoot) for a given jobId
    function getVerificationResult(bytes32 jobId) external view returns (bool, uint256, bytes32);
}

interface IAccessController {
    function isAllowed(address user) external view returns (bool);
}

interface IFeeCollector {
    // Stablecoin fee receiver used for KNX buyback/treasury flows
    function receiveStableFee(address token, uint256 amount) external;
}

contract FeeCollector is IFeeCollector, Ownable {
    using SafeERC20 for IERC20;

    event StableFeeReceived(address indexed token, uint256 amount);
    event Sweep(address indexed token, address indexed to, uint256 amount);

    // Receives fees in stablecoin; swapping to KNX is implemented off this contract (routers/aggregators vary by deployment)
    function receiveStableFee(address token, uint256 amount) external override {
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
        emit StableFeeReceived(token, amount);
    }

    // Owner can sweep accumulated balances to treasury/buyback executor as needed
    function sweep(address token, address to, uint256 amount) external onlyOwner {
        IERC20(token).safeTransfer(to, amount);
        emit Sweep(token, to, amount);
    }
}

contract KonnexSettlementManager is ReentrancyGuard, Ownable {
    using SafeERC20 for IERC20;

    enum SettlementType {
        FixedPrice,
        Metered
        // Milestone can be added with indexed amounts per checkpoint
    }

    enum JobStatus {
        None,
        Locked,
        Proven,
        Settled
    }

    struct Job {
        // Core IDs and actors
        bytes32 jobId;
        address payer;
        address executor;
        // Economic params (stablecoin‑native)
        uint256 rewardStable;      // gross reward escrowed by payer (stablecoins)
        uint256 stakeStable;       // executor stake/bond amount in stablecoins (can come from third‑party stakers)
        uint256 penaltyStable;     // penalty applied on fail, funded from stake/bonds
        uint256 deadline;          // unix deadline for proof submission
        SettlementType settlementType;
        // Lifecycle
        JobStatus status;
        bytes32 proofRef;          // e.g., PoPW CID/reference
        // Metered: amount debited from escrow over time (<= rewardStable)
        uint256 meteredDebited;
        // Accounting
        uint256 totalThirdPartyBond; // total bonded by third‑party stakers for this job
    }

    struct BondStake {
        uint256 amount;
        bool claimed;
    }

    IERC20 public immutable stableToken;  // primary settlement token (e.g., USDC)
    IScoreRegistry public scoreRegistry;  // validator score/decision source
    IAccessController public accessGate;  // optional KYC/ACL gate (can be address(0))
    IFeeCollector public feeCollector;    // receives stable fees for KNX buyback/treasury

    // Fees (basis points) taken from executor payout on pass; sent in stable to FeeCollector
    uint16 public stableFeeBps = 50; // 0.50%
    uint16 public bondYieldBps = 25; // 0.25% yield to third‑party bond providers on pass (taken from reward escrow, before executor payout)
    uint16 private constant BPS_DENOM = 10_000;

    // jobId => Job
    mapping(bytes32 => Job) public jobs;

    // jobId => list of bond stakers (to enable distribution on settlement)
    mapping(bytes32 => address[]) private bondStakers;
    // jobId => staker => BondStake
    mapping(bytes32 => mapping(address => BondStake)) public bondLedger;

    // Events
    event Locked(bytes32 indexed jobId, address indexed payer, address indexed executor, SettlementType stype, uint256 reward, uint256 stake, uint256 penalty, uint256 deadline);
    event Bonded(bytes32 indexed jobId, address indexed staker, uint256 amount);
    event Proved(bytes32 indexed jobId, address indexed executor, bytes32 proofRef);
    event MeteredDebit(bytes32 indexed jobId, uint256 amount, uint256 totalDebited);
    event Settled(bytes32 indexed jobId, bool pass, uint256 executorPayout, uint256 penaltyPaid, uint256 feePaid, uint256 bondYieldPaid);
    event AccessGateUpdated(address indexed gate);
    event ScoreRegistryUpdated(address indexed registry);
    event FeeCollectorUpdated(address indexed collector);
    event FeesUpdated(uint16 stableFeeBps, uint16 bondYieldBps);

    constructor(
        address stableToken_,
        address scoreRegistry_,
        address feeCollector_
    ) {
        require(stableToken_ != address(0) && scoreRegistry_ != address(0) && feeCollector_ != address(0), "zero addr");
        stableToken = IERC20(stableToken_);
        scoreRegistry = IScoreRegistry(scoreRegistry_);
        feeCollector = IFeeCollector(feeCollector_);
    }

    // Optional: enable/disable KYC/ACL markets
    function setAccessGate(address gate) external onlyOwner {
        accessGate = IAccessController(gate);
        emit AccessGateUpdated(gate);
    }

    function setScoreRegistry(address reg) external onlyOwner {
        require(reg != address(0), "zero addr");
        scoreRegistry = IScoreRegistry(reg);
        emit ScoreRegistryUpdated(reg);
    }

    function setFeeCollector(address collector) external onlyOwner {
        require(collector != address(0), "zero addr");
        feeCollector = IFeeCollector(collector);
        emit FeeCollectorUpdated(collector);
    }

    function setFees(uint16 stableFeeBps_, uint16 bondYieldBps_) external onlyOwner {
        require(stableFeeBps_ <= 500 && bondYieldBps_ <= 1000, "fee caps"); // sane caps
        stableFeeBps = stableFeeBps_;
        bondYieldBps = bondYieldBps_;
        emit FeesUpdated(stableFeeBps_, bondYieldBps_);
    }

    // lock: payer escrows reward; executor escrows stake (directly or via third‑party bonds)
    function lock(
        bytes32 jobId,
        address executor,
        uint256 rewardStable,
        uint256 stakeStable,
        uint256 penaltyStable,
        uint256 deadline,
        SettlementType stype
    ) external nonReentrant {
        require(jobs[jobId].status == JobStatus.None, "exists");
        require(executor != address(0) && rewardStable > 0 && deadline > block.timestamp, "invalid");
        _checkAccess(msg.sender);
        _checkAccess(executor);

        // Create job
        Job storage j = jobs[jobId];
        j.jobId = jobId;
        j.payer = msg.sender;
        j.executor = executor;
        j.rewardStable = rewardStable;
        j.stakeStable = stakeStable;
        j.penaltyStable = penaltyStable;
        j.deadline = deadline;
        j.settlementType = stype;
        j.status = JobStatus.Locked;

        // Transfer reward escrow from payer to contract
        stableToken.safeTransferFrom(msg.sender, address(this), rewardStable);

        // If executor supplies stake directly (not via bonds), transfer from executor now
        if (stakeStable > 0) {
            stableToken.safeTransferFrom(executor, address(this), stakeStable);
        }

        emit Locked(jobId, msg.sender, executor, stype, rewardStable, stakeStable, penaltyStable, deadline);
    }

    // Third‑party bond providers can contribute to executor's stake (robotic bonds)
    function bond(bytes32 jobId, uint256 amount) external nonReentrant {
        Job storage j = jobs[jobId];
        require(j.status == JobStatus.Locked, "job not lock");
        require(amount > 0, "zero");
        _checkAccess(msg.sender);

        // Pull funds
        stableToken.safeTransferFrom(msg.sender, address(this), amount);

        // Track staker and amount
        if (bondLedger[jobId][msg.sender].amount == 0) {
            bondStakers[jobId].push(msg.sender);
        }
        bondLedger[jobId][msg.sender].amount += amount;
        j.totalThirdPartyBond += amount;

        emit Bonded(jobId, msg.sender, amount);
    }

    // Metered debit during execution (e.g., usage‐based spending from payer escrow)
    function debitMetered(bytes32 jobId, uint256 amount) external nonReentrant {
        Job storage j = jobs[jobId];
        require(j.status == JobStatus.Locked || j.status == JobStatus.Proven, "bad status");
        require(j.settlementType == SettlementType.Metered, "not metered");
        require(msg.sender == j.payer, "only payer");
        require(j.meteredDebited + amount <= j.rewardStable, "exceeds escrow");
        j.meteredDebited += amount;
        emit MeteredDebit(jobId, amount, j.meteredDebited);
    }

    // prove: executor submits PoPW reference before deadline
    function prove(bytes32 jobId, bytes32 proofRef) external nonReentrant {
        Job storage j = jobs[jobId];
        require(j.status == JobStatus.Locked || j.status == JobStatus.Proven, "bad status");
        require(msg.sender == j.executor, "only exec");
        require(block.timestamp <= j.deadline, "deadline");
        j.proofRef = proofRef;
        j.status = JobStatus.Proven;
        emit Proved(jobId, msg.sender, proofRef);
    }

    // settle: checks validator decision and routes funds accordingly
    function settle(bytes32 jobId) external nonReentrant {
        Job storage j = jobs[jobId];
        require(j.status == JobStatus.Locked || j.status == JobStatus.Proven, "bad status");

        (bool pass,,) = scoreRegistry.getVerificationResult(jobId);

        uint256 totalStake = j.stakeStable + j.totalThirdPartyBond;
        uint256 executorPayout = 0;
        uint256 penaltyPaid = 0;
        uint256 feePaid = 0;
        uint256 bondYieldPaid = 0;

        if (pass) {
            // Calculate gross payout from reward escrow (metered tasks pay out only the debited portion)
            uint256 gross = (j.settlementType == SettlementType.Metered) ? j.meteredDebited : j.rewardStable;

            // Bond yield carved out from gross reward for stakers
            uint256 yieldForBonds = (j.totalThirdPartyBond > 0) ? (gross * bondYieldBps) / BPS_DENOM : 0;
            bondYieldPaid = yieldForBonds;

            // Fee on executor payout (after bond yield)
            uint256 baseForExec = gross - yieldForBonds;
            feePaid = (baseForExec * stableFeeBps) / BPS_DENOM;
            executorPayout = baseForExec - feePaid;

            // Distribute: bond yield to stakers pro‑rata, fee to FeeCollector, remainder to executor
            _payBondProRata(jobId, yieldForBonds, true /* include principal on pass */);
            if (feePaid > 0) {
                // Send fee in stablecoin to FeeCollector for KNX buyback/treasury
                stableToken.safeApprove(address(feeCollector), 0);
                stableToken.safeApprove(address(feeCollector), feePaid);
                feeCollector.receiveStableFee(address(stableToken), feePaid);
            }
            if (executorPayout > 0) {
                stableToken.safeTransfer(j.executor, executorPayout);
            }

            // Return executor's direct stake (if any)
            if (j.stakeStable > 0) {
                stableToken.safeTransfer(j.executor, j.stakeStable);
            }

            // Refund any unspent escrow to payer (metered remainder or dust)
            uint256 spent = gross;
            if (j.rewardStable > spent) {
                stableToken.safeTransfer(j.payer, j.rewardStable - spent);
            }
        } else {
            // Fail: apply penalty from total stake (executor stake + third‑party bonds), pay to payer
            uint256 slashed = j.penaltyStable > totalStake ? totalStake : j.penaltyStable;
            penaltyPaid = slashed;

            if (slashed > 0) {
                // Slash executor's direct stake first, then third‑party bonds
                uint256 fromExecStake = slashed > j.stakeStable ? j.stakeStable : slashed;
                if (fromExecStake > 0) {
                    j.stakeStable -= fromExecStake;
                    stableToken.safeTransfer(j.payer, fromExecStake);
                    slashed -= fromExecStake;
                }
                if (slashed > 0 && j.totalThirdPartyBond > 0) {
                    _slashBondProRata(jobId, slashed);
                    stableToken.safeTransfer(j.payer, slashed);
                }
            }

            // Refund full reward escrow to payer
            if (j.rewardStable > 0) {
                stableToken.safeTransfer(j.payer, j.rewardStable);
            }

            // Return any remaining direct stake to executor
            if (j.stakeStable > 0) {
                stableToken.safeTransfer(j.executor, j.stakeStable);
            }

            // Return remaining bonds (principal only, no yield) to stakers
            _payBondProRata(jobId, 0, true /* include principal */);
        }

        j.status = JobStatus.Settled;
        emit Settled(jobId, pass, executorPayout, penaltyPaid, feePaid, bondYieldPaid);

        // Zero out escrowed amounts to avoid double‑spends in any edge path
        j.rewardStable = 0;
        j.stakeStable = 0;
        j.totalThirdPartyBond = 0;
    }

    // Internal helpers

    function _checkAccess(address user) internal view {
        if (address(accessGate) != address(0)) {
            require(accessGate.isAllowed(user), "acl");
        }
    }

    function _payBondProRata(bytes32 jobId, uint256 yieldAmount, bool includePrincipal) internal {
        address[] memory stakers = bondStakers[jobId];
        if (stakers.length == 0) {
            return;
        }

        // Compute total principal remaining for this job
        uint256 totalPrincipal = 0;
        for (uint256 i = 0; i < stakers.length; i++) {
            totalPrincipal += bondLedger[jobId][stakers[i]].amount;
        }
        if (totalPrincipal == 0 && yieldAmount == 0) {
            return;
        }

        for (uint256 i = 0; i < stakers.length; i++) {
            address staker = stakers[i];
            BondStake storage b = bondLedger[jobId][staker];
            if (b.claimed || b.amount == 0) continue;

            uint256 principalShare = b.amount;
            uint256 yieldShare = (yieldAmount > 0 && totalPrincipal > 0) ? (yieldAmount * principalShare) / totalPrincipal : 0;

            uint256 payout = yieldShare + (includePrincipal ? principalShare : 0);
            if (payout > 0) {
                // Zero out before transfer to guard reentrancy
                b.claimed = true;
                b.amount = 0;
                stableToken.safeTransfer(staker, payout);
            }
        }
        // Clear staker list to free storage in future deployments (optional micro‑opt)
        delete bondStakers[jobId];
    }

    function _slashBondProRata(bytes32 jobId, uint256 slashAmount) internal {
        address[] memory stakers = bondStakers[jobId];
        if (stakers.length == 0 || slashAmount == 0) {
            return;
        }

        uint256 totalPrincipal = 0;
        for (uint256 i = 0; i < stakers.length; i++) {
            totalPrincipal += bondLedger[jobId][stakers[i]].amount;
        }
        if (totalPrincipal == 0) return;

        for (uint256 i = 0; i < stakers.length; i++) {
            address staker = stakers[i];
            BondStake storage b = bondLedger[jobId][staker];
            if (b.amount == 0) continue;
            uint256 slashShare = (slashAmount * b.amount) / totalPrincipal;
            if (slashShare > b.amount) slashShare = b.amount;
            b.amount -= slashShare;
        }
    }
}
```

### Notes

* The `KonnexSettlementManager` implements:
  * `lock(jobId, executor, rewardStable, stakeStable, penaltyStable, deadline, stype)` — payer escrows reward; executor may escrow stake directly; third‑party stakers add stake via `bond`.
  * `bond(jobId, amount)` — third‑party stakers add stablecoin bonds that secure execution and earn yield on pass.
  * `prove(jobId, proofRef)` — executor posts a PoPW bundle reference before `deadline`.
  * `debitMetered(jobId, amount)` — optional usage‑based debits for metered contracts.
  * `settle(jobId)` — reads validator decision from `IScoreRegistry`, applies penalties or releases funds, sends a stablecoin fee to `FeeCollector` for KNX buyback/treasury.
* The `FeeCollector` is a simple stablecoin sink. Teams typically wire this to a swap/buyback executor off‑chain or via per‑chain router adapters.
* To add milestones, track `uint256[] milestoneAmounts` and a checkpoint index per job, and debit per milestone similarly to `debitMetered`.


---

# 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.konnex.world/understand-konnex/stablecoins-integration.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.
