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 }