Skip to content

add digital asset client with create_collection and mint capabilities #170

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

Tlazypanda
Copy link

Description

Add digital asset client with create_collection and mint capabilities. Also added an examples file to test out the flow.

Test Plan

Test file is yet to be added. + Functionality like transfer & indexing is remaining

Related Links

@Tlazypanda Tlazypanda requested review from gregnazario and a team as code owners June 6, 2025 06:46
Copy link
Contributor

@gregnazario gregnazario left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to add unit tests on some of the functions.

Additionally, run gofumpt -l -w . to format and golangci-lint run to lint.

It's looking pretty good though

Comment on lines 12 to 17
// SerializeString serializes a single string using the BCS format
func SerializeString(input string) ([]byte, error) {
return bcs.SerializeSingle(func(ser *bcs.Serializer) {
ser.WriteString(input)
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add this to bcs if it isn't there already

// === COLLECTION OPERATIONS ===

// CreateCollection creates a new collection using the Digital Asset standard
func (dac *DigitalAssetClient) CreateCollection(creator *Account, options CreateCollectionOptions) (*string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might not need to return *string, you can probably just return string and use "" as a failure mode return type. It's standard in Go, though I'm not sure I'm attached to either.

Comment on lines 125 to 128
descriptionBytes, err := SerializeString(options.Description)
if err != nil {
return nil, fmt.Errorf("failed to serialize description: %w", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's times like this, I wish Go had throws, so it wouldn't have to be duplicated for each of these.

I would normally say, make this all into a function, so you don't duplicate the error message.

Comment on lines +289 to +293
// Build the transaction
var functionName string
var args [][]byte

if options.IsSoulBound {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this breakdown

Comment on lines 294 to 312
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 address: %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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would probably move pieces like this to an internal function (lowercase starting letter) e.g. mintSoulboundArgs()

Comment on lines 494 to 507
case "address":
// For address, decode hex and use as 32-byte array
addressStr := strings.TrimPrefix(value, "0x")
addressBytes, parseErr := hex.DecodeString(addressStr)
if parseErr != nil {
return nil, fmt.Errorf("failed to parse address value '%s': %w", value, parseErr)
}
// Ensure it's 32 bytes
if len(addressBytes) < 32 {
padded := make([]byte, 32)
copy(padded[32-len(addressBytes):], addressBytes)
addressBytes = padded
}
valueBytes = addressBytes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to write a parser, there's already a parser for address

Comment on lines 484 to 492
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.SerializeSingle(func(s *bcs.Serializer) {
s.Bool(boolValue)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There actually should already be convertArg to convert all of these types already

Comment on lines +510 to +524
// 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)
})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I would use the existing convertArg

Comment on lines 525 to 531
default:
// For unknown types, fall back to BCS string
fmt.Printf("Warning: unknown property type '%s', treating as Move String\n", propertyType)
valueBytes, err = bcs.SerializeSingle(func(s *bcs.Serializer) {
s.WriteString(value)
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... maybe want to error out, rather than write something that might not parse

)

const (
testEd25519PrivateKey = "ed25519-priv-0xc5338cd251c22daa8c9c9cc94f498cc8a5c7e1d2e75287a5dda91096fe64efa5"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also just generate new keys each time, unless you need something consistent.

Comment on lines +772 to +778
// ObjectTypeTag is the TypeTag for 0x1::object::ObjectCore
var ObjectTypeTag = TypeTag{&StructTag{
Address: AccountOne,
Module: "object",
Name: "ObjectCore",
}}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would rename to ObjectCoreTypeTag

Comment on lines +161 to +174
// 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())
// }
// }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can just remove the commented code

Comment on lines +184 to +188
// main This example shows how to create and transfer digital assets (NFTs)
func main() {
// Run the main example
example(aptos.DevnetConfig)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may have to add a test file so it runs in CI. Look at the other examples

var valueBytes []byte
var err error

switch propertyType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to use the convertArg to convert it into the correct type for each. Then we don't need copied serialization methods

Comment on lines +329 to +339
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{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably take in the types, and convert them instead in the args functions, so you don't have to pass in a large list of bytes (where you might get one out of order)

@gregnazario
Copy link
Contributor

Update changelog please

valueBytes, err = bcs.SerializeU8(numValue)

case "u16":
// For u8, parse and BCS serialize as u8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated comments

// For u8, parse and BCS serialize as u8
numValue, parseErr := ConvertToU16(value)
if parseErr != nil {
return nil, fmt.Errorf("failed to parse u8 value '%s': %w", value, parseErr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants