Python
from boa3.builtin.contract import Nep17TransferEvent, abort
@metadata
def manifest_metadata() -> NeoMetadata:
meta = NeoMetadata()
meta.author = "coz"
return meta
OWNER = UInt160(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
TOKEN_TOTAL_SUPPLY = 100_000_000 * 100_000_000 # 10m total supply * 10^8 (decimals)
# Events
on_transfer = Nep17TransferEvent
@public
def transfer(from_address: UInt160, to_address: UInt160, amount: int, data: Any) -> bool:
assert len(from_address) == 20 and len(to_address) == 20
assert amount >= 0
# The function MUST return false if the from account balance does not have enough tokens to spend.
from_balance = get(from_address).to_int()
if from_balance < amount:
return False
# The function should check whether the from address equals the caller contract hash.
if from_address != calling_script_hash:
if not check_witness(from_address):
return False
# skip balance changes if transferring to yourself or transferring 0 cryptocurrency
if from_address != to_address and amount != 0:
if from_balance == amount:
delete(from_address)
else:
put(from_address, from_balance - amount)
to_balance = get(to_address).to_int()
put(to_address, to_balance + amount)
on_transfer(from_address, to_address, amount)
# if the to_address is a smart contract, it must call the contract's onPayment method
post_transfer(from_address, to_address, amount, data)
return True
C#
using Neo;
using Neo.SmartContract;
using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Attributes;
using Neo.SmartContract.Framework.Native;
using Neo.SmartContract.Framework.Services;
using System;
using System.Numerics;
namespace Desktop
{
[ManifestExtra("Author", "Neo")]
[ManifestExtra("Email", "dev@neo.org")]
[ManifestExtra("Description", "This is a contract example")]
[ContractSourceCode("https://github.com/neo-project/neo-devpack-dotnet/tree/master/src/Neo.SmartContract.Template")]
public class Contract1 : SmartContract
{
//TODO: Replace it with your own address.
[InitialValue("NiNmXL8FjEUEs1nfX9uHFBNaenxDHJtmuB", ContractParameterType.Hash160)]
static readonly UInt160 Owner = default;
private static bool IsOwner() => Runtime.CheckWitness(Owner);
// When this contract address is included in the transaction signature,
// this method will be triggered as a VerificationTrigger to verify that the signature is correct.
// For example, this method needs to be called when withdrawing token from the contract.
public static bool Verify() => IsOwner();
// TODO: Replace it with your methods.
public static string MyMethod()
{
return Storage.Get(Storage.CurrentContext, "Hello");
}
public static void _deploy(object data, bool update)
{
if (update) return;
// It will be executed during deploy
Storage.Put(Storage.CurrentContext, "Hello", "World");
}
public static void Update(ByteString nefFile, string manifest)
{
if (!IsOwner()) throw new Exception("No authorization.");
ContractManagement.Update(nefFile, manifest, null);
}
public static void Destroy()
{
if (!IsOwner()) throw new Exception("No authorization.");
ContractManagement.Destroy();
}
}
}
Go
package nep17Contract
var (
token nep17.Token
ctx storage.Context
)
// initializes the Token Interface and storage context
func init() {
token = nep17.Token{
...
Name: "Nep17 example",
Owner: util.FromAddress("NdHjSPVnw99RDMCoJdCnAcjkE23gvqUeg2"),
TotalSupply: 10000000000000000
}
ctx = storage.GetContext()
}
// Transfer token from one user to another
func (t Token) Transfer(ctx storage.Context, from, to interop.Hash160, amount int, data interface{}) bool {
amountFrom := t.CanTransfer(ctx, from, to, amount)
if amountFrom == -1 {
return false
}
if amountFrom == 0 {
storage.Delete(ctx, from)
}
if amountFrom > 0 {
diff := amountFrom - amount
storage.Put(ctx, from, diff)
}
amountTo := getIntFromDB(ctx, to)
totalAmountTo := amountTo + amount
storage.Put(ctx, to, totalAmountTo)
runtime.Notify("Transfer", from, to, amount)
if to != nil && management.GetContract(to) != nil {
contract.Call(to, "onNEP17Payment", contract.All, from, amount, data)
}
return true
}
Typescript
import { SmartContract} from '@neo-one/smart-contract';
export class NEP17Contract extends SmartContract {
public readonly properties = {
name: 'NEO•ONE NEP17 Example',
groups: [],
trusts: '*',
permissions: [],
};
public readonly name = 'NEO•ONE NEP17 Example';
public readonly decimals = 8;
private readonly notifyTransfer = createEventNotifier<Address | undefined, Address | undefined, Fixed<8>>(
'Transfer', 'from', 'to', 'amount',
);
public transfer(from: Address, to: Address, amount: Fixed<8>, data?: any): boolean {
if (amount < 0) {throw new Error(`Amount must be greater than 0: ${amount}`);}
const fromBalance = this.balanceOf(from);
if (fromBalance < amount) { return false; }
const contract = Contract.for(to);
if (contract !== undefined && !Address.isCaller(to)) {
const smartContract = SmartContract.for<TokenPayableContract>(to);
if (!smartContract.approveReceiveTransfer(from, amount, this.address)) {
return false;
}
}
const toBalance = this.balanceOf(to);
this.balances.set(from, fromBalance - amount);
this.balances.set(to, toBalance + amount);
this.notifyTransfer(from, to, amount);
if (contract !== undefined) {
const smartContract = SmartContract.for<TokenPayableContract>(to);
smartContract.onNEP17Payable(from, amount, data);
}
return true;
}
}
Java
package io.neow3j.examples.contractdevelopment.contracts;
import static io.neow3j.devpack.StringLiteralHelper.addressToScriptHash;
import io.neow3j.devpack.*
@ManifestExtra(key = "name", value = "FungibleToken")
@ManifestExtra(key = "author", value = "AxLabs")
@SupportedStandards("NEP-17")
@Permission(contract = "fffdc93764dbaddd97c48f252a53ea4643faa3fd") // ContractManagement
public class FungibleToken {
static final Hash160 owner = addressToScriptHash("NM7Aky765FG8NhhwtxjXRx7jEL1cnw7PBP");
@DisplayName("Transfer")
static Event3Args onTransfer;
static final int initialSupply = 200_000_000;
static final int decimals = 8;
static final String assetPrefix = "asset";
static final String totalSupplyKey = "totalSupply";
static final StorageContext sc = Storage.getStorageContext();
static final StorageMap assetMap = sc.createMap(assetPrefix);
public static String symbol() {
return "FGT";
}
public static int decimals() {
return decimals;
}
public static int totalSupply() {
return getTotalSupply();
}
static int getTotalSupply() {
return Storage.getInteger(sc, totalSupplyKey);
}
public static boolean transfer(Hash160 from, Hash160 to, int amount, Object[] data)
throws Exception {
if (!Hash160.isValid(from) || !Hash160.isValid(to)) {
throw new Exception("From or To address is not a valid address.");
}
if (amount < 0) {
throw new Exception("The transfer amount was negative.");
}
if (!Runtime.checkWitness(from) && from != Runtime.getCallingScriptHash()) {
throw new Exception("Invalid sender signature. The sender of the tokens needs to be "
+ "the signing account.");
}
if (getBalance(from) < amount) {
return false;
}
if (from != to && amount != 0) {
deductFromBalance(from, amount);
addToBalance(to, amount);
}
onTransfer.fire(from, to, amount);
if (ContractManagement.getContract(to) != null) {
Contract.call(to, "onNEP17Payment", CallFlags.All, data);
}
return true;
}
public static int balanceOf(Hash160 account) throws Exception {
if (!Hash160.isValid(account)) {
throw new Exception("Argument is not a valid address.");
}
return getBalance(account);
}
@OnDeployment
public static void deploy(Object data, boolean update) throws Exception {
throwIfSignerIsNotOwner();
if (!update) {
if (Storage.get(sc, totalSupplyKey) != null) {
throw new Exception("Contract was already deployed.");
}
// Initialize supply
Storage.put(sc, totalSupplyKey, initialSupply);
// And allocate all tokens to the contract owner.
assetMap.put(owner.toByteArray(), initialSupply);
}
}
public static void update(ByteString script, String manifest) throws Exception {
throwIfSignerIsNotOwner();
if (script.length() == 0 && manifest.length() == 0) {
throw new Exception("The new contract script and manifest must not be empty.");
}
ContractManagement.update(script, manifest);
}
public static void destroy() throws Exception {
throwIfSignerIsNotOwner();
ContractManagement.destroy();
}
@OnVerification
public static boolean verify() throws Exception {
throwIfSignerIsNotOwner();
return true;
}
/**
* Gets the address of the contract owner.
*
* @return the address of the contract owner.
*/
public static Hash160 contractOwner() {
return owner;
}
private static void throwIfSignerIsNotOwner() throws Exception {
if (!Runtime.checkWitness(owner)) {
throw new Exception("The calling entity is not the owner of this contract.");
}
}
private static void addToBalance(Hash160 key, int value) {
assetMap.put(key.toByteArray(), getBalance(key) + value);
}
private static void deductFromBalance(Hash160 key, int value) {
int oldValue = getBalance(key);
if (oldValue == value) {
assetMap.delete(key.toByteArray());
} else {
assetMap.put(key.toByteArray(), oldValue - value);
}
}
private static int getBalance(Hash160 key) {
return assetMap.getInteger(key.toByteArray());
}
}
Learn More