Skip to content

Commit

Permalink
manifest: support NEP-24
Browse files Browse the repository at this point in the history
Close #3451

Signed-off-by: Ekaterina Pavlova <[email protected]>
  • Loading branch information
AliceInHunterland committed Oct 25, 2024
1 parent b8a65d3 commit d642090
Show file tree
Hide file tree
Showing 10 changed files with 609 additions and 5 deletions.
22 changes: 22 additions & 0 deletions examples/nft-nd/nft.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ you own a hash it's HASHY.
package nft

import (
"math/big"

"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
Expand Down Expand Up @@ -285,3 +287,23 @@ func Properties(id []byte) map[string]string {
}
return result
}

// RoyaltyRecipient contains information about the recipient and the royalty amount.
type RoyaltyRecipient struct {
Address interop.Hash160
Amount *big.Int
}

func RoyaltyInfo(tokenID []byte, royaltyToken interop.Hash160, salePrice int) []RoyaltyRecipient {
ctx := storage.GetReadOnlyContext()
owner := getOwnerOf(ctx, tokenID)
if !runtime.CheckWitness(owner) {
panic("invalid owner")
}
return []RoyaltyRecipient{
{
Address: owner,
Amount: big.NewInt(int64(salePrice / 10)),
},
}
}
18 changes: 15 additions & 3 deletions examples/nft-nd/nft.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: "HASHY NFT"
sourceurl: https://github.com/nspcc-dev/neo-go/
supportedstandards: ["NEP-11"]
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", "tokens", "properties"]
supportedstandards: ["NEP-11","NEP-24"]
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", "tokens", "properties", "royaltyInfo"]
events:
- name: Transfer
parameters:
Expand All @@ -13,7 +13,19 @@ events:
type: Integer
- name: tokenId
type: ByteArray
- name: RoyaltiesTransferred
parameters:
- name: royaltyToken
type: Hash160
- name: royaltyRecipient
type: Hash160
- name: buyer
type: Hash160
- name: tokenId
type: ByteArray
- name: amount
type: Integer
permissions:
- hash: fffdc93764dbaddd97c48f252a53ea4643faa3fd
methods: ["update", "destroy"]
- methods: ["onNEP11Payment"]
- methods: ["onNEP11Payment"]
32 changes: 32 additions & 0 deletions pkg/rpcclient/nep24/doc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package nep24_test

import (
"context"
"math/big"

"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24"
"github.com/nspcc-dev/neo-go/pkg/util"
)

func ExampleRoyaltyReader() {
// No error checking done at all, intentionally.
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})

// Safe methods are reachable with just an invoker, no need for an account there.
inv := invoker.New(c, nil)

// NEP-24 contract hash.
nep24Hash := util.Uint160{9, 8, 7}

// And a reader interface.
n24 := nep24.RoyaltyReader{Invoker: inv, Hash: nep24Hash}

// Get the royalty information for a token.
tokenID := []byte("someTokenID")
royaltyToken := util.Uint160{1, 2, 3}
salePrice := big.NewInt(1000)
royaltyInfo, _ := n24.RoyaltyInfo(tokenID, royaltyToken, salePrice)
_ = royaltyInfo
}
163 changes: 163 additions & 0 deletions pkg/rpcclient/nep24/royalty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
Package nep24 contains RPC wrappers to work with NEP-24 contracts.
Safe methods are encapsulated into RoyaltyReader structure.
*/
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
}

// 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
}
var royalties []RoyaltyRecipient
for _, item := range res.Stack {
royalty, ok := item.Value().([]stackitem.Item)
if !ok || len(royalty) != 2 {
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)
}
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
}
Loading

0 comments on commit d642090

Please sign in to comment.