# 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.

```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
}
```