rpc: add NEP11 commands which return iterator
These are Tokens (optional), TokensOf and OwnerOf (divisible).
This commit is contained in:
parent
9eeebf481c
commit
e27c894338
4 changed files with 148 additions and 1 deletions
|
@ -25,6 +25,7 @@ const (
|
|||
totalSupplyPrefix = "s"
|
||||
accountPrefix = "a"
|
||||
tokenPrefix = "t"
|
||||
tokensPrefix = "ts"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -76,6 +77,10 @@ func mkTokenKey(token []byte) []byte {
|
|||
return append(res, token...)
|
||||
}
|
||||
|
||||
func mkTokensKey() []byte {
|
||||
return []byte(tokensPrefix)
|
||||
}
|
||||
|
||||
// BalanceOf returns the number of tokens owned by specified address.
|
||||
func BalanceOf(holder interop.Hash160) int {
|
||||
if len(holder) != 20 {
|
||||
|
@ -112,6 +117,36 @@ func setTokensOf(ctx storage.Context, holder interop.Hash160, tokens []string) {
|
|||
}
|
||||
}
|
||||
|
||||
// setTokens saves minted token if it is not saved yet.
|
||||
func setTokens(ctx storage.Context, newToken string) {
|
||||
key := mkTokensKey()
|
||||
var tokens = []string{}
|
||||
val := storage.Get(ctx, key)
|
||||
if val != nil {
|
||||
tokens = std.Deserialize(val.([]byte)).([]string)
|
||||
}
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
if util.Equals(tokens[i], newToken) {
|
||||
return
|
||||
}
|
||||
}
|
||||
tokens = append(tokens, newToken)
|
||||
val = std.Serialize(tokens)
|
||||
storage.Put(ctx, key, val)
|
||||
}
|
||||
|
||||
// Tokens returns an iterator that contains all of the tokens minted by the contract.
|
||||
func Tokens() iterator.Iterator {
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
var arr = []string{}
|
||||
key := mkTokensKey()
|
||||
val := storage.Get(ctx, key)
|
||||
if val != nil {
|
||||
arr = std.Deserialize(val.([]byte)).([]string)
|
||||
}
|
||||
return iterator.Create(arr)
|
||||
}
|
||||
|
||||
// TokensOf returns an iterator with all tokens held by specified address.
|
||||
func TokensOf(holder interop.Hash160) iterator.Iterator {
|
||||
if len(holder) != 20 {
|
||||
|
@ -219,6 +254,7 @@ func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
|
|||
toksOf = append(toksOf, token)
|
||||
setTokensOf(ctx, from, toksOf)
|
||||
setOwnerOf(ctx, []byte(token), from)
|
||||
setTokens(ctx, token)
|
||||
|
||||
total++
|
||||
storage.Put(ctx, []byte(totalSupplyPrefix), total)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name: "HASHY NFT"
|
||||
supportedstandards: ["NEP-11"]
|
||||
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf"]
|
||||
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", "tokens"]
|
||||
events:
|
||||
- name: Transfer
|
||||
parameters:
|
||||
|
|
|
@ -95,3 +95,38 @@ func topMapFromStack(st []stackitem.Item) (*stackitem.Map, error) {
|
|||
}
|
||||
return st[index].(*stackitem.Map), nil
|
||||
}
|
||||
|
||||
// topIterableFromStack returns top list of elements of `resultItemType` type from stack.
|
||||
func topIterableFromStack(st []stackitem.Item, resultItemType interface{}) ([]interface{}, error) {
|
||||
index := len(st) - 1 // top stack element is last in the array
|
||||
if t := st[index].Type(); t != stackitem.InteropT {
|
||||
return nil, fmt.Errorf("invalid return stackitem type: %s (InteropInterface expected)", t.String())
|
||||
}
|
||||
iter, ok := st[index].Value().(result.Iterator)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to deserialize iterable from interop stackitem: invalid value type (Array expected)")
|
||||
}
|
||||
result := make([]interface{}, len(iter.Values))
|
||||
for i := range iter.Values {
|
||||
switch resultItemType.(type) {
|
||||
case string:
|
||||
bytes, err := iter.Values[i].TryBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to deserialize string from stackitem #%d: %w", i, err)
|
||||
}
|
||||
result[i] = string(bytes)
|
||||
case util.Uint160:
|
||||
bytes, err := iter.Values[i].TryBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to deserialize uint160 from stackitem #%d: %w", i, err)
|
||||
}
|
||||
result[i], err = util.Uint160DecodeBytesBE(bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode uint160 from stackitem #%d: %w", i, err)
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unsupported iterable type")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -85,6 +85,33 @@ func (c *Client) CreateNEP11TransferTx(acc *wallet.Account, tokenHash util.Uint1
|
|||
}}, cosigners...))
|
||||
}
|
||||
|
||||
// NEP11TokensOf returns an array of token IDs for the specified owner of the specified NFT token.
|
||||
func (c *Client) NEP11TokensOf(tokenHash util.Uint160, owner util.Uint160) ([]string, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "tokensOf", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.Hash160Type,
|
||||
Value: owner,
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arr, err := topIterableFromStack(result.Stack, string(""))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get token IDs from stack: %w", err)
|
||||
}
|
||||
ids := make([]string, len(arr))
|
||||
for i := range ids {
|
||||
ids[i] = arr[i].(string)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Non-divisible NFT methods section start.
|
||||
|
||||
// NEP11NDOwnerOf invokes `ownerOf` non-devisible NEP11 method with the
|
||||
|
@ -138,6 +165,33 @@ func (c *Client) NEP11DBalanceOf(tokenHash, owner util.Uint160, tokenID string)
|
|||
return c.nepBalanceOf(tokenHash, owner, &tokenID)
|
||||
}
|
||||
|
||||
// NEP11DOwnerOf returns list of the specified NEP11 divisible token owners.
|
||||
func (c *Client) NEP11DOwnerOf(tokenHash util.Uint160, tokenID string) ([]util.Uint160, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{
|
||||
{
|
||||
Type: smartcontract.StringType,
|
||||
Value: tokenID,
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arr, err := topIterableFromStack(result.Stack, util.Uint160{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get token IDs from stack: %w", err)
|
||||
}
|
||||
owners := make([]util.Uint160, len(arr))
|
||||
for i := range owners {
|
||||
owners[i] = arr[i].(util.Uint160)
|
||||
}
|
||||
return owners, nil
|
||||
}
|
||||
|
||||
// Divisible NFT methods section end.
|
||||
|
||||
// Optional NFT methods section start.
|
||||
|
@ -160,4 +214,26 @@ func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID string) (*stack
|
|||
return topMapFromStack(result.Stack)
|
||||
}
|
||||
|
||||
// NEP11Tokens returns list of the tokens minted by the contract.
|
||||
func (c *Client) NEP11Tokens(tokenHash util.Uint160) ([]string, error) {
|
||||
result, err := c.InvokeFunction(tokenHash, "tokens", []smartcontract.Parameter{}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = getInvocationError(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arr, err := topIterableFromStack(result.Stack, string(""))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get token IDs from stack: %w", err)
|
||||
}
|
||||
tokens := make([]string, len(arr))
|
||||
for i := range tokens {
|
||||
tokens[i] = arr[i].(string)
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
// Optional NFT methods section end.
|
||||
|
|
Loading…
Reference in a new issue