forked from TrueCloudLab/neoneo-go
examples: add an example of NEP11 Divisible token
This commit is contained in:
parent
9c2e92d7d9
commit
5561b94698
5 changed files with 457 additions and 11 deletions
|
@ -20,18 +20,19 @@ You can use `my_wallet.json` to deploy example contracts.
|
||||||
|
|
||||||
See the table below for the detailed examples description.
|
See the table below for the detailed examples description.
|
||||||
|
|
||||||
| Example | Description |
|
| Example | Description |
|
||||||
| --- | --- |
|
|--------------------------| --- |
|
||||||
| [engine](engine) | This contract demonstrates how to use `runtime` interop package which implements an API for `System.Runtime.*` NEO system calls. Please, refer to the `runtime` [package documentation](../pkg/interop/doc.go) for details. |
|
| [engine](engine) | This contract demonstrates how to use `runtime` interop package which implements an API for `System.Runtime.*` NEO system calls. Please, refer to the `runtime` [package documentation](../pkg/interop/doc.go) for details. |
|
||||||
| [events](events) | The contract shows how execution notifications with the different arguments types can be sent with the help of `runtime.Notify` function of the `runtime` interop package. Please, refer to the `runtime.Notify` [function documentation](../pkg/interop/runtime/runtime.go) for details. |
|
| [events](events) | The contract shows how execution notifications with the different arguments types can be sent with the help of `runtime.Notify` function of the `runtime` interop package. Please, refer to the `runtime.Notify` [function documentation](../pkg/interop/runtime/runtime.go) for details. |
|
||||||
| [iterator](iterator) | This example describes a way to work with NEO iterators. Please, refer to the `iterator` [package documentation](../pkg/interop/iterator/iterator.go) for details. |
|
| [iterator](iterator) | This example describes a way to work with NEO iterators. Please, refer to the `iterator` [package documentation](../pkg/interop/iterator/iterator.go) for details. |
|
||||||
| [nft-nd](nft-nd) | NEP-11 non-divisible NFT. See NEP-11 token standard [specification](https://github.com/neo-project/proposals/pull/130) for details. |
|
| [nft-d](nft-d) | NEP-11 divisible NFT. See NEP-11 token standard [specification](https://github.com/neo-project/proposals/blob/master/nep-11.mediawiki) for details. |
|
||||||
|
| [nft-nd](nft-nd) | NEP-11 non-divisible NFT. See NEP-11 token standard [specification](https://github.com/neo-project/proposals/blob/master/nep-11.mediawiki) for details. |
|
||||||
| [nft-nd-nns](nft-nd-nns) | Neo Name Service contract which is NEP-11 non-divisible NFT. The contract implements methods for Neo domain name system managing such as domains registration/transferring, records addition and names resolving. |
|
| [nft-nd-nns](nft-nd-nns) | Neo Name Service contract which is NEP-11 non-divisible NFT. The contract implements methods for Neo domain name system managing such as domains registration/transferring, records addition and names resolving. |
|
||||||
| [oracle](oracle) | Oracle demo contract exposing two methods that you can use to process URLs. It uses oracle native contract, see [interop package documentation](../pkg/interop/native/oracle/oracle.go) also. |
|
| [oracle](oracle) | Oracle demo contract exposing two methods that you can use to process URLs. It uses oracle native contract, see [interop package documentation](../pkg/interop/native/oracle/oracle.go) also. |
|
||||||
| [runtime](runtime) | This contract demonstrates how to use special `_initialize` and `_deploy` methods. See the [compiler documentation](../docs/compiler.md#vm-api-interop-layer ) for methods details. It also shows the pattern for checking owner witness inside the contract with the help of `runtime.CheckWitness` interop [function](../pkg/interop/runtime/runtime.go). |
|
| [runtime](runtime) | This contract demonstrates how to use special `_initialize` and `_deploy` methods. See the [compiler documentation](../docs/compiler.md#vm-api-interop-layer ) for methods details. It also shows the pattern for checking owner witness inside the contract with the help of `runtime.CheckWitness` interop [function](../pkg/interop/runtime/runtime.go). |
|
||||||
| [storage](storage) | The contract implements API for basic operations with a contract storage. It shows hos to use `storage` interop package. See the `storage` [package documentation](../pkg/interop/storage/storage.go). |
|
| [storage](storage) | The contract implements API for basic operations with a contract storage. It shows hos to use `storage` interop package. See the `storage` [package documentation](../pkg/interop/storage/storage.go). |
|
||||||
| [timer](timer) | The idea of the contract is to count `tick` method invocations and destroy itself after the third invocation. It shows how to use `contract.Call` interop function to call, update (migrate) and destroy the contract. Please, refer to the `contract.Call` [function documentation](../pkg/interop/contract/contract.go) |
|
| [timer](timer) | The idea of the contract is to count `tick` method invocations and destroy itself after the third invocation. It shows how to use `contract.Call` interop function to call, update (migrate) and destroy the contract. Please, refer to the `contract.Call` [function documentation](../pkg/interop/contract/contract.go) |
|
||||||
| [token](token) | This contract implements NEP-17 token standard (like NEO and GAS tokens) with all required methods and operations. See the NEP-17 token standard [specification](https://github.com/neo-project/proposals/pull/126) for details. |
|
| [token](token) | This contract implements NEP-17 token standard (like NEO and GAS tokens) with all required methods and operations. See the NEP-17 token standard [specification](https://github.com/neo-project/proposals/pull/126) for details. |
|
||||||
| [token-sale](token-sale) | The contract represents a token with `allowance`. It means that the token owner should approve token withdrawing before the transfer. The contract demonstrates how interop packages can be combined to work together. |
|
| [token-sale](token-sale) | The contract represents a token with `allowance`. It means that the token owner should approve token withdrawing before the transfer. The contract demonstrates how interop packages can be combined to work together. |
|
||||||
|
|
||||||
## Compile
|
## Compile
|
||||||
|
|
5
examples/nft-d/go.mod
Normal file
5
examples/nft-d/go.mod
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module github.com/nspcc-dev/neo-go/examples/nft
|
||||||
|
|
||||||
|
go 1.15
|
||||||
|
|
||||||
|
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220118080652-4eddfdbbc652
|
2
examples/nft-d/go.sum
Normal file
2
examples/nft-d/go.sum
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220118080652-4eddfdbbc652 h1:Paq5oU7mlXjzFcVDD97RA4sxFljAmFrnLrcsObBGIGY=
|
||||||
|
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220118080652-4eddfdbbc652/go.mod h1:/zA6GVDzpSkwq8/HQJxPWDcvfn2BbZnahUO9A1wAevM=
|
416
examples/nft-d/nft.go
Normal file
416
examples/nft-d/nft.go
Normal file
|
@ -0,0 +1,416 @@
|
||||||
|
/*
|
||||||
|
Package nft contains divisible non-fungible NEP-11-compatible token
|
||||||
|
implementation. This token can be minted with GAS transfer to contract address,
|
||||||
|
it will retrieve NeoFS container ID and object ID from the transfer data and
|
||||||
|
produce NFT which represents NeoFS object.
|
||||||
|
*/
|
||||||
|
package nft
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/gas"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/management"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
decimals = 2
|
||||||
|
multiplier = 100
|
||||||
|
)
|
||||||
|
|
||||||
|
// Prefixes used for contract data storage.
|
||||||
|
const (
|
||||||
|
totalSupplyPrefix = "s"
|
||||||
|
// balancePrefix contains map from [address + token id] to address's balance of the specified token.
|
||||||
|
balancePrefix = "b"
|
||||||
|
// tokenOwnerPrefix contains map from [token id + owner] to token's owner.
|
||||||
|
tokenOwnerPrefix = "t"
|
||||||
|
// tokenPrefix contains map from token id to empty array.
|
||||||
|
tokenPrefix = "i"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// contractOwner is a special address that can perform some management
|
||||||
|
// functions on this contract like updating/destroying it and can also
|
||||||
|
// be used for contract address verification.
|
||||||
|
contractOwner = util.FromAddress("NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ObjectIdentifier represents NFT structure and contains the container ID and
|
||||||
|
// object ID of the NeoFS object.
|
||||||
|
type ObjectIdentifier struct {
|
||||||
|
ContainerID []byte
|
||||||
|
ObjectID []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common methods
|
||||||
|
|
||||||
|
// Symbol returns token symbol, it's NFSO.
|
||||||
|
func Symbol() string {
|
||||||
|
return "NFSO"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decimals returns token decimals, this NFT is divisible.
|
||||||
|
func Decimals() int {
|
||||||
|
return decimals
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalSupply is a contract method that returns the number of tokens minted.
|
||||||
|
func TotalSupply() int {
|
||||||
|
return totalSupply(storage.GetReadOnlyContext())
|
||||||
|
}
|
||||||
|
|
||||||
|
// totalSupply is an internal implementation of TotalSupply operating with
|
||||||
|
// given context. The number itself is stored raw in the DB with totalSupplyPrefix
|
||||||
|
// key.
|
||||||
|
func totalSupply(ctx storage.Context) int {
|
||||||
|
var res int
|
||||||
|
|
||||||
|
val := storage.Get(ctx, []byte(totalSupplyPrefix))
|
||||||
|
if val != nil {
|
||||||
|
res = val.(int)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// mkBalancePrefix creates DB key-prefix for account balances specified
|
||||||
|
// by concatenating balancePrefix and account address.
|
||||||
|
func mkBalancePrefix(holder interop.Hash160) []byte {
|
||||||
|
res := []byte(balancePrefix)
|
||||||
|
return append(res, holder...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mkBalanceKey creates DB key for account specified by concatenating balancePrefix,
|
||||||
|
// account address and token ID.
|
||||||
|
func mkBalanceKey(holder interop.Hash160, tokenID []byte) []byte {
|
||||||
|
res := mkBalancePrefix(holder)
|
||||||
|
return append(res, tokenID...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mkTokenOwnerPrefix creates DB key prefix for token specified by its ID.
|
||||||
|
func mkTokenOwnerPrefix(tokenID []byte) []byte {
|
||||||
|
res := []byte(tokenOwnerPrefix)
|
||||||
|
return append(res, tokenID...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mkTokenOwnerKey creates DB key for token specified by concatenating tokenOwnerPrefix,
|
||||||
|
// token ID and holder.
|
||||||
|
func mkTokenOwnerKey(tokenID []byte, holder interop.Hash160) []byte {
|
||||||
|
res := mkTokenOwnerPrefix(tokenID)
|
||||||
|
return append(res, holder...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mkTokenKey creates DB key for token specified by its ID.
|
||||||
|
func mkTokenKey(tokenID []byte) []byte {
|
||||||
|
res := []byte(tokenPrefix)
|
||||||
|
return append(res, tokenID...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BalanceOf returns the overall number of tokens owned by specified address.
|
||||||
|
func BalanceOf(holder interop.Hash160) int {
|
||||||
|
if len(holder) != interop.Hash160Len {
|
||||||
|
panic("bad owner address")
|
||||||
|
}
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
balance := 0
|
||||||
|
iter := tokensOf(ctx, holder)
|
||||||
|
for iterator.Next(iter) {
|
||||||
|
tokenID := iterator.Value(iter).([]byte)
|
||||||
|
key := mkBalanceKey(holder, tokenID)
|
||||||
|
balance += getBalanceOf(ctx, key)
|
||||||
|
}
|
||||||
|
return balance
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBalanceOf returns balance of the account of the specified tokenID using
|
||||||
|
// database key.
|
||||||
|
func getBalanceOf(ctx storage.Context, balanceKey []byte) int {
|
||||||
|
val := storage.Get(ctx, balanceKey)
|
||||||
|
if val != nil {
|
||||||
|
return val.(int)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// addToBalance adds amount to the account balance. Amount can be negative. It returns
|
||||||
|
// updated balance value.
|
||||||
|
func addToBalance(ctx storage.Context, holder interop.Hash160, tokenID []byte, amount int) int {
|
||||||
|
key := mkBalanceKey(holder, tokenID)
|
||||||
|
old := getBalanceOf(ctx, key)
|
||||||
|
old += amount
|
||||||
|
if old > 0 {
|
||||||
|
storage.Put(ctx, key, old)
|
||||||
|
} else {
|
||||||
|
storage.Delete(ctx, key)
|
||||||
|
}
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokensOf returns an iterator with all tokens held by specified address.
|
||||||
|
func TokensOf(holder interop.Hash160) iterator.Iterator {
|
||||||
|
if len(holder) != interop.Hash160Len {
|
||||||
|
panic("bad owner address")
|
||||||
|
}
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
|
||||||
|
return tokensOf(ctx, holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokensOf(ctx storage.Context, holder interop.Hash160) iterator.Iterator {
|
||||||
|
key := mkBalancePrefix(holder)
|
||||||
|
// We don't store zero balances, thus only relevant token IDs of the holder will
|
||||||
|
// be returned.
|
||||||
|
iter := storage.Find(ctx, key, storage.KeysOnly|storage.RemovePrefix)
|
||||||
|
return iter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer token from its owner to another user, if there's one owner of the token.
|
||||||
|
// It will return false if token is shared between multiple owners.
|
||||||
|
func Transfer(to interop.Hash160, token []byte, data interface{}) bool {
|
||||||
|
if len(to) != interop.Hash160Len {
|
||||||
|
panic("invalid 'to' address")
|
||||||
|
}
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
var (
|
||||||
|
owner interop.Hash160
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
iter := ownersOf(ctx, token)
|
||||||
|
for iterator.Next(iter) {
|
||||||
|
if ok {
|
||||||
|
// Token is shared between multiple owners.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
owner = iterator.Value(iter).(interop.Hash160)
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
panic("unknown token")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that although calling script hash is not checked explicitly in
|
||||||
|
// this contract it is in fact checked for in `CheckWitness` itself.
|
||||||
|
if !runtime.CheckWitness(owner) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
key := mkBalanceKey(owner, token)
|
||||||
|
amount := getBalanceOf(ctx, key)
|
||||||
|
|
||||||
|
if string(owner) != string(to) {
|
||||||
|
addToBalance(ctx, owner, token, -amount)
|
||||||
|
removeOwner(ctx, token, owner)
|
||||||
|
|
||||||
|
addToBalance(ctx, to, token, amount)
|
||||||
|
addOwner(ctx, token, to)
|
||||||
|
}
|
||||||
|
postTransfer(owner, to, token, amount, data)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// postTransfer emits Transfer event and calls onNEP11Payment if needed.
|
||||||
|
func postTransfer(from interop.Hash160, to interop.Hash160, token []byte, amount int, data interface{}) {
|
||||||
|
runtime.Notify("Transfer", from, to, amount, token)
|
||||||
|
if management.GetContract(to) != nil {
|
||||||
|
contract.Call(to, "onNEP11Payment", contract.All, from, amount, token, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// end of common methods.
|
||||||
|
|
||||||
|
// Optional methods.
|
||||||
|
|
||||||
|
// Properties returns properties of the given NFT.
|
||||||
|
func Properties(id []byte) map[string]string {
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
if !isTokenValid(ctx, id) {
|
||||||
|
panic("unknown token")
|
||||||
|
}
|
||||||
|
t := std.Deserialize(id).(ObjectIdentifier)
|
||||||
|
result := map[string]string{
|
||||||
|
"name": "NFSO " + string(id),
|
||||||
|
"fullName": "NeoFS Object",
|
||||||
|
"containerID": string(t.ContainerID),
|
||||||
|
"objectID": string(t.ObjectID),
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tokens returns all token IDs minted by the contract.
|
||||||
|
func Tokens() iterator.Iterator {
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
prefix := []byte(tokenPrefix)
|
||||||
|
iter := storage.Find(ctx, prefix, storage.KeysOnly|storage.RemovePrefix)
|
||||||
|
return iter
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTokenValid(ctx storage.Context, tokenID []byte) bool {
|
||||||
|
key := mkTokenKey(tokenID)
|
||||||
|
result := storage.Get(ctx, key)
|
||||||
|
return result != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of optional methods.
|
||||||
|
|
||||||
|
// Divisible methods.
|
||||||
|
|
||||||
|
// TransferDivisible token from its owner to another user, notice that it only has three
|
||||||
|
// parameters because token owner can be deduced from token ID itself.
|
||||||
|
func TransferDivisible(from, to interop.Hash160, amount int, token []byte, data interface{}) bool {
|
||||||
|
if len(from) != interop.Hash160Len {
|
||||||
|
panic("invalid 'from' address")
|
||||||
|
}
|
||||||
|
if len(to) != interop.Hash160Len {
|
||||||
|
panic("invalid 'to' address")
|
||||||
|
}
|
||||||
|
if amount < 0 {
|
||||||
|
panic("negative 'amount'")
|
||||||
|
}
|
||||||
|
if amount > multiplier {
|
||||||
|
panic("invalid 'amount'")
|
||||||
|
}
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
if !isTokenValid(ctx, token) {
|
||||||
|
panic("unknown token")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that although calling script hash is not checked explicitly in
|
||||||
|
// this contract it is in fact checked for in `CheckWitness` itself.
|
||||||
|
if !runtime.CheckWitness(from) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
key := mkBalanceKey(from, token)
|
||||||
|
balance := getBalanceOf(ctx, key)
|
||||||
|
if amount > balance {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(from) != string(to) {
|
||||||
|
updBalance := addToBalance(ctx, from, token, -amount)
|
||||||
|
if updBalance == 0 {
|
||||||
|
removeOwner(ctx, token, from)
|
||||||
|
}
|
||||||
|
|
||||||
|
updBalance = addToBalance(ctx, to, token, amount)
|
||||||
|
if updBalance != 0 {
|
||||||
|
addOwner(ctx, token, to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
postTransfer(from, to, token, amount, data)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// OwnerOf returns owner of specified token.
|
||||||
|
func OwnerOf(token []byte) iterator.Iterator {
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
if !isTokenValid(ctx, token) {
|
||||||
|
panic("unknown token")
|
||||||
|
}
|
||||||
|
return ownersOf(ctx, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BalanceOfDivisible returns the number of token with the specified tokenID owned by specified address.
|
||||||
|
func BalanceOfDivisible(holder interop.Hash160, token []byte) int {
|
||||||
|
if len(holder) != interop.Hash160Len {
|
||||||
|
panic("bad holder address")
|
||||||
|
}
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
key := mkBalanceKey(holder, token)
|
||||||
|
return getBalanceOf(ctx, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// end of divisible methods.
|
||||||
|
|
||||||
|
// ownersOf returns iterator over owners of the specified token. Owner is
|
||||||
|
// stored as value of the token key (prefix + token ID + owner).
|
||||||
|
func ownersOf(ctx storage.Context, token []byte) iterator.Iterator {
|
||||||
|
key := mkTokenOwnerPrefix(token)
|
||||||
|
iter := storage.Find(ctx, key, storage.ValuesOnly)
|
||||||
|
return iter
|
||||||
|
}
|
||||||
|
|
||||||
|
func addOwner(ctx storage.Context, token []byte, holder interop.Hash160) {
|
||||||
|
key := mkTokenOwnerKey(token, holder)
|
||||||
|
storage.Put(ctx, key, holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeOwner(ctx storage.Context, token []byte, holder interop.Hash160) {
|
||||||
|
key := mkTokenOwnerKey(token, holder)
|
||||||
|
storage.Delete(ctx, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnNEP17Payment mints tokens if at least 10 GAS is provided. You don't call
|
||||||
|
// this method directly, instead it's called by GAS contract when you transfer
|
||||||
|
// GAS from your address to the address of this NFT contract.
|
||||||
|
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
|
||||||
|
if string(runtime.GetCallingScriptHash()) != gas.Hash {
|
||||||
|
panic("only GAS is accepted")
|
||||||
|
}
|
||||||
|
if amount < 10_00000000 {
|
||||||
|
panic("minting NFSO costs at least 10 GAS")
|
||||||
|
}
|
||||||
|
tokenInfo := data.([]interface{})
|
||||||
|
if len(tokenInfo) != 2 {
|
||||||
|
panic("invalid 'data'")
|
||||||
|
}
|
||||||
|
containerID := tokenInfo[0].([]byte)
|
||||||
|
if len(containerID) != 32 {
|
||||||
|
panic("invalid container ID")
|
||||||
|
}
|
||||||
|
objectID := tokenInfo[1].([]byte)
|
||||||
|
if len(objectID) != 32 {
|
||||||
|
panic("invalid object ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
t := ObjectIdentifier{
|
||||||
|
ContainerID: containerID,
|
||||||
|
ObjectID: objectID,
|
||||||
|
}
|
||||||
|
id := std.Serialize(t)
|
||||||
|
|
||||||
|
var ctx = storage.GetContext()
|
||||||
|
if isTokenValid(ctx, id) {
|
||||||
|
panic("NFSO for the specified address is already minted")
|
||||||
|
}
|
||||||
|
key := mkTokenKey(id)
|
||||||
|
storage.Put(ctx, key, []byte{})
|
||||||
|
|
||||||
|
total := totalSupply(ctx)
|
||||||
|
|
||||||
|
addOwner(ctx, from, id)
|
||||||
|
addToBalance(ctx, from, id, multiplier)
|
||||||
|
|
||||||
|
total++
|
||||||
|
storage.Put(ctx, []byte(totalSupplyPrefix), total)
|
||||||
|
|
||||||
|
postTransfer(nil, from, id, multiplier, nil) // no `data` during minting
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify allows owner to manage contract's address, including earned GAS
|
||||||
|
// transfer from contract's address to somewhere else. It just checks for transaction
|
||||||
|
// to also be signed by contract owner, so contract's witness should be empty.
|
||||||
|
func Verify() bool {
|
||||||
|
return runtime.CheckWitness(contractOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy destroys the contract, only owner can do that.
|
||||||
|
func Destroy() {
|
||||||
|
if !Verify() {
|
||||||
|
panic("only owner can destroy")
|
||||||
|
}
|
||||||
|
management.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates the contract, only owner can do that.
|
||||||
|
func Update(nef, manifest []byte) {
|
||||||
|
if !Verify() {
|
||||||
|
panic("only owner can update")
|
||||||
|
}
|
||||||
|
management.Update(nef, manifest)
|
||||||
|
}
|
22
examples/nft-d/nft.yml
Normal file
22
examples/nft-d/nft.yml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
name: "NFSO NFT"
|
||||||
|
sourceurl: https://github.com/nspcc-dev/neo-go/
|
||||||
|
supportedstandards: ["NEP-11"]
|
||||||
|
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", "properties", "tokens"]
|
||||||
|
events:
|
||||||
|
- name: Transfer
|
||||||
|
parameters:
|
||||||
|
- name: from
|
||||||
|
type: Hash160
|
||||||
|
- name: to
|
||||||
|
type: Hash160
|
||||||
|
- name: amount
|
||||||
|
type: Integer
|
||||||
|
- name: tokenId
|
||||||
|
type: ByteArray
|
||||||
|
permissions:
|
||||||
|
- hash: fffdc93764dbaddd97c48f252a53ea4643faa3fd
|
||||||
|
methods: ["update", "destroy"]
|
||||||
|
- methods: ["onNEP11Payment"]
|
||||||
|
overloads:
|
||||||
|
balanceOfDivisible: balanceOf
|
||||||
|
transferDivisible: transfer
|
Loading…
Reference in a new issue