Skip to content

Commit

Permalink
Added memo program support (#240)
Browse files Browse the repository at this point in the history
  • Loading branch information
PlamenHristov authored Oct 10, 2024
1 parent bea4b03 commit d09804c
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Thanks!
- [ ] Clients for Solana Program Library (SPL)
- [x] [SPL token](/programs/token)
- [x] [associated-token-account](/programs/associated-token-account)
- [ ] memo
- [x] memo
- [ ] name-service
- [ ] ...
- [ ] Client for Serum
Expand Down
142 changes: 142 additions & 0 deletions programs/memo/Create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright 2021 github.com/gagliardetto
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package memo

import (
"errors"
"fmt"
ag_binary "github.com/gagliardetto/binary"
ag_solanago "github.com/gagliardetto/solana-go"
ag_format "github.com/gagliardetto/solana-go/text/format"
ag_treeout "github.com/gagliardetto/treeout"
)

type Create struct {
// The memo message
Message []byte

// [0] = [SIGNER] Signer
// ··········· The account that will pay for the transaction
ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"`
}

// NewMemoInstructionBuilder creates a new `Memo` instruction builder.
func NewMemoInstructionBuilder() *Create {
nd := &Create{
AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1),
}
return nd
}

// SetMessage sets the memo message
func (inst *Create) SetMessage(message []byte) *Create {
inst.Message = message
return inst
}

// SetSigner sets the signer account
func (inst *Create) SetSigner(signer ag_solanago.PublicKey) *Create {
inst.AccountMetaSlice[0] = ag_solanago.Meta(signer).SIGNER()
return inst
}

func (inst *Create) GetSigner() *ag_solanago.AccountMeta {
return inst.AccountMetaSlice[0]
}

func (inst Create) Build() *MemoInstruction {

return &MemoInstruction{BaseVariant: ag_binary.BaseVariant{
Impl: inst,
TypeID: ag_binary.NoTypeIDDefaultID,
}}
}

// ValidateAndBuild validates the instruction parameters and accounts;
// if there is a validation error, it returns the error.
// Otherwise, it builds and returns the instruction.
func (inst Create) ValidateAndBuild() (*MemoInstruction, error) {
if err := inst.Validate(); err != nil {
return nil, err
}
return inst.Build(), nil
}

func (inst *Create) Validate() error {
// Check whether all (required) parameters are set:
{
if len(inst.Message) == 0 {
return errors.New("Message not set")
}
}

// Check whether all accounts are set:
for accIndex, acc := range inst.AccountMetaSlice {
if acc == nil {
return fmt.Errorf("ins.AccountMetaSlice[%v] is not set", accIndex)
}
}
return nil
}
func (inst *Create) EncodeToTree(parent ag_treeout.Branches) {
parent.Child(ag_format.Program("Memo", ag_solanago.MemoProgramID)).
ParentFunc(func(programBranch ag_treeout.Branches) {
programBranch.Child(ag_format.Instruction("Create")).
ParentFunc(func(instructionBranch ag_treeout.Branches) {
// Parameters of the instruction:
instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {
paramsBranch.Child(ag_format.Param("Message", inst.Message))
})

// Accounts of the instruction:
instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) {
accountsBranch.Child(ag_format.Meta("Signer", inst.AccountMetaSlice[0]))
})
})
})
}

func (inst Create) MarshalWithEncoder(encoder *ag_binary.Encoder) error {
// Serialize `Message` param:
{
err := encoder.Encode(inst.Message)
if err != nil {
return err
}
}
return nil
}

func (inst *Create) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error {
// Deserialize `Message` param:
{
err := decoder.Decode(&inst.Message)
if err != nil {
return err
}
}
return nil
}

// NewMemoInstruction declares a new Memo instruction with the provided parameters and accounts.
func NewMemoInstruction(
// Parameters:
message []byte,
// Accounts:
signer ag_solanago.PublicKey) *Create {
return NewMemoInstructionBuilder().
SetMessage(message).
SetSigner(signer)
}
107 changes: 107 additions & 0 deletions programs/memo/instructions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2021 github.com/gagliardetto
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package memo

import (
"bytes"
"fmt"
"github.com/davecgh/go-spew/spew"
ag_binary "github.com/gagliardetto/binary"
ag_solanago "github.com/gagliardetto/solana-go"
ag_text "github.com/gagliardetto/solana-go/text"
"github.com/gagliardetto/treeout"
)

var ProgramID = ag_solanago.MemoProgramID

func SetProgramID(pubkey ag_solanago.PublicKey) {
ProgramID = pubkey
ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
}

func init() {
ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction)
}

type MemoInstruction struct {
ag_binary.BaseVariant
}

func (inst *MemoInstruction) EncodeToTree(parent treeout.Branches) {
if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok {
enToTree.EncodeToTree(parent)
} else {
parent.Child(spew.Sdump(inst))
}
}

var InstructionImplDef = ag_binary.NewVariantDefinition(
ag_binary.NoTypeIDEncoding,
[]ag_binary.VariantType{
{
Name: "Memo", Type: (*Create)(nil),
},
},
)

func (inst *MemoInstruction) ProgramID() ag_solanago.PublicKey {
return ProgramID
}

func (inst *MemoInstruction) Accounts() (out []*ag_solanago.AccountMeta) {
return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts()
}

func (inst *MemoInstruction) Data() ([]byte, error) {
buf := new(bytes.Buffer)
if err := ag_binary.NewBinEncoder(buf).Encode(inst.Impl); err != nil {
return nil, fmt.Errorf("unable to encode instruction: %w", err)
}
return buf.Bytes(), nil
}

func (inst *MemoInstruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error {
return encoder.Encode(inst.Impl, option)
}

func (inst *MemoInstruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error {
return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef)
}

func (inst MemoInstruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error {
return encoder.Encode(inst.Impl)
}

func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) {
inst, err := DecodeInstruction(accounts, data)
if err != nil {
return nil, err
}
return inst, nil
}

func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*MemoInstruction, error) {
inst := new(MemoInstruction)
if err := ag_binary.NewBinDecoder(data).Decode(inst); err != nil {
return nil, fmt.Errorf("unable to decode instruction: %w", err)
}
if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok {
err := v.SetAccounts(accounts)
if err != nil {
return nil, fmt.Errorf("unable to set accounts for instruction: %w", err)
}
}
return inst, nil
}

0 comments on commit d09804c

Please sign in to comment.