neo-go/pkg/rpcclient/nep24/royalty.go
Ekaterina Pavlova b2bd8e4a0a manifest: support NEP-24
Close #3451

Signed-off-by: Ekaterina Pavlova <ekt@morphbits.io>
2024-11-20 14:45:28 +03:00

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
}