Developers
  • SWT PROTOCOL
    • Introduction
    • Accounts
    • Transactions
    • Fees
    • Smart Contracts
  • GETTING STARTED
    • Network Node
      • Introduction
      • STEP 1:RENTING A LINUX VPS
      • STEP 2:INSTALLATION OF PUTTY
      • STEP 3:INSTALLATION OF FILEZILLA
      • STEP 4:VPS CONNECTION AND LINUX CONFIGURATION WITH PUTTY (SSH)
      • STEP 5:HOW TO SECURE YOUR VPS
      • STEP 6:USING TMUX THROUGH PUTTY
      • STEP 7:UPLOAD OF THE SWT SOFTWARE WITH FILEZILLA
      • STEP 8:EXTRACT THE SWT SOFTWARE FOLDER
      • STEP 9:RUNNING THE SWT NODE THROUGH TMUX
      • STEP 10:RUNNING THE MONITORING TOOLS
  • API REFERENCE
    • Apache Thrift API
      • Transactions
      • Blocks
      • Smart contract
      • Tokens
      • Wallets
      • Sync info
      • Data structures
    • REST API
      • GetBlocks()
      • GetNodeInfo()
      • GetBalance()
      • GetWalletInfo()
      • GetTokenBalance()
      • GetTransactionsByWallet()
      • GetTransactionInfo()
      • GetEstimatedFee()
      • GetContractByAddress()
      • GetContractFromTransaction()
      • GetContractMethods()
      • ContractValidation()
      • ContractCall()
      • TransactionPack()
      • TransactionExec()
  • SMART CONTRACTS
    • Creating "Hello-world" Smart Contract
    • Smart Contract Methods
    • Smart Contract Standarts
      • Token Smart Contract
      • Escrow Smart Contract
      • Stable coin Token
  • HOW TO REST API
    • Introduction
    • Retrieve a balance from the blockchain
    • Request a specific transaction from the blockchain
    • Sending Transactions to the SWT Blockchain
    • Validating and deploying a Smart Contract with REST API
Powered by GitBook
On this page
  1. SMART CONTRACTS
  2. Smart Contract Standarts

Stable coin Token

Description

The code below is an example and requires further refinement for specific business tasks.

The contract below has the following capabilities

  1. Possibility to increase the number of tokens in circulation - additional issue

  2. Possibility of reducing the number of tokens in circulation (not less than zero) - burning

  3. The ability to block tokens on a specific account for a specific time

  4. The ability to block a certain number of tokens on a certain account for a certain time or until a certain time

  5. Complete stopping of the ability to transfer the token (all addresses)

package com.example.contract;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
 
import javax.management.RuntimeErrorException;
 
import java.util.Date;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import static java.math.BigDecimal.ZERO;
 
import com.credits.scapi.v3.SmartContract;
import com.credits.scapi.v0.ExtensionStandard;
 
public class StableCoinSharedTokenContract extends SmartContract implements ExtensionStandard {
 
    private final String owner;
    private final int decimal;
    HashMap<String, BigDecimal> balances;
    private String name;
    private String symbol;
    private BigDecimal totalCoins;
    private HashMap<String, Map<String, BigDecimal>> allowed;
    private boolean frozen;
	private HashMap<String, Date> frozenAccounts;
	private HashMap<String, PartialFreeze> frozenSums;
 
	private HashSet<String> burnable;
	private HashSet<String> permit_burn;
	private HashSet<String> permit_emit;
 
	class PartialFreeze implements Serializable {
		long until;
		BigDecimal sum;
 
		PartialFreeze(long until_ms, BigDecimal amount) {
			until = until_ms;
			sum = amount;
		}
	}
 
    public StableCoinSharedTokenContract() {
        super();
 
        // deploy time settings
        name = "Credits USD";
        symbol = "CUSD";
		decimal = 2;
		long initial = 1000L;
        totalCoins = new BigDecimal(initial).setScale(decimal, RoundingMode.FLOOR);
 
        owner = initiator;
        allowed = new HashMap<>();
        balances = new HashMap<>();
        balances.put(owner, totalCoins);
        frozen = false;
 
		frozenAccounts = new HashMap<String, Date>();
		frozenSums = new HashMap<String, PartialFreeze>();
		permit_burn = new HashSet<String>();
		permit_burn.add(owner);
		permit_emit = new HashSet<String>();
		permit_emit.add(owner);
		burnable = new HashSet<String>();
    }
 
    @Override
    public int getDecimal() {
        return decimal;
    }
 
    @Override
    public void register() {
        ensureIsNotFrozen(initiator);
        balances.putIfAbsent(initiator, ZERO.setScale(decimal, RoundingMode.FLOOR));
    }
 
    @Override
    public boolean setFrozen(boolean isFrozen) {
        if (!isOwner()) {
            throw new RuntimeException("only owner can change frozen state");
        }
        this.frozen = isFrozen;
        return true;
    }
 
    @Override
    public String getName() {
        return name;
    }
 
    @Override
    public String getSymbol() {
        return symbol;
    }
 
    @Override
    public String totalSupply() {
        return totalCoins.toString();
    }
 
    @Override
    public String balanceOf(String account) {
        return getTokensBalance(account).toString();
    }
 
    @Override
    public String allowance(String owner, String spender) {
        if (allowed.get(owner) == null) {
            return "0";
        }
        BigDecimal amount = allowed.get(owner).get(spender);
        return amount != null ? amount.toString() : "0";
    }
 
    @Override
    public boolean transfer(String to, String amount) {
        contractIsNotFrozen();
        ensureIsNotFrozen(initiator);
        ensureIsNotFrozen(to);
 
        if (!to.equals(initiator)) {
            BigDecimal decimalAmount = toBigDecimal(amount);
			ensureAllowedTransferFrom(initiator, decimalAmount);
			BigDecimal sourceBalance = getTokensBalance(initiator);
            BigDecimal targetTokensBalance = getTokensBalance(to);
            if(targetTokensBalance == null) {
                targetTokensBalance = ZERO.setScale(decimal, RoundingMode.FLOOR);
            }
            balances.put(initiator, sourceBalance.subtract(decimalAmount));
            balances.put(to, targetTokensBalance.add(decimalAmount));
        }
        return true;
    }
 
    @Override
    public boolean transferFrom(String from, String to, String amount) {
        contractIsNotFrozen();
        ensureIsNotFrozen(initiator);
        ensureIsNotFrozen(to);
        ensureIsNotFrozen(from);
 
        if (!from.equals(to)) {
            BigDecimal fromBalance = getTokensBalance(from);
            if(fromBalance == null ) {
                throw new RuntimeException(from + " is not a holder");
            }
            BigDecimal toBalance = getTokensBalance(to);
            if(toBalance == null) {
                toBalance = ZERO.setScale(decimal, RoundingMode.FLOOR);
            }
			BigDecimal decimalAmount = toBigDecimal(amount);
			ensureAllowedTransferFrom(from, decimalAmount);
 
            Map<String, BigDecimal> spender = allowed.get(from);
            if (spender == null || !spender.containsKey(initiator)) {
                throw new RuntimeException(initiator + " require allowance from " + from + " to transfer tokens");
            }
 
            BigDecimal allowTokens = spender.get(initiator);
            if (allowTokens.compareTo(decimalAmount) < 0) {
                throw new RuntimeException("maximum " + allowTokens + " tokens are allowed to transfer");
            }
 
            spender.put(initiator, allowTokens.subtract(decimalAmount));
            balances.put(from, fromBalance.subtract(decimalAmount));
            balances.put(to, toBalance.add(decimalAmount));
        }
        return true;
    }
 
    @Override
    public void approve(String spender, String amount) {
        ensureIsNotFrozen(initiator);
        ensureIsNotFrozen(spender);
 
        initiatorIsRegistered();
        BigDecimal decimalAmount = toBigDecimal(amount);
        Map<String, BigDecimal> initiatorSpenders = allowed.get(initiator);
        if (initiatorSpenders == null) {
            Map<String, BigDecimal> newSpender = new HashMap<>();
            newSpender.put(spender, decimalAmount);
            allowed.put(initiator, newSpender);
        } else {
            BigDecimal spenderAmount = initiatorSpenders.get(spender);
            initiatorSpenders.put(spender, spenderAmount == null ? decimalAmount : spenderAmount.add(decimalAmount));
        }
    }
 
    @Override
    public boolean burn(String amount) {
        if (!isOwner())
            throw new RuntimeException("can not burn tokens, only owner can");
 
        BigDecimal decimalAmount = toBigDecimal(amount);
        BigDecimal burnable = getTokensBalance(owner);
        if(burnable.compareTo(decimalAmount) < 0) {
            throw new RuntimeException("unable to burn " + amount + " tokens but only " + burnable);
        }
        totalCoins = totalCoins.subtract(decimalAmount);
        balances.put(owner, balances.get(owner).subtract(decimalAmount));
        return true;
    }
 
    public String getBurnAvail() {
        return getTokensBalance(owner).toString();
    }
 
	public void emit(String amount) {
        if (!isOwner())
            throw new RuntimeException("only owner can emit tokens");
 
		BigDecimal emitted = toBigDecimal(amount);
		if(emitted.compareTo(BigDecimal.ZERO) <= 0) {
            throw new RuntimeException("amount must be positive");
		}
        totalCoins = totalCoins.add(emitted);
        balances.put(owner, balances.get(owner).add(emitted));
	}
 
    public void payable(String amount, String currency) {
        throw new RuntimeException("unsupported operation: buy tokens");
    }
 
    @Override
    public String payable(BigDecimal amount, byte [] data) {
        throw new RuntimeException("unsupported method: obsolete");
    }
 
    @Override
    public boolean buyTokens(String amount) {
        throw new RuntimeException("unsupported operation: buy tokens");
    }
 
    private void contractIsNotFrozen() {
        if (frozen) throw new RuntimeException("unavailable action! The smart-contract is frozen");
    }
 
    private void initiatorIsRegistered() {
        if (!balances.containsKey(initiator))
            throw new RuntimeException("operation rejected, " + initiator + " is not a holder");
    }
 
    private BigDecimal toBigDecimal(String stringValue) {
        return new BigDecimal(stringValue).setScale(decimal, RoundingMode.FLOOR);
    }
 
    private BigDecimal getTokensBalance(String account) {
        if(!balances.containsKey(account)) {
            return ZERO;
        }
        return balances.get(account);
    }
 
    // extensions
 
    private boolean isOwner() {
        return initiator.equals(owner);
    }
 
    private void ensureIsNotFrozen(String account) {
        if (isAccountFrozen(account)) {
            throw new RuntimeException("account is frozen");
        }
    }
 
    public void freezeAccount(String account, long unix_time) {
		testFreezeBy(initiator);
		testFreezeOf(account);
 
		frozenAccounts.put(account, new Date(unix_time * 1000L));
    }
 
    public void defrostAccount(String account) {
		testFreezeBy(initiator);
		testFreezeOf(account);
 
		if(frozenAccounts.containsKey(account)) {
            frozenAccounts.remove(account);
        }
    }
 
    public boolean isAccountFrozen(String account) {
        if(! frozenAccounts.containsKey(account)) {
            return false;
        }
        Date now = new Date(getBlockchainTimeMills());
        if(frozenAccounts.get(account).before(now)) {
            frozenAccounts.remove(account);
            return false;
        }
        return true;
    }
 
    public String getAccountDefrostDate(String account) {
        if(! frozenAccounts.containsKey(account)) {
            return "is not frozen";
        }
        return printDate(frozenAccounts.get(account));
    }
 
	public void freezeSum(String account, long unix_time, String amount) {
		testFreezeBy(initiator);
		testFreezeOf(account);
 
		BigDecimal sum = toBigDecimal(amount);
		if(sum.compareTo(BigDecimal.ZERO) <= 0) {
			throw new RuntimeException("amount must be positive");
		}
        frozenSums.put(account, new PartialFreeze(unix_time * 1000L, sum));
    }
 
	private void ensureAllowedTransferFrom(String account, BigDecimal sum) {
		BigDecimal total = getTokensBalance(account);
		if(frozenSums.containsKey(account)) {
			PartialFreeze p = frozenSums.get(account);
			if(getBlockchainTimeMills() <= p.until) {
				total = total.subtract(p.sum);
			}
		}
		if(total.compareTo(sum) < 0) {
			throw new RuntimeException("insufficient tokens to transfer from");
		}
	}
 
	public String getFrozenSumValue(String account) {
		PartialFreeze p = frozenSums.get(account);
		if(p == null) {
			return "";
		}
		return p.sum.toString();
	}
 
	public String getFrozenSumDate(String account) {
		PartialFreeze p = frozenSums.get(account);
		if(p == null) {
			return "";
		}
		return printDate(new Date(p.until));
	}
 
	public void optimizeFrozenTokens() {
		long now = getBlockchainTimeMills();
		frozenSums.values().removeIf((p) -> { return p.until < now; });
	}
 
    private String printDate(Date d) {
        SimpleDateFormat fmt = new SimpleDateFormat("dd.MM.yyyy HH:mm::ss");
        return fmt.format(d);
	}
	
	// support pernmissions to burn and emit tokens
 
	private void updateSet(HashSet<String> set, String key, boolean on) {
		if(on) {
			if(!set.contains(key)) {
				set.add(key);
			}
		}
		else {
			if(set.contains(key)) {
				set.remove(key);
			}
		}
	}
 
	public void permitBurnOn(String account, boolean allow) {
		if(!isOwner()) {
			throw new RuntimeException("Only owner can do this");
		}
		updateSet(burnable, account, allow);
	}
 
	public void permitBurnBy(String account, boolean allow) {
		if(!isOwner()) {
			throw new RuntimeException("Only owner can do this");
		}
		updateSet(permit_burn, account,	allow);
	}
 
	public void permitEmit(String account, boolean allow) {
		if(!isOwner()) {
			throw new RuntimeException("Only owner can do this");
		}
		updateSet(permit_emit, account, allow);
	}
 
	private void testBurnOn(String account) {
		if(!burnable.contains(account)) {
			throw new RuntimeException("Burning on this account is not permitted");
		}
	}
 
	private void testBurnBy(String account) {
		if(isOwner()) {
			return;
		}
		if(!permit_burn.contains(account)) {
			throw new RuntimeException("Burning by this account is not permitted");
		}
	}
 
	private void testEmitBy(String account) {
		if(isOwner()) {
			return;
		}
		if(!permit_emit.contains(account)) {
			throw new RuntimeException("Emit by this account is not permitted");
		}
	}
 
	private void testFreezeOf(String account) {
		if(isOwner()) {
			return;
		}
		if(!burnable.contains(account)) {
			throw new RuntimeException("Freezing this account is not permitted");
		}
	}
 
	private void testFreezeBy(String account) {
		if(isOwner()) {
			return;
		}
		if(!permit_burn.contains(account)) {
			throw new RuntimeException("Freezing by this account is not permitted");
		}
	}
 
	public void burnOn(String account, String amount) {
        testBurnBy(initiator);
		testBurnOn(account);
 
        BigDecimal decimalAmount = toBigDecimal(amount);
        BigDecimal burnable = getTokensBalance(account);
        if(burnable.compareTo(decimalAmount) < 0) {
            throw new RuntimeException("unable to burn " + amount + " tokens but only " + burnable);
        }
        totalCoins = totalCoins.subtract(decimalAmount);
        balances.put(account, balances.get(account).subtract(decimalAmount));
	}
 
	public void emitSelf(String amount) {
		testEmitBy(initiator);
		
		BigDecimal emitted = toBigDecimal(amount);
		if(emitted.compareTo(BigDecimal.ZERO) <= 0) {
            throw new RuntimeException("amount must be positive");
		}
		totalCoins = totalCoins.add(emitted);
		if(balances.containsKey(initiator)) {
			emitted = emitted.add(balances.get(initiator));
		}
        balances.put(initiator, emitted);
	}
}
PreviousEscrow Smart ContractNextIntroduction

Last updated 1 year ago