---
# System prepended metadata

title: Kaufmich KM$

---

# Kaufmich KM$

--- 

### 2 Transaction types

1. Transfer transaction
2. Mass transfer transaction

---

#### Transfer transaction 

```
{
  "amount": 1001, // KM$ 10.01 
  "attachment": "txId:101",
  "sender": "3PN2bVFxJjgudPKqEGZ41TVsD5ZJmxqnPSu", // user
  "assetId": "7uncmN7dZfV3fYVvNdYTngrrbamPYMgwpDnYG1bGy6nA", // KM$
  "recipient": "3PFmoN5YLoPNsL4cmNGkRxbUKrUVntwyAhf", // escort
  ... 
}
```
---

#### Mass transfer transaction

```
{
  "totalAmount": 1000,
  "attachment": "txId:102",
  "sender": "3PN2bVFxJjgudPKqEGZ41TVsD5ZJmxqnPSu", // user
  "assetId": "Fx2rhWK36H1nfXsiD4orNpBm2QG1JrMhx3eUcPVcoZm2", // another KM$
  "transfers": [
    {
      "recipient": "3PFmoN5YLoPNsL4cmNGkRxbUKrUVntwyAhf", // escort
      "amount": 970 // KM$ 9.70
    },
    {
      "recipient": "3PGNLwUG2GPpw74teTAxXFLxgFt3T2uQJsF", // ideawise
      "amount": 30 // KM$ 0.30 commission
    },
  ],
  ...
}
```

The implication is that before doing a transaction, we have to know what the commission fee is. We use that info to split the transaction into 2 transactions: for the *escort* and *ideawise*. Then we bundle both transactions with the mass transfer transaction. Then a script in the blockchain checks for the conditions (correct amounts, commission, recipient of beneficiary etc.). 

Note: it is easy to fetch the commission fee from the blockchain.

---

#### Asset script

Whenever an asset is created, a "smart contract" or `asset script` is attached to the asset. There is basically a `hook` that calls the script at any transaction.

The `script` has two outcomes:
1. **allow** the transaction
2. **deny** the transaction

It checks for various conditions on recipient and amount.

> Any transaction to `Compay` or `ideawise` does not incur a commission fee, thus a simple `transfer transaction` is allowed.

> Any transaction to an "escort" or another "user" needs to be a `mass transfer transaction`, where a smaller part of the total transaction (KM$) is paid as a commission to a beneficiary (Ideawise) ([v2](https://github.com/dexterslabor/kaufmich/tree/v2): and another commission to a "karma" pot).

The % fee can be updated at any time by the admin.

The trading of an asset can be frozen at any time by the admin.

The trading of an asset can be un-frozen at any time by the admin.

---

#### Data transaction

A `data transaction` updates a key-value store that is associated with an account. We will use the asset issuer's account (Compay). The `asset script` reads that storage. 

> The size of an account data storage is unlimited.

Example data:
`(assetId, feePercentage)`
`(assetIdFrozen, boolean)`


---

### Reference

Example of the custom asset script written for Kaufmich (V2).

Note: difference to v1 is a major refactoring but also a new feature. It is possible to send a commission fee to ideawise AND a "karma" pot.

![](https://i.imgur.com/bGCXUns.png)


```typescript
# Token owners can transfer asset only if they pay a commission fee to a beneficiary address
# The fee is defined by the token issuer in a data transaction
# (key, value) = (assetId, feePercentage)

{-# STDLIB_VERSION 3 #-}
{-# CONTENT_TYPE EXPRESSION #-}
{-# SCRIPT_TYPE ASSET #-}

##### Constants #####

let errorAssetWithoutIssuer = "asset does not have issuer"
let dataTxKeyBeneficiary = "beneficiary"
let dataTxKeyFrozen = "frozen"
let dataTxKeyKarma = "karma"

##### Getters #####
func getAssetIssuerAddress(assetId: ByteVector) = {
    let asset = assetInfo(assetId)
    match(asset) {
        case asset: Asset => {
            let issuerAddress = addressFromPublicKey(asset.issuerPublicKey)
            issuerAddress
        }
        case _ => throw(errorAssetWithoutIssuer)
    }
}

func getAddressFromDataTx(issuerAddress: Address, dataTxKey: String) = {
    let beneficiaryAddressString = getStringValue(issuerAddress, dataTxKey)
    let beneficiaryAddress = addressFromString(beneficiaryAddressString)
    beneficiaryAddress
}

func getBeneficiaryAddress(issuerAddress: Address) = {
    let beneficiaryAddress = getAddressFromDataTx(issuerAddress, dataTxKeyBeneficiary)
    beneficiaryAddress
}

func getKarmaAdddress(issuerAddress: Address) = {
    let karmaAddress = getAddressFromDataTx(issuerAddress, dataTxKeyKarma)
    karmaAddress
}

##### Permission helpers ######

func checkAssetIsFrozen(assetId: ByteVector) = {
    let asset = assetInfo(assetId)
    match(asset) {
        case asset: Asset => {
            let issuerAddress = addressFromPublicKey(asset.issuerPublicKey)
            let isFrozen = getBooleanValue(issuerAddress, dataTxKeyFrozen)
            isFrozen
        }
        case _ => throw(errorAssetWithoutIssuer)
    }
}

func checkFeeRequired(assetId: ByteVector, senderAddress: Address) = {
    let issuerAddress = getAssetIssuerAddress(assetId)
    let beneficiaryAddress = getBeneficiaryAddress(issuerAddress)
    
    # if the sender is the creator OR beneficiary, do not apply commission fee calculation
    if(senderAddress == issuerAddress || senderAddress == beneficiaryAddress) then {
        false
    } else {
        true 
    }
}

# Check that commission fee is above threshold
# The fee is defined by the token issuer in a data transaction
# (key, value) = (assetId, feePercentage)
func checkFeeAmountCondition(issuerAddress: Address, assetId: ByteVector, massTransferTransaction: MassTransferTransaction) = {
    # check commission fee on first transaction in mass transfer
    let feePercentage = getIntegerValue(issuerAddress, toBase58String(assetId))
    let firstAmount = massTransferTransaction.transfers[0].amount
    let secondAmount = massTransferTransaction.transfers[1].amount
    let totalFee = firstAmount + secondAmount
    # no floating point math that's why 100 is on left side and not divisor on right side of equation
    let totalFeeConditionIsMet = totalFee * 100 >= massTransferTransaction.totalAmount * feePercentage

    # TODO change key for karma fee
    # probably string operation is best appraoch
    # getIntegerValue(issuerAddress, toBase58String(`assetIdK`))
    let karmaFeePercentage = getIntegerValue(issuerAddress, toBase58String(assetId))
    let karmaFeeConditionIsMet = totalFee * 100 >= secondAmount * karmaFeePercentage

    let feeAmountCondition = totalFeeConditionIsMet == karmaFeeConditionIsMet
    feeAmountCondition
}

# checks that the first transaction in the batch goes to the beneficiary address
func checkFeeRecipientCondition(issuerAddress: Address, massTransferTransaction: MassTransferTransaction) = {
    let beneficiaryAddress = getBeneficiaryAddress(issuerAddress)
    let firstRecipient = massTransferTransaction.transfers[0].recipient
    let firstFeeRecipientCondition = beneficiaryAddress == firstRecipient

    let karmaAddress = getKarmaAdddress(issuerAddress)
    let secondRecipient = massTransferTransaction.transfers[1].recipient
    let secondFeeRecipientCondition = karmaAddress == secondRecipient

    let feeRecipientConditions = karmaAddress == secondRecipient
    feeRecipientConditions
}

func checkMassTxForConditions(massTransferTransaction: MassTransferTransaction, assetId: ByteVector) = {
    let issuerAddress = getAssetIssuerAddress(assetId)

    let feeRecipientCondition = checkFeeRecipientCondition(issuerAddress, massTransferTransaction)
    let feeAmountCondition = checkFeeAmountCondition(issuerAddress, assetId, massTransferTransaction)

    let allConditions = feeRecipientCondition && feeAmountCondition
    allConditions
}

##### MAIN FUNCTION OF ASSET SCRIPT #####

match tx {
    case transferTransaction: TransferTransaction => {
        let assetId = extract(transferTransaction.assetId)
        let senderAddress = transferTransaction.sender
        let feeRequired = checkFeeRequired(assetId, senderAddress)

        if(feeRequired) 
            # if fee is required then block transfer transaction
            then false
            # if fee is not require then let the transaction go through
            else true
    }
    # a mass transfer is a batch transaction
    
    case massTransferTransaction: MassTransferTransaction => {
        let assetId = extract(massTransferTransaction.assetId)
        let isFrozen = checkAssetIsFrozen(assetId)
        if(isFrozen)
            # block transaction
            then false else {
            let senderAddress = massTransferTransaction.sender
            let feeRequired = checkFeeRequired(assetId, senderAddress)
            if(feeRequired) 
                then {
                    checkMassTxForConditions(massTransferTransaction, assetId)
                } else {
                    # if fee is not required then let the transaction pass without checks
                    true
                }
            }
    }
    # all other transactions are deactivated
    case _ => false
}
```