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 contracts onPayment
post_transfer(from_address, to_address, amount, data)
return True
C#
using Neo.SmartContract.Framework;
[DisplayName("Nep17 example")]
[ManifestExtra("Author", "Neo Core Dev")]
public class Nep17Contract : SmartContract
{
#region Token Settings
static readonly ulong TotalSupply = 10_000_000_000_000_000;
static readonly UInt160 Owner = "NdHjSPVnw99RDMCoJdCnAcjkE23gvqUeg2".ToScriptHash();
#endregion
#region Notifications
[DisplayName("Transfer")]
public static event Action OnTransfer;
#endregion
public static bool Transfer(UInt160 from, UInt160 to, BigInteger amount, object data)
{
if (!ValidateAddress(from) || !ValidateAddress(to)) throw new Exception("The parameters from and to SHOULD be 20-byte non-zero addresses.");
if (amount <= 0) throw new Exception("The parameter amount MUST be greater than 0.");
if (!Runtime.CheckWitness(from) && !from.Equals(ExecutionEngine.CallingScriptHash)) throw new Exception("No authorization.");
if (AssetStorage.Get(from) < amount) throw new Exception("Insufficient balance.");
if (from == to) return true;
AssetStorage.Reduce(from, amount);
AssetStorage.Increase(to, amount);
OnTransfer(from, to, amount);
// Validate payable
if (IsDeployed(to)) Contract.Call(to, "onPayment", new object[] { from, amount, data });
return true;
}
}
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
import io.neow3j.devpack.contracts.ContractManagement;
@ManifestExtra(key = "Name", value = "Nep17 example")
@ManifestExtra(key = "Author", value = "neow3j")
public class Nep17Contract {
static final Hash160 contractOwner = addressToScriptHash("NdHjSPVnw99RDMCoJdCnAcjkE23gvqUeg2");
static final int initialSupply = 10_000_000_000_000_000;
static final StorageContext ctx = Storage.getStorageContext();
static final StorageMap tokenOwnerMap = ctx.createMap("tokenOwner");
@DisplayName("Transfer")
static Event3Args
onTransfer;
// Transfers a token.
public static boolean transfer(Hash160 from, Hash160 to, int amount, Object[] data) throws Exception {
if (!from.isValid() || !to.isValid()) {
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 != ExecutionEngine.getCallingScriptHash()) {
throw new Exception("Invalid sender signature. The sender of the tokens needs to be the signing account.");
}
if (assetGet(from) < amount) {return false;}
if (from != to && amount != 0) {
deductFromBalance(from, amount);
addToBalance(to, amount);
}
if (ContractManagement.getContract(to) != null) {
Contract.call(to, "onNEP17Payment", CallFlags.ALL, data);
}
onTransfer.notify(from, to, amount);
return true;
}
}
Learn More