diff --git a/CHANGELOG.md b/CHANGELOG.md index d7a6e0c..cadf615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to the Aptos Go SDK will be captured in this file. This chan adheres to the format set out by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Unreleased +- [`Feature`] Added Digital AssetClient `CreateCollection` & `MintToken`. # v1.11.0 (9/26/2025) - Refactor address internal packages to be more clear diff --git a/bcs/serializer.go b/bcs/serializer.go index 690ecdd..c55b015 100644 --- a/bcs/serializer.go +++ b/bcs/serializer.go @@ -332,6 +332,13 @@ func SerializeBytes(input []byte) ([]byte, error) { }) } +// SerializeString serializes a single string using the BCS format +func SerializeString(input string) ([]byte, error) { + return SerializeSingle(func(ser *Serializer) { + ser.WriteString(input) + }) +} + // SerializeSingle is a convenience function, to not have to create a serializer to serialize one value // // Here's an example for handling a nested byte array diff --git a/digital_asset_client.go b/digital_asset_client.go new file mode 100644 index 0000000..6694435 --- /dev/null +++ b/digital_asset_client.go @@ -0,0 +1,553 @@ +package aptos + +import ( + "encoding/hex" + "errors" + "fmt" + "strconv" + "strings" + + "github.com/aptos-labs/aptos-go-sdk/bcs" +) + +// DigitalAssetClient provides methods for interacting with Aptos Digital Assets (NFTs) and Fungible Assets +type DigitalAssetClient struct { + client *Client +} + +// NewDigitalAssetClient creates a new digital asset client +func NewDigitalAssetClient(client *Client) *DigitalAssetClient { + return &DigitalAssetClient{ + client: client, + } +} + +// Collection represents a collection of digital assets +type Collection struct { + Address string `json:"address"` + Name string `json:"name"` + Description string `json:"description"` + URI string `json:"uri"` + Creator string `json:"creator"` + MaxSupply uint64 `json:"max_supply"` + CurrentSupply uint64 `json:"current_supply"` + MutableDescription bool `json:"mutable_description"` + MutableRoyalty bool `json:"mutable_royalty"` + MutableURI bool `json:"mutable_uri"` + MutableTokenDesc bool `json:"mutable_token_description"` + MutableTokenName bool `json:"mutable_token_name"` + MutableTokenProps bool `json:"mutable_token_properties"` + MutableTokenURI bool `json:"mutable_token_uri"` + TokensBurnedIdsSupported bool `json:"tokens_burnable_by_creator"` + TokensFreezableByCreator bool `json:"tokens_freezable_by_creator"` + RoyaltyPayeeAddress string `json:"royalty_payee_address"` + RoyaltyPointsNumerator uint64 `json:"royalty_points_numerator"` + RoyaltyPointsDenominator uint64 `json:"royalty_points_denominator"` +} + +// Token represents a digital asset (NFT) +type Token struct { + Address string `json:"address"` + Name string `json:"name"` + Description string `json:"description"` + URI string `json:"uri"` + CollectionAddress string `json:"collection_address"` + CollectionName string `json:"collection_name"` + CreatorAddress string `json:"creator_address"` + OwnerAddress string `json:"owner_address"` + PropertyMap map[string]string `json:"property_map"` + PropertyMapKeys []string `json:"property_map_keys"` + PropertyMapValues []string `json:"property_map_values"` + PropertyMapTypes []string `json:"property_map_types"` + LastTransactionHash string `json:"last_transaction_hash"` + LastTransactionVersion uint64 `json:"last_transaction_version"` + IsSoulBound bool `json:"is_soul_bound"` + IsBurned bool `json:"is_burned"` + IsFrozen bool `json:"is_frozen"` +} + +// FungibleAsset represents a fungible asset balance +type FungibleAsset struct { + AssetAddress string `json:"asset_address"` + AssetName string `json:"asset_name"` + AssetSymbol string `json:"asset_symbol"` + AssetDecimals uint8 `json:"asset_decimals"` + AssetURI string `json:"asset_uri"` + Balance uint64 `json:"balance"` + OwnerAddress string `json:"owner_address"` + LastTransaction string `json:"last_transaction"` + IsFrozen bool `json:"is_frozen"` + SupplyAggregator string `json:"supply_aggregator"` +} + +// CreateCollectionOptions represents options for creating a collection +type CreateCollectionOptions struct { + Description string + Name string + URI string + MaxSupply uint64 + MutableDescription bool + MutableRoyalty bool + MutableURI bool + MutableTokenDescription bool + MutableTokenName bool + MutableTokenProperties bool + MutableTokenURI bool + TokensBurnableByCreator bool + TokensFreezableByCreator bool + RoyaltyPointsNumerator uint64 + RoyaltyPointsDenominator uint64 +} + +// MintTokenOptions represents options for minting a token +type MintTokenOptions struct { + CollectionName string + TokenName string + TokenDescription string + TokenURI string + PropertyKeys []string + PropertyValues []string + PropertyTypes []string + IsSoulBound bool +} + +// === COLLECTION OPERATIONS === + +// CreateCollection creates a new collection using the Digital Asset standard +func (dac *DigitalAssetClient) CreateCollection(creator *Account, options CreateCollectionOptions) (string, error) { + // Serialize arguments for BCS + descriptionBytes, err := bcs.SerializeString(options.Description) + if err != nil { + return CreateErrorMessage("description", err) + } + + nameBytes, err := bcs.SerializeString(options.Name) + if err != nil { + return CreateErrorMessage("name", err) + } + + uriBytes, err := bcs.SerializeString(options.URI) + if err != nil { + return CreateErrorMessage("URI", err) + } + + maxSupplyBytes, err := bcs.SerializeU64(options.MaxSupply) + if err != nil { + return CreateErrorMessage("max_supply", err) + } + + mutableDescBytes, err := bcs.SerializeBool(options.MutableDescription) + if err != nil { + return CreateErrorMessage("mutable_description", err) + } + + mutableRoyaltyBytes, err := bcs.SerializeBool(options.MutableRoyalty) + if err != nil { + return CreateErrorMessage("mutable_royalty", err) + } + + mutableURIBytes, err := bcs.SerializeBool(options.MutableURI) + if err != nil { + return CreateErrorMessage("mutable_uri", err) + } + + mutableTokenDescBytes, err := bcs.SerializeBool(options.MutableTokenDescription) + if err != nil { + return CreateErrorMessage("mutable_token_description", err) + } + + mutableTokenNameBytes, err := bcs.SerializeBool(options.MutableTokenName) + if err != nil { + return CreateErrorMessage("mutable_token_name", err) + } + + mutableTokenPropsBytes, err := bcs.SerializeBool(options.MutableTokenProperties) + if err != nil { + return CreateErrorMessage("mutable_token_properties", err) + } + + mutableTokenURIBytes, err := bcs.SerializeBool(options.MutableTokenURI) + if err != nil { + return CreateErrorMessage("mutable_token_uri", err) + } + + tokensBurnableBytes, err := bcs.SerializeBool(options.TokensBurnableByCreator) + if err != nil { + return CreateErrorMessage("tokens_burnable_by_creator", err) + } + + tokensFreezableBytes, err := bcs.SerializeBool(options.TokensFreezableByCreator) + if err != nil { + return CreateErrorMessage("tokens_freezable_by_creator", err) + } + + royaltyNumeratorBytes, err := bcs.SerializeU64(options.RoyaltyPointsNumerator) + if err != nil { + return CreateErrorMessage("royalty_points_numerator", err) + } + + royaltyDenominatorBytes, err := bcs.SerializeU64(options.RoyaltyPointsDenominator) + if err != nil { + return CreateErrorMessage("royalty_points_denominator", err) + } + + // Build the transaction + rawTxn, err := dac.client.BuildTransaction(creator.AccountAddress(), TransactionPayload{ + Payload: &EntryFunction{ + Module: ModuleId{ + Address: AccountFour, // 0x4 + Name: "aptos_token", + }, + Function: "create_collection", + ArgTypes: []TypeTag{}, + Args: [][]byte{ + descriptionBytes, + maxSupplyBytes, + nameBytes, + uriBytes, + mutableDescBytes, + mutableRoyaltyBytes, + mutableURIBytes, + mutableTokenDescBytes, + mutableTokenNameBytes, + mutableTokenPropsBytes, + mutableTokenURIBytes, + tokensBurnableBytes, + tokensFreezableBytes, + royaltyNumeratorBytes, + royaltyDenominatorBytes, + }, + }, + }) + if err != nil { + return "", fmt.Errorf("failed to build transaction: %w", err) + } + + // Sign and submit the transaction + signedTxn, err := rawTxn.SignedTransaction(creator) + if err != nil { + return "", fmt.Errorf("failed to sign transaction: %w", err) + } + + submitResult, err := dac.client.SubmitTransaction(signedTxn) + if err != nil { + return "", fmt.Errorf("failed to submit transaction: %w", err) + } + + return submitResult.Hash, nil +} + +func CreateErrorMessage(attr string, err error) (string, error) { + return "", fmt.Errorf("failed to serialize %s: %w", attr, err) +} + +// === TOKEN OPERATIONS === + +// MintToken mints a new token (NFT) to the specified collection +func (dac *DigitalAssetClient) MintToken(creator *Account, recipient AccountAddress, options MintTokenOptions) (string, error) { + // Serialize arguments + collectionBytes, err := bcs.SerializeString(options.CollectionName) + if err != nil { + return CreateErrorMessage("collection", err) + } + + descriptionBytes, err := bcs.SerializeString(options.TokenDescription) + if err != nil { + return CreateErrorMessage("description", err) + } + + nameBytes, err := bcs.SerializeString(options.TokenName) + if err != nil { + return CreateErrorMessage("name", err) + } + + uriBytes, err := bcs.SerializeString(options.TokenURI) + if err != nil { + return CreateErrorMessage("URI", err) + } + + // Serialize property keys + propertyKeysBytes, err := SerializeVectorString(options.PropertyKeys) + if err != nil { + return CreateErrorMessage("property_keys", err) + } + + // Serialize property types + propertyTypesBytes, err := SerializeVectorString(options.PropertyTypes) + if err != nil { + return CreateErrorMessage("property_types", err) + } + + // Serialize property values (as bytes) + propertyValuesBytes, err := SerializePropertyValues(options.PropertyValues, options.PropertyTypes) + if err != nil { + return CreateErrorMessage("property_values", err) + } + + // Build the transaction + var functionName string + var args [][]byte + + if options.IsSoulBound { + functionName, args, err = mintSoulboundArgs(recipient, collectionBytes, descriptionBytes, nameBytes, uriBytes, propertyKeysBytes, propertyTypesBytes, propertyValuesBytes) + if err != nil { + return "", err + } + } else { + functionName, args = mintArgs(collectionBytes, descriptionBytes, nameBytes, uriBytes, propertyKeysBytes, propertyTypesBytes, propertyValuesBytes) + } + + rawTxn, err := dac.client.BuildTransaction(creator.AccountAddress(), TransactionPayload{ + Payload: &EntryFunction{ + Module: ModuleId{ + Address: AccountFour, // 0x4 + Name: "aptos_token", + }, + Function: functionName, + ArgTypes: []TypeTag{}, + Args: args, + }, + }) + if err != nil { + return "", fmt.Errorf("failed to build transaction: %w", err) + } + + // Sign and submit the transaction + signedTxn, err := rawTxn.SignedTransaction(creator) + if err != nil { + return "", fmt.Errorf("failed to sign transaction: %w", err) + } + + submitResult, err := dac.client.SubmitTransaction(signedTxn) + if err != nil { + return "", fmt.Errorf("failed to submit transaction: %w", err) + } + + return submitResult.Hash, nil +} + +func mintSoulboundArgs(recipient AccountAddress, collectionBytes []byte, descriptionBytes []byte, nameBytes []byte, uriBytes []byte, propertyKeysBytes []byte, propertyTypesBytes []byte, propertyValuesBytes []byte) (string, [][]byte, error) { + functionName := "mint_soul_bound" + + // Serialize the soul_bound_to address + recipientBytes, err := bcs.Serialize(&recipient) + if err != nil { + return "", nil, fmt.Errorf("failed to serialize recipient: %w", err) + } + + // mint_soul_bound requires the recipient address as the last argument + args := [][]byte{ + collectionBytes, + descriptionBytes, + nameBytes, + uriBytes, + propertyKeysBytes, + propertyTypesBytes, + propertyValuesBytes, + recipientBytes, // soul_bound_to: address + } + + return functionName, args, err +} + +func mintArgs(collectionBytes []byte, descriptionBytes []byte, nameBytes []byte, uriBytes []byte, propertyKeysBytes []byte, propertyTypesBytes []byte, propertyValuesBytes []byte) (string, [][]byte) { + functionName := "mint" + + // mint_soul_bound requires the recipient address as the last argument + args := [][]byte{ + collectionBytes, + descriptionBytes, + nameBytes, + uriBytes, + propertyKeysBytes, + propertyTypesBytes, + propertyValuesBytes, + } + + return functionName, args +} + +// TransferToken transfers a token from one account to another +func (dac *DigitalAssetClient) TransferToken(sender *Account, tokenAddress AccountAddress, recipient AccountAddress) (*string, error) { + // Serialize arguments + tokenAddressBytes, err := bcs.Serialize(&tokenAddress) + if err != nil { + return nil, fmt.Errorf("failed to serialize token address: %w", err) + } + + recipientBytes, err := bcs.Serialize(&recipient) + if err != nil { + return nil, fmt.Errorf("failed to serialize recipient: %w", err) + } + + // Build the transaction + rawTxn, err := dac.client.BuildTransaction(sender.AccountAddress(), TransactionPayload{ + Payload: &EntryFunction{ + Module: ModuleId{ + Address: AccountOne, // 0x1 + Name: "object", + }, + Function: "transfer", + ArgTypes: []TypeTag{ObjectTypeTag}, + Args: [][]byte{ + tokenAddressBytes, + recipientBytes, + }, + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to build transaction: %w", err) + } + + // Sign and submit the transaction + signedTxn, err := rawTxn.SignedTransaction(sender) + if err != nil { + return nil, fmt.Errorf("failed to sign transaction: %w", err) + } + + submitResult, err := dac.client.SubmitTransaction(signedTxn) + if err != nil { + return nil, fmt.Errorf("failed to submit transaction: %w", err) + } + + return &submitResult.Hash, nil +} + +// === HELPER FUNCTIONS FOR BCS SERIALIZATION === + +// SerializeVectorString serializes a vector of strings for BCS + +func SerializeVectorString(strings []string) ([]byte, error) { + return bcs.SerializeSingle(func(ser *bcs.Serializer) { + bcs.SerializeSequenceWithFunction(strings, ser, func(ser *bcs.Serializer, item string) { + ser.WriteString(item) + }) + }) +} + +// SerializeVectorBytes serializes a vector of byte arrays for BCS +func SerializeVectorBytes(values []string) ([]byte, error) { + return bcs.SerializeSingle(func(ser *bcs.Serializer) { + byteArrays := make([][]byte, len(values)) + for i, value := range values { + byteArrays[i] = []byte(value) + } + + bcs.SerializeSequenceWithFunction(byteArrays, ser, func(ser *bcs.Serializer, item []byte) { + ser.WriteBytes(item) + }) + }) +} + +func SerializePropertyValues(values []string, types []string) ([]byte, error) { + if len(values) != len(types) { + return nil, errors.New("property values and types must have the same length") + } + + // Convert each value to BCS-serialized bytes according to its type + byteArrays := make([][]byte, len(values)) + + for i, value := range values { + propertyType := types[i] + + var valueBytes []byte + var err error + + switch propertyType { + case "0x1::string::String": + // For Move String type, BCS serialize the string + valueBytes, err = bcs.SerializeSingle(func(s *bcs.Serializer) { + s.WriteString(value) + }) + + case "u8": + // For u8, parse and BCS serialize as u8 + numValue, parseErr := ConvertToU8(value) + if parseErr != nil { + return nil, fmt.Errorf("failed to parse u8 value '%s': %w", value, parseErr) + } + valueBytes, err = bcs.SerializeU8(numValue) + + case "u16": + // For u16, parse and BCS serialize as u16 + numValue, parseErr := ConvertToU16(value) + if parseErr != nil { + return nil, fmt.Errorf("failed to parse u8 value '%s': %w", value, parseErr) + } + valueBytes, err = bcs.SerializeU16(numValue) + + case "u32": + // For u32, parse and BCS serialize as u32 + numValue, parseErr := ConvertToU32(value) + if parseErr != nil { + return nil, fmt.Errorf("failed to parse u8 value '%s': %w", value, parseErr) + } + valueBytes, err = bcs.SerializeU32(numValue) + + case "u64": + // For u64, parse and BCS serialize as u64 + numValue, parseErr := ConvertToU64(value) + if parseErr != nil { + return nil, fmt.Errorf("failed to parse u64 value '%s': %w", value, parseErr) + } + valueBytes, err = bcs.SerializeU64(numValue) + + case "u128": + // For u128, parse as bigInt and BCS serialize as u128 + bigIntValue, parseErr := StrToBigInt(value) + if parseErr != nil { + return nil, fmt.Errorf("failed to parse bigInt value '%s': %w", value, parseErr) + } + valueBytes, err = bcs.SerializeU128(*bigIntValue) + + case "bool": + // For bool, parse and BCS serialize as bool + boolValue, parseErr := strconv.ParseBool(value) + if parseErr != nil { + return nil, fmt.Errorf("failed to parse bool value '%s': %w", value, parseErr) + } + valueBytes, err = bcs.SerializeBool(boolValue) + + case "address": + // For address, decode hex and use as 32-byte array + addressBytes, parseErr := ConvertToAddress(value) + if parseErr != nil { + return nil, fmt.Errorf("failed to parse address value '%s': %w", value, parseErr) + } + valueBytes, err = bcs.Serialize(addressBytes) + + case "vector": + // For byte vector, decode from hex or use as UTF-8, then BCS serialize + var bytesValue []byte + if strings.HasPrefix(value, "0x") { + bytesValue, err = hex.DecodeString(value[2:]) + if err != nil { + return nil, fmt.Errorf("failed to parse hex bytes value '%s': %w", value, err) + } + } else { + bytesValue = []byte(value) + } + // BCS serialize the byte vector + valueBytes, err = bcs.SerializeSingle(func(s *bcs.Serializer) { + s.WriteBytes(bytesValue) + }) + + default: + // For unknown types, fall back to BCS string + return nil, fmt.Errorf("failed to serialize, unknown property type '%s'", propertyType) + } + + if err != nil { + return nil, fmt.Errorf("failed to serialize property value '%s' of type '%s': %w", value, propertyType, err) + } + + byteArrays[i] = valueBytes + } + + // Serialize as vector> using BCS + return bcs.SerializeSingle(func(ser *bcs.Serializer) { + bcs.SerializeSequenceWithFunction(byteArrays, ser, func(ser *bcs.Serializer, item []byte) { + ser.WriteBytes(item) + }) + }) +} diff --git a/examples/digital_asset/main.go b/examples/digital_asset/main.go new file mode 100644 index 0000000..aa92997 --- /dev/null +++ b/examples/digital_asset/main.go @@ -0,0 +1,188 @@ +// digital_asset is an example of how to create and transfer digital assets (NFTs) +package main + +import ( + "fmt" + + "github.com/aptos-labs/aptos-go-sdk" +) + +const ( + FundAmount = uint64(100_000_000) +) + +func example(networkConfig aptos.NetworkConfig) { + // Create a client for Aptos + client, err := aptos.NewClient(networkConfig) + if err != nil { + panic("Failed to create client:" + err.Error()) + } + + // Create account locally for creator + creator, err := aptos.NewEd25519Account() + if err != nil { + panic("Failed to create creator:" + err.Error()) + } + + // Create a collector account for receiving NFTs + collector, err := aptos.NewEd25519Account() + if err != nil { + panic("Failed to create collector:" + err.Error()) + } + + // Fund both accounts with the faucet + fmt.Printf("CREATOR: %s\n", creator.Address.String()) + fmt.Printf("COLLECTOR: %s\n", collector.Address.String()) + + err = client.Fund(creator.Address, FundAmount) + if err != nil { + panic("Failed to fund creator:" + err.Error()) + } + + err = client.Fund(collector.Address, FundAmount) + if err != nil { + panic("Failed to fund collector:" + err.Error()) + } + + // Create digital asset client + daClient := aptos.NewDigitalAssetClient(client) + + // Create a collection + fmt.Println("\n=== Creating NFT Collection ===") + collectionOptions := aptos.CreateCollectionOptions{ + Description: "Epic Space Warriors NFT Collection", + Name: "SpaceWarriors", + URI: "https://spacewarriors.example.com/collection", + MaxSupply: 10000, + MutableDescription: true, + MutableRoyalty: true, + MutableURI: true, + MutableTokenDescription: true, + MutableTokenName: true, + MutableTokenProperties: true, + MutableTokenURI: true, + TokensBurnableByCreator: true, + TokensFreezableByCreator: false, + RoyaltyPointsNumerator: 250, // 2.5% + RoyaltyPointsDenominator: 10000, + } + + collectionTxHash, err := daClient.CreateCollection(creator, collectionOptions) + if err != nil { + panic("Failed to create collection:" + err.Error()) + } + fmt.Printf("Collection created with transaction hash: %s\n", collectionTxHash) + + // Wait for collection creation + _, err = client.WaitForTransaction(collectionTxHash) + if err != nil { + panic("Failed to wait for collection creation:" + err.Error()) + } + + // Mint NFTs + fmt.Println("\n=== Minting NFTs ===") + + // Mint NFT #1 to creator + mintOptions1 := aptos.MintTokenOptions{ + CollectionName: "SpaceWarriors", + TokenName: "Space Warrior #001", + TokenDescription: "A legendary space warrior with plasma sword", + TokenURI: "https://spacewarriors.example.com/nft/001", + PropertyKeys: []string{"rarity", "faction", "level", "weapon"}, + PropertyValues: []string{"legendary", "solar_federation", "85", "plasma_sword"}, + PropertyTypes: []string{"0x1::string::String", "0x1::string::String", "u64", "0x1::string::String"}, + IsSoulBound: false, + } + + nftTxHash1, err := daClient.MintToken(creator, creator.AccountAddress(), mintOptions1) + if err != nil { + panic("Failed to mint NFT #1:" + err.Error()) + } + fmt.Printf("NFT #1 minted with transaction hash: %s\n", nftTxHash1) + + // Wait for minting + _, err = client.WaitForTransaction(nftTxHash1) + if err != nil { + panic("Failed to wait for NFT #1 minting:" + err.Error()) + } + + // Mint NFT #2 to collector + mintOptions2 := aptos.MintTokenOptions{ + CollectionName: "SpaceWarriors", + TokenName: "Space Warrior #002", + TokenDescription: "An epic space warrior with energy shield", + TokenURI: "https://spacewarriors.example.com/nft/002", + PropertyKeys: []string{"rarity", "faction", "level", "weapon"}, + PropertyValues: []string{"epic", "lunar_alliance", "72", "energy_shield"}, + PropertyTypes: []string{"0x1::string::String", "0x1::string::String", "u64", "0x1::string::String"}, + IsSoulBound: false, + } + + nftTxHash2, err := daClient.MintToken(creator, collector.AccountAddress(), mintOptions2) + if err != nil { + panic("Failed to mint NFT #2:" + err.Error()) + } + fmt.Printf("NFT #2 minted with transaction hash: %s\n", nftTxHash2) + + // Wait for minting + _, err = client.WaitForTransaction(nftTxHash2) + if err != nil { + panic("Failed to wait for NFT #2 minting:" + err.Error()) + } + + // Mint a soul-bound NFT + mintOptions3 := aptos.MintTokenOptions{ + CollectionName: "SpaceWarriors", + TokenName: "First Collection Creator Certificate", + TokenDescription: "A permanent achievement certificate for creating the first collection", + TokenURI: "https://spacewarriors.example.com/certificates/001", + PropertyKeys: []string{"achievement", "timestamp", "rarity"}, + PropertyValues: []string{"first_collection_creator", "1640995200", "mythic"}, + PropertyTypes: []string{"0x1::string::String", "u64", "0x1::string::String"}, + IsSoulBound: true, // This will be soul-bound + } + + nftTxHash3, err := daClient.MintToken(creator, creator.AccountAddress(), mintOptions3) + if err != nil { + panic("Failed to mint soul-bound NFT:" + err.Error()) + } + fmt.Printf("Soul-bound NFT minted with transaction hash: %s\n", nftTxHash3) + + // Wait for minting + _, err = client.WaitForTransaction(nftTxHash3) + if err != nil { + panic("Failed to wait for soul-bound NFT minting:" + err.Error()) + } + + // Transfer NFT (Note: In reality, you'd need to get the actual token address from events) + fmt.Println("\n=== Transferring NFT ===") + + // You would parse the token address from the mint transaction events + // tokenAddress := // Placeholder + + // transferTxHash, err := daClient.TransferToken(creator, tokenAddress, collector.AccountAddress()) + // if err != nil { + // fmt.Printf("Warning: Failed to transfer NFT (expected without proper token address): %v\n", err) + // } else { + // fmt.Printf("NFT transferred with transaction hash: %s\n", *transferTxHash) + + // // Wait for transfer + // _, err = client.WaitForTransaction(*transferTxHash) + // if err != nil { + // panic("Failed to wait for transfer:" + err.Error()) + // } + // } + + fmt.Println("\n=== Digital Asset Example Complete ===") + fmt.Println("Successfully:") + fmt.Println("✅ Created NFT collection with customizable properties") + fmt.Println("✅ Minted regular NFTs with rich property maps") + fmt.Println("✅ Minted soul-bound NFT (non-transferable)") + // fmt.Println("✅ Demonstrated transfer operations") +} + +// main This example shows how to create and transfer digital assets (NFTs) +func main() { + // Run the main example + example(aptos.DevnetConfig) +} diff --git a/typetag.go b/typetag.go index ed2bb09..24a93b3 100644 --- a/typetag.go +++ b/typetag.go @@ -769,4 +769,11 @@ var AptosCoinTypeTag = TypeTag{&StructTag{ Name: "AptosCoin", }} +// ObjectTypeTag is the TypeTag for 0x1::object::ObjectCore +var ObjectTypeTag = TypeTag{&StructTag{ + Address: AccountOne, + Module: "object", + Name: "ObjectCore", +}} + // endregion