mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2025-01-06 23:50:35 +00:00
b2bd8e4a0a
Close #3451 Signed-off-by: Ekaterina Pavlova <ekt@morphbits.io>
185 lines
5.3 KiB
Go
185 lines
5.3 KiB
Go
/*
|
|
Package nep24 provides RPC wrappers for NEP-24 contracts.
|
|
|
|
All methods are safe (read-only) and encapsulated in the RoyaltyReader structure,
|
|
designed for managing NFT royalties and retrieving royalty information.
|
|
Refer to the nep11 package for basic NFT functionalities, while nep24 handles
|
|
royalty-related operations.
|
|
*/
|
|
package nep24
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neptoken"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
)
|
|
|
|
// RoyaltyRecipient contains information about the recipient and the royalty amount.
|
|
type RoyaltyRecipient struct {
|
|
Address util.Uint160
|
|
Amount *big.Int
|
|
}
|
|
|
|
// RoyaltiesTransferredEvent represents a RoyaltiesTransferred event as defined in
|
|
// the NEP-24 standard.
|
|
type RoyaltiesTransferredEvent struct {
|
|
RoyaltyToken util.Uint160
|
|
RoyaltyRecipient util.Uint160
|
|
Buyer util.Uint160
|
|
TokenID []byte
|
|
Amount *big.Int
|
|
}
|
|
|
|
// RoyaltyReader represents safe (read-only) methods of NEP-24 token. It can be
|
|
// used to query data about royalties.
|
|
type RoyaltyReader struct {
|
|
invoker neptoken.Invoker
|
|
hash util.Uint160
|
|
}
|
|
|
|
// NewRoyaltyReader returns a new RoyaltyReader instance.
|
|
func NewRoyaltyReader(invoker neptoken.Invoker, hash util.Uint160) *RoyaltyReader {
|
|
return &RoyaltyReader{
|
|
invoker: invoker,
|
|
hash: hash,
|
|
}
|
|
}
|
|
|
|
// RoyaltyInfo returns the royalty information for the given tokenID, royaltyToken,
|
|
// and salePrice.
|
|
func (c *RoyaltyReader) RoyaltyInfo(tokenID []byte, royaltyToken util.Uint160, salePrice *big.Int) ([]RoyaltyRecipient, error) {
|
|
res, err := c.invoker.Call(c.hash, "royaltyInfo", tokenID, royaltyToken, salePrice)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(res.Stack) != 1 {
|
|
return nil, errors.New("invalid response: expected a single item on the stack")
|
|
}
|
|
rootItem, ok := res.Stack[0].Value().([]stackitem.Item)
|
|
if !ok {
|
|
return nil, errors.New("invalid response: expected an array of royalties")
|
|
}
|
|
|
|
var royalties []RoyaltyRecipient
|
|
for _, item := range rootItem {
|
|
royalty, ok := item.Value().([]stackitem.Item)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid royalty structure: expected array of 2 items, got %d", len(royalty))
|
|
}
|
|
var recipient RoyaltyRecipient
|
|
err = recipient.FromStackItem(royalty)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode royalty detail: %w", err)
|
|
}
|
|
royalties = append(royalties, recipient)
|
|
}
|
|
|
|
return royalties, nil
|
|
}
|
|
|
|
// FromStackItem converts a stack item into a RoyaltyRecipient struct.
|
|
func (r *RoyaltyRecipient) FromStackItem(item []stackitem.Item) error {
|
|
if len(item) != 2 {
|
|
return fmt.Errorf("invalid royalty structure: expected 2 items, got %d", len(item))
|
|
}
|
|
|
|
recipientBytes, err := item[0].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode recipient address: %w", err)
|
|
}
|
|
|
|
recipient, err := util.Uint160DecodeBytesBE(recipientBytes)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid recipient address: %w", err)
|
|
}
|
|
|
|
amountBigInt, err := item[1].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode royalty amount: %w", err)
|
|
}
|
|
if amountBigInt.Sign() < 0 {
|
|
return errors.New("negative royalty amount")
|
|
}
|
|
amount := big.NewInt(0).Set(amountBigInt)
|
|
r.Amount = amount
|
|
r.Address = recipient
|
|
return nil
|
|
}
|
|
|
|
// RoyaltiesTransferredEventsFromApplicationLog retrieves all emitted
|
|
// RoyaltiesTransferredEvents from the provided [result.ApplicationLog].
|
|
func RoyaltiesTransferredEventsFromApplicationLog(log *result.ApplicationLog) ([]*RoyaltiesTransferredEvent, error) {
|
|
if log == nil {
|
|
return nil, errors.New("nil application log")
|
|
}
|
|
var res []*RoyaltiesTransferredEvent
|
|
for i, ex := range log.Executions {
|
|
for j, e := range ex.Events {
|
|
if e.Name != "RoyaltiesTransferred" {
|
|
continue
|
|
}
|
|
event := new(RoyaltiesTransferredEvent)
|
|
err := event.FromStackItem(e.Item)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode event from stackitem (event #%d, execution #%d): %w", j, i, err)
|
|
}
|
|
res = append(res, event)
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
// FromStackItem converts a stack item into a RoyaltiesTransferredEvent struct.
|
|
func (e *RoyaltiesTransferredEvent) FromStackItem(item *stackitem.Array) error {
|
|
if item == nil {
|
|
return errors.New("nil item")
|
|
}
|
|
arr, ok := item.Value().([]stackitem.Item)
|
|
if !ok || len(arr) != 5 {
|
|
return errors.New("invalid event structure: expected array of 5 items")
|
|
}
|
|
|
|
b, err := arr[0].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode RoyaltyToken: %w", err)
|
|
}
|
|
e.RoyaltyToken, err = util.Uint160DecodeBytesBE(b)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid RoyaltyToken: %w", err)
|
|
}
|
|
|
|
b, err = arr[1].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode RoyaltyRecipient: %w", err)
|
|
}
|
|
e.RoyaltyRecipient, err = util.Uint160DecodeBytesBE(b)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid RoyaltyRecipient: %w", err)
|
|
}
|
|
|
|
b, err = arr[2].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode Buyer: %w", err)
|
|
}
|
|
e.Buyer, err = util.Uint160DecodeBytesBE(b)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid Buyer: %w", err)
|
|
}
|
|
|
|
e.TokenID, err = arr[3].TryBytes()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode TokenID: %w", err)
|
|
}
|
|
|
|
e.Amount, err = arr[4].TryInteger()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode Amount: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|