158 lines
5.9 KiB
Go
158 lines
5.9 KiB
Go
|
package nep11
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"math/big"
|
||
|
|
||
|
"github.com/google/uuid"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||
|
)
|
||
|
|
||
|
// DivisibleReader is a reader interface for divisible NEP-11 contract.
|
||
|
type DivisibleReader struct {
|
||
|
BaseReader
|
||
|
}
|
||
|
|
||
|
// Divisible is a state-changing interface for divisible NEP-11 contract.
|
||
|
type Divisible struct {
|
||
|
Base
|
||
|
}
|
||
|
|
||
|
// OwnerIterator is used for iterating over OwnerOf (for divisible NFTs) results.
|
||
|
type OwnerIterator struct {
|
||
|
client Invoker
|
||
|
session uuid.UUID
|
||
|
iterator result.Iterator
|
||
|
}
|
||
|
|
||
|
// NewDivisibleReader creates an instance of DivisibleReader for a contract
|
||
|
// with the given hash using the given invoker.
|
||
|
func NewDivisibleReader(invoker Invoker, hash util.Uint160) *DivisibleReader {
|
||
|
return &DivisibleReader{*NewBaseReader(invoker, hash)}
|
||
|
}
|
||
|
|
||
|
// NewDivisible creates an instance of Divisible for a contract
|
||
|
// with the given hash using the given actor.
|
||
|
func NewDivisible(actor Actor, hash util.Uint160) *Divisible {
|
||
|
return &Divisible{*NewBase(actor, hash)}
|
||
|
}
|
||
|
|
||
|
// OwnerOf returns returns an iterator that allows to walk through all owners of
|
||
|
// the given token. It depends on the server to provide proper session-based
|
||
|
// iterator, but can also work with expanded one.
|
||
|
func (t *DivisibleReader) OwnerOf(token []byte) (*OwnerIterator, error) {
|
||
|
sess, iter, err := unwrap.SessionIterator(t.invoker.Call(t.hash, "ownerOf", token))
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return &OwnerIterator{t.invoker, sess, iter}, nil
|
||
|
}
|
||
|
|
||
|
// OwnerOfExpanded uses the same NEP-11 method as OwnerOf, but can be useful if
|
||
|
// the server used doesn't support sessions and doesn't expand iterators. It
|
||
|
// creates a script that will get num of result items from the iterator right in
|
||
|
// the VM and return them to you. It's only limited by VM stack and GAS available
|
||
|
// for RPC invocations.
|
||
|
func (t *DivisibleReader) OwnerOfExpanded(token []byte, num int) ([]util.Uint160, error) {
|
||
|
return unwrap.ArrayOfUint160(t.invoker.CallAndExpandIterator(t.hash, "ownerOf", num, token))
|
||
|
}
|
||
|
|
||
|
// BalanceOfD is a BalanceOf for divisible NFTs, it returns the amount of token
|
||
|
// owned by a particular account.
|
||
|
func (t *DivisibleReader) BalanceOfD(owner util.Uint160, token []byte) (*big.Int, error) {
|
||
|
return unwrap.BigInt(t.invoker.Call(t.hash, "balanceOf", owner, token))
|
||
|
}
|
||
|
|
||
|
// OwnerOf is the same as (*DivisibleReader).OwnerOf.
|
||
|
func (t *Divisible) OwnerOf(token []byte) (*OwnerIterator, error) {
|
||
|
r := DivisibleReader{t.BaseReader}
|
||
|
return r.OwnerOf(token)
|
||
|
}
|
||
|
|
||
|
// OwnerOfExpanded is the same as (*DivisibleReader).OwnerOfExpanded.
|
||
|
func (t *Divisible) OwnerOfExpanded(token []byte, num int) ([]util.Uint160, error) {
|
||
|
r := DivisibleReader{t.BaseReader}
|
||
|
return r.OwnerOfExpanded(token, num)
|
||
|
}
|
||
|
|
||
|
// BalanceOfD is the same as (*DivisibleReader).BalanceOfD.
|
||
|
func (t *Divisible) BalanceOfD(owner util.Uint160, token []byte) (*big.Int, error) {
|
||
|
r := DivisibleReader{t.BaseReader}
|
||
|
return r.BalanceOfD(owner, token)
|
||
|
}
|
||
|
|
||
|
// TransferD is a divisible version of (*Base).Transfer, allowing to transfer a
|
||
|
// part of NFT. It creates and sends a transaction that performs a `transfer`
|
||
|
// method call using the given parameters and checks for this call result,
|
||
|
// failing the transaction if it's not true. The returned values are transaction
|
||
|
// hash, its ValidUntilBlock value and an error if any.
|
||
|
func (t *Divisible) TransferD(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (util.Uint256, uint32, error) {
|
||
|
script, err := t.transferScript(from, to, amount, id, data)
|
||
|
if err != nil {
|
||
|
return util.Uint256{}, 0, err
|
||
|
}
|
||
|
return t.actor.SendRun(script)
|
||
|
}
|
||
|
|
||
|
// TransferDTransaction is a divisible version of (*Base).TransferTransaction,
|
||
|
// allowing to transfer a part of NFT. It creates a transaction that performs a
|
||
|
// `transfer` method call using the given parameters and checks for this call
|
||
|
// result, failing the transaction if it's not true. This transaction is signed,
|
||
|
// but not sent to the network, instead it's returned to the caller.
|
||
|
func (t *Divisible) TransferDTransaction(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (*transaction.Transaction, error) {
|
||
|
script, err := t.transferScript(from, to, amount, id, data)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return t.actor.MakeRun(script)
|
||
|
}
|
||
|
|
||
|
// TransferDUnsigned is a divisible version of (*Base).TransferUnsigned,
|
||
|
// allowing to transfer a part of NFT. It creates a transaction that performs a
|
||
|
// `transfer` method call using the given parameters and checks for this call
|
||
|
// result, failing the transaction if it's not true. This transaction is not
|
||
|
// signed and just returned to the caller.
|
||
|
func (t *Divisible) TransferDUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (*transaction.Transaction, error) {
|
||
|
script, err := t.transferScript(from, to, amount, id, data)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return t.actor.MakeUnsignedRun(script, nil)
|
||
|
}
|
||
|
|
||
|
// Next returns the next set of elements from the iterator (up to num of them).
|
||
|
// It can return less than num elements in case iterator doesn't have that many
|
||
|
// or zero elements if the iterator has no more elements or the session is
|
||
|
// expired.
|
||
|
func (v *OwnerIterator) Next(num int) ([]util.Uint160, error) {
|
||
|
items, err := v.client.TraverseIterator(v.session, &v.iterator, num)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
res := make([]util.Uint160, len(items))
|
||
|
for i := range items {
|
||
|
b, err := items[i].TryBytes()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("element %d is not a byte string: %w", i, err)
|
||
|
}
|
||
|
u, err := util.Uint160DecodeBytesBE(b)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("element %d is not a uint160: %w", i, err)
|
||
|
}
|
||
|
res[i] = u
|
||
|
}
|
||
|
return res, nil
|
||
|
}
|
||
|
|
||
|
// Terminate closes the iterator session used by OwnerIterator (if it's
|
||
|
// session-based).
|
||
|
func (v *OwnerIterator) Terminate() error {
|
||
|
if v.iterator.ID == nil {
|
||
|
return nil
|
||
|
}
|
||
|
return v.client.TerminateSession(v.session)
|
||
|
}
|