github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/rpcclient/nep11/divisible.go (about)

     1  package nep11
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  
     7  	"github.com/google/uuid"
     8  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
     9  	"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
    10  	"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
    11  	"github.com/nspcc-dev/neo-go/pkg/util"
    12  )
    13  
    14  // DivisibleReader is a reader interface for divisible NEP-11 contract.
    15  type DivisibleReader struct {
    16  	BaseReader
    17  }
    18  
    19  // DivisibleWriter is a state-changing interface for divisible NEP-11 contract.
    20  // It's mostly useful not directly, but as a reusable layer for higher-level
    21  // structures.
    22  type DivisibleWriter struct {
    23  	BaseWriter
    24  }
    25  
    26  // Divisible is a full  reader interface for divisible NEP-11 contract.
    27  type Divisible struct {
    28  	DivisibleReader
    29  	DivisibleWriter
    30  }
    31  
    32  // OwnerIterator is used for iterating over OwnerOf (for divisible NFTs) results.
    33  type OwnerIterator struct {
    34  	client   Invoker
    35  	session  uuid.UUID
    36  	iterator result.Iterator
    37  }
    38  
    39  // NewDivisibleReader creates an instance of DivisibleReader for a contract
    40  // with the given hash using the given invoker.
    41  func NewDivisibleReader(invoker Invoker, hash util.Uint160) *DivisibleReader {
    42  	return &DivisibleReader{*NewBaseReader(invoker, hash)}
    43  }
    44  
    45  // NewDivisible creates an instance of Divisible for a contract
    46  // with the given hash using the given actor.
    47  func NewDivisible(actor Actor, hash util.Uint160) *Divisible {
    48  	return &Divisible{*NewDivisibleReader(actor, hash), DivisibleWriter{BaseWriter{hash, actor}}}
    49  }
    50  
    51  // OwnerOf returns returns an iterator that allows to walk through all owners of
    52  // the given token. It depends on the server to provide proper session-based
    53  // iterator, but can also work with expanded one.
    54  func (t *DivisibleReader) OwnerOf(token []byte) (*OwnerIterator, error) {
    55  	sess, iter, err := unwrap.SessionIterator(t.invoker.Call(t.hash, "ownerOf", token))
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	return &OwnerIterator{t.invoker, sess, iter}, nil
    60  }
    61  
    62  // OwnerOfExpanded uses the same NEP-11 method as OwnerOf, but can be useful if
    63  // the server used doesn't support sessions and doesn't expand iterators. It
    64  // creates a script that will get num of result items from the iterator right in
    65  // the VM and return them to you. It's only limited by VM stack and GAS available
    66  // for RPC invocations.
    67  func (t *DivisibleReader) OwnerOfExpanded(token []byte, num int) ([]util.Uint160, error) {
    68  	return unwrap.ArrayOfUint160(t.invoker.CallAndExpandIterator(t.hash, "ownerOf", num, token))
    69  }
    70  
    71  // BalanceOfD is a BalanceOf for divisible NFTs, it returns the amount of token
    72  // owned by a particular account.
    73  func (t *DivisibleReader) BalanceOfD(owner util.Uint160, token []byte) (*big.Int, error) {
    74  	return unwrap.BigInt(t.invoker.Call(t.hash, "balanceOf", owner, token))
    75  }
    76  
    77  // TransferD is a divisible version of (*Base).Transfer, allowing to transfer a
    78  // part of NFT. It creates and sends a transaction that performs a `transfer`
    79  // method call using the given parameters and checks for this call result,
    80  // failing the transaction if it's not true. The returned values are transaction
    81  // hash, its ValidUntilBlock value and an error if any.
    82  func (t *DivisibleWriter) TransferD(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data any) (util.Uint256, uint32, error) {
    83  	script, err := t.transferScript(from, to, amount, id, data)
    84  	if err != nil {
    85  		return util.Uint256{}, 0, err
    86  	}
    87  	return t.actor.SendRun(script)
    88  }
    89  
    90  // TransferDTransaction is a divisible version of (*Base).TransferTransaction,
    91  // allowing to transfer a part of NFT. It creates a transaction that performs a
    92  // `transfer` method call using the given parameters and checks for this call
    93  // result, failing the transaction if it's not true. This transaction is signed,
    94  // but not sent to the network, instead it's returned to the caller.
    95  func (t *DivisibleWriter) TransferDTransaction(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data any) (*transaction.Transaction, error) {
    96  	script, err := t.transferScript(from, to, amount, id, data)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	return t.actor.MakeRun(script)
   101  }
   102  
   103  // TransferDUnsigned is a divisible version of (*Base).TransferUnsigned,
   104  // allowing to transfer a part of NFT. It creates a transaction that performs a
   105  // `transfer` method call using the given parameters and checks for this call
   106  // result, failing the transaction if it's not true. This transaction is not
   107  // signed and just returned to the caller.
   108  func (t *DivisibleWriter) TransferDUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data any) (*transaction.Transaction, error) {
   109  	script, err := t.transferScript(from, to, amount, id, data)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	return t.actor.MakeUnsignedRun(script, nil)
   114  }
   115  
   116  // Next returns the next set of elements from the iterator (up to num of them).
   117  // It can return less than num elements in case iterator doesn't have that many
   118  // or zero elements if the iterator has no more elements or the session is
   119  // expired.
   120  func (v *OwnerIterator) Next(num int) ([]util.Uint160, error) {
   121  	items, err := v.client.TraverseIterator(v.session, &v.iterator, num)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	res := make([]util.Uint160, len(items))
   126  	for i := range items {
   127  		b, err := items[i].TryBytes()
   128  		if err != nil {
   129  			return nil, fmt.Errorf("element %d is not a byte string: %w", i, err)
   130  		}
   131  		u, err := util.Uint160DecodeBytesBE(b)
   132  		if err != nil {
   133  			return nil, fmt.Errorf("element %d is not a uint160: %w", i, err)
   134  		}
   135  		res[i] = u
   136  	}
   137  	return res, nil
   138  }
   139  
   140  // Terminate closes the iterator session used by OwnerIterator (if it's
   141  // session-based).
   142  func (v *OwnerIterator) Terminate() error {
   143  	if v.iterator.ID == nil {
   144  		return nil
   145  	}
   146  	return v.client.TerminateSession(v.session)
   147  }