From db2fdaa42ed9062d23eb51ae278856197fb66633 Mon Sep 17 00:00:00 2001 From: Sonic Date: Fri, 10 Apr 2026 16:31:34 +0300 Subject: [PATCH] feat: add token-2022 extensions --- .../ConfidentialMintBurnExtension.go | 129 +++ .../ConfidentialTransferExtension.go | 144 ++++ .../ConfidentialTransferFeeExtension.go | 134 ++++ programs/token-2022/CpiGuardExtension.go | 139 ++++ programs/token-2022/CreateNativeMint.go | 117 +++ .../DefaultAccountStateExtension.go | 170 ++++ .../token-2022/GroupMemberPointerExtension.go | 190 +++++ programs/token-2022/GroupPointerExtension.go | 190 +++++ .../InitializeNonTransferableMint.go | 83 ++ .../token-2022/InitializePermanentDelegate.go | 106 +++ .../InterestBearingMintExtension.go | 195 +++++ programs/token-2022/MemoTransferExtension.go | 139 ++++ .../token-2022/MetadataPointerExtension.go | 192 +++++ programs/token-2022/PausableExtension.go | 161 ++++ .../token-2022/PermissionedBurnExtension.go | 94 +++ programs/token-2022/Reallocate.go | 191 +++++ .../token-2022/ScaledUiAmountExtension.go | 210 +++++ programs/token-2022/TransferFeeExtension.go | 430 ++++++++++ programs/token-2022/TransferHookExtension.go | 223 ++++++ programs/token-2022/UnwrapLamports.go | 191 +++++ programs/token-2022/WithdrawExcessLamports.go | 148 ++++ programs/token-2022/extension_states.go | 586 ++++++++++++++ programs/token-2022/extension_test.go | 755 ++++++++++++++++++ programs/token-2022/instructions.go | 168 ++++ 24 files changed, 5085 insertions(+) create mode 100644 programs/token-2022/ConfidentialMintBurnExtension.go create mode 100644 programs/token-2022/ConfidentialTransferExtension.go create mode 100644 programs/token-2022/ConfidentialTransferFeeExtension.go create mode 100644 programs/token-2022/CpiGuardExtension.go create mode 100644 programs/token-2022/CreateNativeMint.go create mode 100644 programs/token-2022/DefaultAccountStateExtension.go create mode 100644 programs/token-2022/GroupMemberPointerExtension.go create mode 100644 programs/token-2022/GroupPointerExtension.go create mode 100644 programs/token-2022/InitializeNonTransferableMint.go create mode 100644 programs/token-2022/InitializePermanentDelegate.go create mode 100644 programs/token-2022/InterestBearingMintExtension.go create mode 100644 programs/token-2022/MemoTransferExtension.go create mode 100644 programs/token-2022/MetadataPointerExtension.go create mode 100644 programs/token-2022/PausableExtension.go create mode 100644 programs/token-2022/PermissionedBurnExtension.go create mode 100644 programs/token-2022/Reallocate.go create mode 100644 programs/token-2022/ScaledUiAmountExtension.go create mode 100644 programs/token-2022/TransferFeeExtension.go create mode 100644 programs/token-2022/TransferHookExtension.go create mode 100644 programs/token-2022/UnwrapLamports.go create mode 100644 programs/token-2022/WithdrawExcessLamports.go create mode 100644 programs/token-2022/extension_states.go create mode 100644 programs/token-2022/extension_test.go diff --git a/programs/token-2022/ConfidentialMintBurnExtension.go b/programs/token-2022/ConfidentialMintBurnExtension.go new file mode 100644 index 00000000..a0c01f15 --- /dev/null +++ b/programs/token-2022/ConfidentialMintBurnExtension.go @@ -0,0 +1,129 @@ +package token2022 + +import ( + "errors" + + 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" +) + +// ConfidentialMintBurn sub-instruction IDs. +const ( + ConfidentialMintBurn_InitializeMint uint8 = iota + ConfidentialMintBurn_UpdateDecryptableSupply + ConfidentialMintBurn_RotateSupplyElGamalPubkey + ConfidentialMintBurn_Mint + ConfidentialMintBurn_Burn +) + +// ConfidentialMintBurnExtension is the instruction wrapper for the +// ConfidentialMintBurn extension (ID 42). +// This is a complex extension involving encrypted supply and ZK proofs. +type ConfidentialMintBurnExtension struct { + SubInstruction uint8 + RawData []byte + + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *ConfidentialMintBurnExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + return nil +} + +func (slice ConfidentialMintBurnExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst ConfidentialMintBurnExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_ConfidentialMintBurnExtension), + }} +} + +func (inst ConfidentialMintBurnExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *ConfidentialMintBurnExtension) Validate() error { + if len(inst.Accounts) == 0 { + return errors.New("accounts is empty") + } + return nil +} + +func (inst *ConfidentialMintBurnExtension) EncodeToTree(parent ag_treeout.Branches) { + names := []string{ + "InitializeMint", "UpdateDecryptableSupply", + "RotateSupplyElGamalPubkey", "Mint", "Burn", + } + name := "Unknown" + if int(inst.SubInstruction) < len(names) { + name = names[inst.SubInstruction] + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("ConfidentialMintBurn." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("RawData (len)", len(inst.RawData))) + }) + }) + }) +} + +func (obj ConfidentialMintBurnExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + err = encoder.WriteUint8(obj.SubInstruction) + if err != nil { + return err + } + if len(obj.RawData) > 0 { + err = encoder.WriteBytes(obj.RawData, false) + if err != nil { + return err + } + } + return nil +} + +func (obj *ConfidentialMintBurnExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + if err != nil { + return err + } + remaining := decoder.Remaining() + if remaining > 0 { + obj.RawData, err = decoder.ReadNBytes(remaining) + if err != nil { + return err + } + } + return nil +} + +// NewConfidentialMintBurnInstruction creates a raw confidential mint/burn extension instruction. +func NewConfidentialMintBurnInstruction( + subInstruction uint8, + rawData []byte, + accounts ...ag_solanago.AccountMeta, +) *ConfidentialMintBurnExtension { + inst := &ConfidentialMintBurnExtension{ + SubInstruction: subInstruction, + RawData: rawData, + Accounts: make(ag_solanago.AccountMetaSlice, len(accounts)), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + for i := range accounts { + inst.Accounts[i] = &accounts[i] + } + return inst +} diff --git a/programs/token-2022/ConfidentialTransferExtension.go b/programs/token-2022/ConfidentialTransferExtension.go new file mode 100644 index 00000000..c14a4098 --- /dev/null +++ b/programs/token-2022/ConfidentialTransferExtension.go @@ -0,0 +1,144 @@ +package token2022 + +import ( + "errors" + + 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" +) + +// ConfidentialTransfer sub-instruction IDs. +const ( + ConfidentialTransfer_InitializeMint uint8 = iota + ConfidentialTransfer_UpdateMint + ConfidentialTransfer_ConfigureAccount + ConfidentialTransfer_ApproveAccount + ConfidentialTransfer_EmptyAccount + ConfidentialTransfer_Deposit + ConfidentialTransfer_Withdraw + ConfidentialTransfer_Transfer + ConfidentialTransfer_ApplyPendingBalance + ConfidentialTransfer_EnableConfidentialCredits + ConfidentialTransfer_DisableConfidentialCredits + ConfidentialTransfer_EnableNonConfidentialCredits + ConfidentialTransfer_DisableNonConfidentialCredits + ConfidentialTransfer_TransferWithSplitProofs + ConfidentialTransfer_TransferWithSplitProofsInParallel +) + +// ConfidentialTransferExtension is the instruction wrapper for the ConfidentialTransfer extension (ID 27). +// This is a complex extension with many sub-instructions involving zero-knowledge proofs. +// The raw sub-instruction data is preserved for encoding/decoding. +type ConfidentialTransferExtension struct { + SubInstruction uint8 + // Raw data for the sub-instruction (after the sub-instruction byte). + RawData []byte + + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *ConfidentialTransferExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + return nil +} + +func (slice ConfidentialTransferExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst ConfidentialTransferExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_ConfidentialTransferExtension), + }} +} + +func (inst ConfidentialTransferExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *ConfidentialTransferExtension) Validate() error { + if len(inst.Accounts) == 0 { + return errors.New("accounts is empty") + } + return nil +} + +func (inst *ConfidentialTransferExtension) EncodeToTree(parent ag_treeout.Branches) { + names := []string{ + "InitializeMint", "UpdateMint", "ConfigureAccount", "ApproveAccount", + "EmptyAccount", "Deposit", "Withdraw", "Transfer", + "ApplyPendingBalance", "EnableConfidentialCredits", "DisableConfidentialCredits", + "EnableNonConfidentialCredits", "DisableNonConfidentialCredits", + "TransferWithSplitProofs", "TransferWithSplitProofsInParallel", + } + name := "Unknown" + if int(inst.SubInstruction) < len(names) { + name = names[inst.SubInstruction] + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("ConfidentialTransfer." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("RawData (len)", len(inst.RawData))) + }) + }) + }) +} + +func (obj ConfidentialTransferExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + err = encoder.WriteUint8(obj.SubInstruction) + if err != nil { + return err + } + if len(obj.RawData) > 0 { + err = encoder.WriteBytes(obj.RawData, false) + if err != nil { + return err + } + } + return nil +} + +func (obj *ConfidentialTransferExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + if err != nil { + return err + } + remaining := decoder.Remaining() + if remaining > 0 { + obj.RawData, err = decoder.ReadNBytes(remaining) + if err != nil { + return err + } + } + return nil +} + +// NewConfidentialTransferInstruction creates a raw confidential transfer extension instruction. +// Due to the complexity of ZK proof data, this provides a low-level interface. +func NewConfidentialTransferInstruction( + subInstruction uint8, + rawData []byte, + accounts ...ag_solanago.AccountMeta, +) *ConfidentialTransferExtension { + inst := &ConfidentialTransferExtension{ + SubInstruction: subInstruction, + RawData: rawData, + Accounts: make(ag_solanago.AccountMetaSlice, len(accounts)), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + for i := range accounts { + inst.Accounts[i] = &accounts[i] + } + return inst +} diff --git a/programs/token-2022/ConfidentialTransferFeeExtension.go b/programs/token-2022/ConfidentialTransferFeeExtension.go new file mode 100644 index 00000000..b12b0325 --- /dev/null +++ b/programs/token-2022/ConfidentialTransferFeeExtension.go @@ -0,0 +1,134 @@ +package token2022 + +import ( + "errors" + + 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" +) + +// ConfidentialTransferFee sub-instruction IDs. +const ( + ConfidentialTransferFee_InitializeConfidentialTransferFeeConfig uint8 = iota + ConfidentialTransferFee_WithdrawWithheldTokensFromMint + ConfidentialTransferFee_WithdrawWithheldTokensFromAccounts + ConfidentialTransferFee_HarvestWithheldTokensToMint + ConfidentialTransferFee_EnableHarvestToMint + ConfidentialTransferFee_DisableHarvestToMint +) + +// ConfidentialTransferFeeExtension is the instruction wrapper for the +// ConfidentialTransferFee extension (ID 37). +// This is a complex extension involving encrypted fee amounts and ZK proofs. +type ConfidentialTransferFeeExtension struct { + SubInstruction uint8 + RawData []byte + + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *ConfidentialTransferFeeExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + return nil +} + +func (slice ConfidentialTransferFeeExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst ConfidentialTransferFeeExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_ConfidentialTransferFeeExtension), + }} +} + +func (inst ConfidentialTransferFeeExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *ConfidentialTransferFeeExtension) Validate() error { + if len(inst.Accounts) == 0 { + return errors.New("accounts is empty") + } + return nil +} + +func (inst *ConfidentialTransferFeeExtension) EncodeToTree(parent ag_treeout.Branches) { + names := []string{ + "InitializeConfidentialTransferFeeConfig", + "WithdrawWithheldTokensFromMint", + "WithdrawWithheldTokensFromAccounts", + "HarvestWithheldTokensToMint", + "EnableHarvestToMint", + "DisableHarvestToMint", + } + name := "Unknown" + if int(inst.SubInstruction) < len(names) { + name = names[inst.SubInstruction] + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("ConfidentialTransferFee." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("RawData (len)", len(inst.RawData))) + }) + }) + }) +} + +func (obj ConfidentialTransferFeeExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + err = encoder.WriteUint8(obj.SubInstruction) + if err != nil { + return err + } + if len(obj.RawData) > 0 { + err = encoder.WriteBytes(obj.RawData, false) + if err != nil { + return err + } + } + return nil +} + +func (obj *ConfidentialTransferFeeExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + if err != nil { + return err + } + remaining := decoder.Remaining() + if remaining > 0 { + obj.RawData, err = decoder.ReadNBytes(remaining) + if err != nil { + return err + } + } + return nil +} + +// NewConfidentialTransferFeeInstruction creates a raw confidential transfer fee extension instruction. +func NewConfidentialTransferFeeInstruction( + subInstruction uint8, + rawData []byte, + accounts ...ag_solanago.AccountMeta, +) *ConfidentialTransferFeeExtension { + inst := &ConfidentialTransferFeeExtension{ + SubInstruction: subInstruction, + RawData: rawData, + Accounts: make(ag_solanago.AccountMetaSlice, len(accounts)), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + for i := range accounts { + inst.Accounts[i] = &accounts[i] + } + return inst +} diff --git a/programs/token-2022/CpiGuardExtension.go b/programs/token-2022/CpiGuardExtension.go new file mode 100644 index 00000000..4c994ea3 --- /dev/null +++ b/programs/token-2022/CpiGuardExtension.go @@ -0,0 +1,139 @@ +package token2022 + +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" +) + +// CpiGuard sub-instruction IDs. +const ( + CpiGuard_Enable uint8 = iota + CpiGuard_Disable +) + +// CpiGuardExtension is the instruction wrapper for the CpiGuard extension (ID 34). +// Sub-instructions: Enable, Disable. +type CpiGuardExtension struct { + SubInstruction uint8 + + // [0] = [WRITE] account + // ··········· The token account to update. + // + // [1] = [] owner + // ··········· The account's owner or multisig. + // + // [2...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *CpiGuardExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + return nil +} + +func (slice CpiGuardExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst CpiGuardExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_CpiGuardExtension), + }} +} + +func (inst CpiGuardExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *CpiGuardExtension) Validate() error { + if len(inst.Accounts) < 2 || inst.Accounts[0] == nil { + return errors.New("accounts.Account is not set") + } + if inst.Accounts[1] == nil { + return errors.New("accounts.Owner is not set") + } + if !inst.Accounts[1].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + return nil +} + +func (inst *CpiGuardExtension) EncodeToTree(parent ag_treeout.Branches) { + name := "Enable" + if inst.SubInstruction == CpiGuard_Disable { + name = "Disable" + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("CpiGuard." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("account", inst.Accounts[0])) + accountsBranch.Child(ag_format.Meta(" owner", inst.Accounts[1])) + }) + }) + }) +} + +func (obj CpiGuardExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return encoder.WriteUint8(obj.SubInstruction) +} + +func (obj *CpiGuardExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + return err +} + +func newCpiGuardInstruction( + subInstruction uint8, + account ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *CpiGuardExtension { + inst := &CpiGuardExtension{ + SubInstruction: subInstruction, + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(account).WRITE() + inst.Accounts[1] = ag_solanago.Meta(owner) + if len(multisigSigners) == 0 { + inst.Accounts[1].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} + +// NewEnableCpiGuardInstruction creates an instruction to enable CPI guard. +func NewEnableCpiGuardInstruction( + account ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *CpiGuardExtension { + return newCpiGuardInstruction(CpiGuard_Enable, account, owner, multisigSigners) +} + +// NewDisableCpiGuardInstruction creates an instruction to disable CPI guard. +func NewDisableCpiGuardInstruction( + account ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *CpiGuardExtension { + return newCpiGuardInstruction(CpiGuard_Disable, account, owner, multisigSigners) +} diff --git a/programs/token-2022/CreateNativeMint.go b/programs/token-2022/CreateNativeMint.go new file mode 100644 index 00000000..f3f27340 --- /dev/null +++ b/programs/token-2022/CreateNativeMint.go @@ -0,0 +1,117 @@ +package token2022 + +import ( + "errors" + + 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" +) + +// CreateNativeMint creates the native mint (SOL) for the Token-2022 program. +type CreateNativeMint struct { + // [0] = [WRITE, SIGNER] payer + // ··········· The payer for the native mint creation. + // + // [1] = [WRITE] nativeMint + // ··········· The native mint address. + // + // [2] = [] systemProgram + // ··········· System program. + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func NewCreateNativeMintInstructionBuilder() *CreateNativeMint { + nd := &CreateNativeMint{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), + } + return nd +} + +func (inst *CreateNativeMint) SetPayerAccount(payer ag_solanago.PublicKey) *CreateNativeMint { + inst.AccountMetaSlice[0] = ag_solanago.Meta(payer).WRITE().SIGNER() + return inst +} + +func (inst *CreateNativeMint) GetPayerAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +func (inst *CreateNativeMint) SetNativeMintAccount(nativeMint ag_solanago.PublicKey) *CreateNativeMint { + inst.AccountMetaSlice[1] = ag_solanago.Meta(nativeMint).WRITE() + return inst +} + +func (inst *CreateNativeMint) GetNativeMintAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +func (inst *CreateNativeMint) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *CreateNativeMint { + inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram) + return inst +} + +func (inst *CreateNativeMint) GetSystemProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + +func (inst CreateNativeMint) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_CreateNativeMint), + }} +} + +func (inst CreateNativeMint) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *CreateNativeMint) Validate() error { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Payer is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.NativeMint is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.SystemProgram is not set") + } + return nil +} + +func (inst *CreateNativeMint) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("CreateNativeMint")). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" payer", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta(" nativeMint", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2])) + }) + }) + }) +} + +func (obj CreateNativeMint) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} + +func (obj *CreateNativeMint) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +func NewCreateNativeMintInstruction( + payer ag_solanago.PublicKey, + nativeMint ag_solanago.PublicKey, +) *CreateNativeMint { + return NewCreateNativeMintInstructionBuilder(). + SetPayerAccount(payer). + SetNativeMintAccount(nativeMint). + SetSystemProgramAccount(ag_solanago.SystemProgramID) +} diff --git a/programs/token-2022/DefaultAccountStateExtension.go b/programs/token-2022/DefaultAccountStateExtension.go new file mode 100644 index 00000000..2a9bbea6 --- /dev/null +++ b/programs/token-2022/DefaultAccountStateExtension.go @@ -0,0 +1,170 @@ +package token2022 + +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" +) + +// DefaultAccountState sub-instruction IDs. +const ( + DefaultAccountState_Initialize uint8 = iota + DefaultAccountState_Update +) + +// DefaultAccountStateExtension is the instruction wrapper for the DefaultAccountState extension (ID 28). +// Sub-instructions: Initialize, Update. +type DefaultAccountStateExtension struct { + SubInstruction uint8 + State *AccountState + + // For Initialize: + // [0] = [WRITE] mint - The mint to initialize. + // + // For Update: + // [0] = [WRITE] mint - The mint. + // [1] = [] freezeAuthority - The mint freeze authority or multisig. + // [2...] = [SIGNER] signers + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *DefaultAccountStateExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + if obj.SubInstruction == DefaultAccountState_Initialize { + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + } else { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + } + return nil +} + +func (slice DefaultAccountStateExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst DefaultAccountStateExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_DefaultAccountStateExtension), + }} +} + +func (inst DefaultAccountStateExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *DefaultAccountStateExtension) Validate() error { + if inst.State == nil { + return errors.New("State parameter is not set") + } + if len(inst.Accounts) == 0 || inst.Accounts[0] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.SubInstruction == DefaultAccountState_Update { + if len(inst.Accounts) < 2 || inst.Accounts[1] == nil { + return errors.New("accounts.FreezeAuthority is not set") + } + if !inst.Accounts[1].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + } + return nil +} + +func (inst *DefaultAccountStateExtension) EncodeToTree(parent ag_treeout.Branches) { + name := "Initialize" + if inst.SubInstruction == DefaultAccountState_Update { + name = "Update" + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("DefaultAccountState." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("State", *inst.State)) + }) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("mint", inst.Accounts[0])) + if inst.SubInstruction == DefaultAccountState_Update && len(inst.Accounts) > 1 { + accountsBranch.Child(ag_format.Meta("freezeAuthority", inst.Accounts[1])) + } + }) + }) + }) +} + +func (obj DefaultAccountStateExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + err = encoder.WriteUint8(obj.SubInstruction) + if err != nil { + return err + } + err = encoder.WriteUint8(uint8(*obj.State)) + if err != nil { + return err + } + return nil +} + +func (obj *DefaultAccountStateExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + if err != nil { + return err + } + v, err := decoder.ReadUint8() + if err != nil { + return err + } + state := AccountState(v) + obj.State = &state + return nil +} + +// NewInitializeDefaultAccountStateInstruction creates an instruction to initialize +// the default account state for a mint. +func NewInitializeDefaultAccountStateInstruction( + state AccountState, + mint ag_solanago.PublicKey, +) *DefaultAccountStateExtension { + inst := &DefaultAccountStateExtension{ + SubInstruction: DefaultAccountState_Initialize, + State: &state, + Accounts: make(ag_solanago.AccountMetaSlice, 1), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// NewUpdateDefaultAccountStateInstruction creates an instruction to update +// the default account state for a mint. +func NewUpdateDefaultAccountStateInstruction( + state AccountState, + mint ag_solanago.PublicKey, + freezeAuthority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *DefaultAccountStateExtension { + inst := &DefaultAccountStateExtension{ + SubInstruction: DefaultAccountState_Update, + State: &state, + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + inst.Accounts[1] = ag_solanago.Meta(freezeAuthority) + if len(multisigSigners) == 0 { + inst.Accounts[1].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} diff --git a/programs/token-2022/GroupMemberPointerExtension.go b/programs/token-2022/GroupMemberPointerExtension.go new file mode 100644 index 00000000..9bf158eb --- /dev/null +++ b/programs/token-2022/GroupMemberPointerExtension.go @@ -0,0 +1,190 @@ +package token2022 + +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" +) + +// GroupMemberPointer sub-instruction IDs. +const ( + GroupMemberPointer_Initialize uint8 = iota + GroupMemberPointer_Update +) + +// GroupMemberPointerExtension is the instruction wrapper for the GroupMemberPointer extension (ID 41). +type GroupMemberPointerExtension struct { + SubInstruction uint8 + + Authority *ag_solanago.PublicKey `bin:"-"` + MemberAddress *ag_solanago.PublicKey `bin:"-"` + + // For Initialize: + // [0] = [WRITE] mint + // + // For Update: + // [0] = [WRITE] mint + // [1] = [] authority + // [2...] = [SIGNER] signers + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *GroupMemberPointerExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + if obj.SubInstruction == GroupMemberPointer_Initialize { + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + } else { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + } + return nil +} + +func (slice GroupMemberPointerExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst GroupMemberPointerExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_GroupMemberPointerExtension), + }} +} + +func (inst GroupMemberPointerExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *GroupMemberPointerExtension) Validate() error { + if len(inst.Accounts) == 0 || inst.Accounts[0] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.SubInstruction == GroupMemberPointer_Update { + if len(inst.Accounts) < 2 || inst.Accounts[1] == nil { + return errors.New("accounts.Authority is not set") + } + if !inst.Accounts[1].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + } + return nil +} + +func (inst *GroupMemberPointerExtension) EncodeToTree(parent ag_treeout.Branches) { + name := "Initialize" + if inst.SubInstruction == GroupMemberPointer_Update { + name = "Update" + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("GroupMemberPointer." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + if inst.SubInstruction == GroupMemberPointer_Initialize { + paramsBranch.Child(ag_format.Param(" Authority (OPT)", inst.Authority)) + } + paramsBranch.Child(ag_format.Param("MemberAddress (OPT)", inst.MemberAddress)) + }) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" mint", inst.Accounts[0])) + if inst.SubInstruction == GroupMemberPointer_Update && len(inst.Accounts) > 1 { + accountsBranch.Child(ag_format.Meta("authority", inst.Accounts[1])) + } + }) + }) + }) +} + +func (obj GroupMemberPointerExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + err = encoder.WriteUint8(obj.SubInstruction) + if err != nil { + return err + } + switch obj.SubInstruction { + case GroupMemberPointer_Initialize: + if err = writeOptionalPubkey(encoder, obj.Authority); err != nil { + return err + } + if err = writeOptionalPubkey(encoder, obj.MemberAddress); err != nil { + return err + } + case GroupMemberPointer_Update: + if err = writeOptionalPubkey(encoder, obj.MemberAddress); err != nil { + return err + } + } + return nil +} + +func (obj *GroupMemberPointerExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + if err != nil { + return err + } + switch obj.SubInstruction { + case GroupMemberPointer_Initialize: + obj.Authority, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + obj.MemberAddress, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + case GroupMemberPointer_Update: + obj.MemberAddress, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + } + return nil +} + +// NewInitializeGroupMemberPointerInstruction creates an instruction to initialize the group member pointer. +func NewInitializeGroupMemberPointerInstruction( + authority *ag_solanago.PublicKey, + memberAddress *ag_solanago.PublicKey, + mint ag_solanago.PublicKey, +) *GroupMemberPointerExtension { + inst := &GroupMemberPointerExtension{ + SubInstruction: GroupMemberPointer_Initialize, + Authority: authority, + MemberAddress: memberAddress, + Accounts: make(ag_solanago.AccountMetaSlice, 1), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// NewUpdateGroupMemberPointerInstruction creates an instruction to update the group member pointer. +func NewUpdateGroupMemberPointerInstruction( + memberAddress *ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *GroupMemberPointerExtension { + inst := &GroupMemberPointerExtension{ + SubInstruction: GroupMemberPointer_Update, + MemberAddress: memberAddress, + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + inst.Accounts[1] = ag_solanago.Meta(authority) + if len(multisigSigners) == 0 { + inst.Accounts[1].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} diff --git a/programs/token-2022/GroupPointerExtension.go b/programs/token-2022/GroupPointerExtension.go new file mode 100644 index 00000000..bab8ce93 --- /dev/null +++ b/programs/token-2022/GroupPointerExtension.go @@ -0,0 +1,190 @@ +package token2022 + +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" +) + +// GroupPointer sub-instruction IDs. +const ( + GroupPointer_Initialize uint8 = iota + GroupPointer_Update +) + +// GroupPointerExtension is the instruction wrapper for the GroupPointer extension (ID 40). +type GroupPointerExtension struct { + SubInstruction uint8 + + Authority *ag_solanago.PublicKey `bin:"-"` + GroupAddress *ag_solanago.PublicKey `bin:"-"` + + // For Initialize: + // [0] = [WRITE] mint + // + // For Update: + // [0] = [WRITE] mint + // [1] = [] authority + // [2...] = [SIGNER] signers + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *GroupPointerExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + if obj.SubInstruction == GroupPointer_Initialize { + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + } else { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + } + return nil +} + +func (slice GroupPointerExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst GroupPointerExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_GroupPointerExtension), + }} +} + +func (inst GroupPointerExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *GroupPointerExtension) Validate() error { + if len(inst.Accounts) == 0 || inst.Accounts[0] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.SubInstruction == GroupPointer_Update { + if len(inst.Accounts) < 2 || inst.Accounts[1] == nil { + return errors.New("accounts.Authority is not set") + } + if !inst.Accounts[1].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + } + return nil +} + +func (inst *GroupPointerExtension) EncodeToTree(parent ag_treeout.Branches) { + name := "Initialize" + if inst.SubInstruction == GroupPointer_Update { + name = "Update" + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("GroupPointer." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + if inst.SubInstruction == GroupPointer_Initialize { + paramsBranch.Child(ag_format.Param(" Authority (OPT)", inst.Authority)) + } + paramsBranch.Child(ag_format.Param("GroupAddress (OPT)", inst.GroupAddress)) + }) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" mint", inst.Accounts[0])) + if inst.SubInstruction == GroupPointer_Update && len(inst.Accounts) > 1 { + accountsBranch.Child(ag_format.Meta("authority", inst.Accounts[1])) + } + }) + }) + }) +} + +func (obj GroupPointerExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + err = encoder.WriteUint8(obj.SubInstruction) + if err != nil { + return err + } + switch obj.SubInstruction { + case GroupPointer_Initialize: + if err = writeOptionalPubkey(encoder, obj.Authority); err != nil { + return err + } + if err = writeOptionalPubkey(encoder, obj.GroupAddress); err != nil { + return err + } + case GroupPointer_Update: + if err = writeOptionalPubkey(encoder, obj.GroupAddress); err != nil { + return err + } + } + return nil +} + +func (obj *GroupPointerExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + if err != nil { + return err + } + switch obj.SubInstruction { + case GroupPointer_Initialize: + obj.Authority, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + obj.GroupAddress, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + case GroupPointer_Update: + obj.GroupAddress, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + } + return nil +} + +// NewInitializeGroupPointerInstruction creates an instruction to initialize the group pointer. +func NewInitializeGroupPointerInstruction( + authority *ag_solanago.PublicKey, + groupAddress *ag_solanago.PublicKey, + mint ag_solanago.PublicKey, +) *GroupPointerExtension { + inst := &GroupPointerExtension{ + SubInstruction: GroupPointer_Initialize, + Authority: authority, + GroupAddress: groupAddress, + Accounts: make(ag_solanago.AccountMetaSlice, 1), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// NewUpdateGroupPointerInstruction creates an instruction to update the group pointer. +func NewUpdateGroupPointerInstruction( + groupAddress *ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *GroupPointerExtension { + inst := &GroupPointerExtension{ + SubInstruction: GroupPointer_Update, + GroupAddress: groupAddress, + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + inst.Accounts[1] = ag_solanago.Meta(authority) + if len(multisigSigners) == 0 { + inst.Accounts[1].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} diff --git a/programs/token-2022/InitializeNonTransferableMint.go b/programs/token-2022/InitializeNonTransferableMint.go new file mode 100644 index 00000000..6247020b --- /dev/null +++ b/programs/token-2022/InitializeNonTransferableMint.go @@ -0,0 +1,83 @@ +package token2022 + +import ( + "errors" + + 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" +) + +// InitializeNonTransferableMint initializes the non-transferable extension for a mint. +// Tokens from this mint cannot be transferred. +type InitializeNonTransferableMint struct { + // [0] = [WRITE] mint + // ··········· The mint to initialize. + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func NewInitializeNonTransferableMintInstructionBuilder() *InitializeNonTransferableMint { + nd := &InitializeNonTransferableMint{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1), + } + return nd +} + +func (inst *InitializeNonTransferableMint) SetMintAccount(mint ag_solanago.PublicKey) *InitializeNonTransferableMint { + inst.AccountMetaSlice[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +func (inst *InitializeNonTransferableMint) GetMintAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +func (inst InitializeNonTransferableMint) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_InitializeNonTransferableMint), + }} +} + +func (inst InitializeNonTransferableMint) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InitializeNonTransferableMint) Validate() error { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Mint is not set") + } + return nil +} + +func (inst *InitializeNonTransferableMint) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InitializeNonTransferableMint")). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("mint", inst.AccountMetaSlice[0])) + }) + }) + }) +} + +func (obj InitializeNonTransferableMint) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} + +func (obj *InitializeNonTransferableMint) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +func NewInitializeNonTransferableMintInstruction( + mint ag_solanago.PublicKey, +) *InitializeNonTransferableMint { + return NewInitializeNonTransferableMintInstructionBuilder(). + SetMintAccount(mint) +} diff --git a/programs/token-2022/InitializePermanentDelegate.go b/programs/token-2022/InitializePermanentDelegate.go new file mode 100644 index 00000000..34eb6bd8 --- /dev/null +++ b/programs/token-2022/InitializePermanentDelegate.go @@ -0,0 +1,106 @@ +package token2022 + +import ( + "errors" + + 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" +) + +// InitializePermanentDelegate initializes the permanent delegate extension for a mint. +// The permanent delegate can transfer or burn any tokens from any account for this mint. +type InitializePermanentDelegate struct { + // The permanent delegate for the mint. + Delegate *ag_solanago.PublicKey + + // [0] = [WRITE] mint + // ··········· The mint to initialize. + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func NewInitializePermanentDelegateInstructionBuilder() *InitializePermanentDelegate { + nd := &InitializePermanentDelegate{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1), + } + return nd +} + +func (inst *InitializePermanentDelegate) SetDelegate(delegate ag_solanago.PublicKey) *InitializePermanentDelegate { + inst.Delegate = &delegate + return inst +} + +func (inst *InitializePermanentDelegate) SetMintAccount(mint ag_solanago.PublicKey) *InitializePermanentDelegate { + inst.AccountMetaSlice[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +func (inst *InitializePermanentDelegate) GetMintAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +func (inst InitializePermanentDelegate) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_InitializePermanentDelegate), + }} +} + +func (inst InitializePermanentDelegate) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InitializePermanentDelegate) Validate() error { + if inst.Delegate == nil { + return errors.New("Delegate parameter is not set") + } + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Mint is not set") + } + return nil +} + +func (inst *InitializePermanentDelegate) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InitializePermanentDelegate")). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Delegate", inst.Delegate)) + }) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("mint", inst.AccountMetaSlice[0])) + }) + }) + }) +} + +func (obj InitializePermanentDelegate) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + err = encoder.Encode(obj.Delegate) + if err != nil { + return err + } + return nil +} + +func (obj *InitializePermanentDelegate) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + err = decoder.Decode(&obj.Delegate) + if err != nil { + return err + } + return nil +} + +func NewInitializePermanentDelegateInstruction( + delegate ag_solanago.PublicKey, + mint ag_solanago.PublicKey, +) *InitializePermanentDelegate { + return NewInitializePermanentDelegateInstructionBuilder(). + SetDelegate(delegate). + SetMintAccount(mint) +} diff --git a/programs/token-2022/InterestBearingMintExtension.go b/programs/token-2022/InterestBearingMintExtension.go new file mode 100644 index 00000000..bb6bf350 --- /dev/null +++ b/programs/token-2022/InterestBearingMintExtension.go @@ -0,0 +1,195 @@ +package token2022 + +import ( + "encoding/binary" + "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" +) + +// InterestBearingMint sub-instruction IDs. +const ( + InterestBearingMint_Initialize uint8 = iota + InterestBearingMint_UpdateRate +) + +// InterestBearingMintExtension is the instruction wrapper for the InterestBearingMint extension (ID 33). +type InterestBearingMintExtension struct { + SubInstruction uint8 + + // For Initialize: the rate authority. + RateAuthority *ag_solanago.PublicKey `bin:"-"` + // Rate in basis points. + Rate int16 `bin:"-"` + + // For Initialize: + // [0] = [WRITE] mint + // + // For UpdateRate: + // [0] = [WRITE] mint + // [1] = [] rateAuthority + // [2...] = [SIGNER] signers + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *InterestBearingMintExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + if obj.SubInstruction == InterestBearingMint_Initialize { + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + } else { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + } + return nil +} + +func (slice InterestBearingMintExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst InterestBearingMintExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_InterestBearingMintExtension), + }} +} + +func (inst InterestBearingMintExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InterestBearingMintExtension) Validate() error { + if len(inst.Accounts) == 0 || inst.Accounts[0] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.SubInstruction == InterestBearingMint_UpdateRate { + if len(inst.Accounts) < 2 || inst.Accounts[1] == nil { + return errors.New("accounts.RateAuthority is not set") + } + if !inst.Accounts[1].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + } + return nil +} + +func (inst *InterestBearingMintExtension) EncodeToTree(parent ag_treeout.Branches) { + name := "Initialize" + if inst.SubInstruction == InterestBearingMint_UpdateRate { + name = "UpdateRate" + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InterestBearingMint." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + if inst.SubInstruction == InterestBearingMint_Initialize { + paramsBranch.Child(ag_format.Param("RateAuthority (OPT)", inst.RateAuthority)) + } + paramsBranch.Child(ag_format.Param("Rate", inst.Rate)) + }) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" mint", inst.Accounts[0])) + if inst.SubInstruction == InterestBearingMint_UpdateRate && len(inst.Accounts) > 1 { + accountsBranch.Child(ag_format.Meta("rateAuthority", inst.Accounts[1])) + } + }) + }) + }) +} + +func (obj InterestBearingMintExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + err = encoder.WriteUint8(obj.SubInstruction) + if err != nil { + return err + } + switch obj.SubInstruction { + case InterestBearingMint_Initialize: + if err = writeOptionalPubkey(encoder, obj.RateAuthority); err != nil { + return err + } + err = encoder.WriteInt16(obj.Rate, binary.LittleEndian) + if err != nil { + return err + } + case InterestBearingMint_UpdateRate: + err = encoder.WriteInt16(obj.Rate, binary.LittleEndian) + if err != nil { + return err + } + } + return nil +} + +func (obj *InterestBearingMintExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + if err != nil { + return err + } + switch obj.SubInstruction { + case InterestBearingMint_Initialize: + obj.RateAuthority, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + obj.Rate, err = decoder.ReadInt16(binary.LittleEndian) + if err != nil { + return err + } + case InterestBearingMint_UpdateRate: + obj.Rate, err = decoder.ReadInt16(binary.LittleEndian) + if err != nil { + return err + } + } + return nil +} + +// NewInitializeInterestBearingMintInstruction creates an instruction to initialize interest-bearing mint. +func NewInitializeInterestBearingMintInstruction( + rateAuthority *ag_solanago.PublicKey, + rate int16, + mint ag_solanago.PublicKey, +) *InterestBearingMintExtension { + inst := &InterestBearingMintExtension{ + SubInstruction: InterestBearingMint_Initialize, + RateAuthority: rateAuthority, + Rate: rate, + Accounts: make(ag_solanago.AccountMetaSlice, 1), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// NewUpdateInterestRateInstruction creates an instruction to update the interest rate. +func NewUpdateInterestRateInstruction( + rate int16, + mint ag_solanago.PublicKey, + rateAuthority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *InterestBearingMintExtension { + inst := &InterestBearingMintExtension{ + SubInstruction: InterestBearingMint_UpdateRate, + Rate: rate, + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + inst.Accounts[1] = ag_solanago.Meta(rateAuthority) + if len(multisigSigners) == 0 { + inst.Accounts[1].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} diff --git a/programs/token-2022/MemoTransferExtension.go b/programs/token-2022/MemoTransferExtension.go new file mode 100644 index 00000000..8518dd3a --- /dev/null +++ b/programs/token-2022/MemoTransferExtension.go @@ -0,0 +1,139 @@ +package token2022 + +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" +) + +// MemoTransfer sub-instruction IDs. +const ( + MemoTransfer_Enable uint8 = iota + MemoTransfer_Disable +) + +// MemoTransferExtension is the instruction wrapper for the MemoTransfer extension (ID 30). +// Sub-instructions: Enable, Disable. +type MemoTransferExtension struct { + SubInstruction uint8 + + // [0] = [WRITE] account + // ··········· The token account to update. + // + // [1] = [] owner + // ··········· The account's owner or multisig. + // + // [2...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *MemoTransferExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + return nil +} + +func (slice MemoTransferExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst MemoTransferExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_MemoTransferExtension), + }} +} + +func (inst MemoTransferExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *MemoTransferExtension) Validate() error { + if len(inst.Accounts) < 2 || inst.Accounts[0] == nil { + return errors.New("accounts.Account is not set") + } + if inst.Accounts[1] == nil { + return errors.New("accounts.Owner is not set") + } + if !inst.Accounts[1].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + return nil +} + +func (inst *MemoTransferExtension) EncodeToTree(parent ag_treeout.Branches) { + name := "Enable" + if inst.SubInstruction == MemoTransfer_Disable { + name = "Disable" + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("MemoTransfer." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("account", inst.Accounts[0])) + accountsBranch.Child(ag_format.Meta(" owner", inst.Accounts[1])) + }) + }) + }) +} + +func (obj MemoTransferExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return encoder.WriteUint8(obj.SubInstruction) +} + +func (obj *MemoTransferExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + return err +} + +func newMemoTransferInstruction( + subInstruction uint8, + account ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *MemoTransferExtension { + inst := &MemoTransferExtension{ + SubInstruction: subInstruction, + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(account).WRITE() + inst.Accounts[1] = ag_solanago.Meta(owner) + if len(multisigSigners) == 0 { + inst.Accounts[1].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} + +// NewEnableMemoTransferInstruction creates an instruction to enable required memo transfers. +func NewEnableMemoTransferInstruction( + account ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *MemoTransferExtension { + return newMemoTransferInstruction(MemoTransfer_Enable, account, owner, multisigSigners) +} + +// NewDisableMemoTransferInstruction creates an instruction to disable required memo transfers. +func NewDisableMemoTransferInstruction( + account ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *MemoTransferExtension { + return newMemoTransferInstruction(MemoTransfer_Disable, account, owner, multisigSigners) +} diff --git a/programs/token-2022/MetadataPointerExtension.go b/programs/token-2022/MetadataPointerExtension.go new file mode 100644 index 00000000..dbcbe4ba --- /dev/null +++ b/programs/token-2022/MetadataPointerExtension.go @@ -0,0 +1,192 @@ +package token2022 + +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" +) + +// MetadataPointer sub-instruction IDs. +const ( + MetadataPointer_Initialize uint8 = iota + MetadataPointer_Update +) + +// MetadataPointerExtension is the instruction wrapper for the MetadataPointer extension (ID 39). +type MetadataPointerExtension struct { + SubInstruction uint8 + + // The authority that can set the metadata address. + Authority *ag_solanago.PublicKey `bin:"-"` + // The account address that holds the metadata. + MetadataAddress *ag_solanago.PublicKey `bin:"-"` + + // For Initialize: + // [0] = [WRITE] mint + // + // For Update: + // [0] = [WRITE] mint + // [1] = [] authority + // [2...] = [SIGNER] signers + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *MetadataPointerExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + if obj.SubInstruction == MetadataPointer_Initialize { + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + } else { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + } + return nil +} + +func (slice MetadataPointerExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst MetadataPointerExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_MetadataPointerExtension), + }} +} + +func (inst MetadataPointerExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *MetadataPointerExtension) Validate() error { + if len(inst.Accounts) == 0 || inst.Accounts[0] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.SubInstruction == MetadataPointer_Update { + if len(inst.Accounts) < 2 || inst.Accounts[1] == nil { + return errors.New("accounts.Authority is not set") + } + if !inst.Accounts[1].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + } + return nil +} + +func (inst *MetadataPointerExtension) EncodeToTree(parent ag_treeout.Branches) { + name := "Initialize" + if inst.SubInstruction == MetadataPointer_Update { + name = "Update" + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("MetadataPointer." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + if inst.SubInstruction == MetadataPointer_Initialize { + paramsBranch.Child(ag_format.Param(" Authority (OPT)", inst.Authority)) + } + paramsBranch.Child(ag_format.Param("MetadataAddress (OPT)", inst.MetadataAddress)) + }) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" mint", inst.Accounts[0])) + if inst.SubInstruction == MetadataPointer_Update && len(inst.Accounts) > 1 { + accountsBranch.Child(ag_format.Meta("authority", inst.Accounts[1])) + } + }) + }) + }) +} + +func (obj MetadataPointerExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + err = encoder.WriteUint8(obj.SubInstruction) + if err != nil { + return err + } + switch obj.SubInstruction { + case MetadataPointer_Initialize: + if err = writeOptionalPubkey(encoder, obj.Authority); err != nil { + return err + } + if err = writeOptionalPubkey(encoder, obj.MetadataAddress); err != nil { + return err + } + case MetadataPointer_Update: + if err = writeOptionalPubkey(encoder, obj.MetadataAddress); err != nil { + return err + } + } + return nil +} + +func (obj *MetadataPointerExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + if err != nil { + return err + } + switch obj.SubInstruction { + case MetadataPointer_Initialize: + obj.Authority, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + obj.MetadataAddress, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + case MetadataPointer_Update: + obj.MetadataAddress, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + } + return nil +} + +// NewInitializeMetadataPointerInstruction creates an instruction to initialize the metadata pointer. +func NewInitializeMetadataPointerInstruction( + authority *ag_solanago.PublicKey, + metadataAddress *ag_solanago.PublicKey, + mint ag_solanago.PublicKey, +) *MetadataPointerExtension { + inst := &MetadataPointerExtension{ + SubInstruction: MetadataPointer_Initialize, + Authority: authority, + MetadataAddress: metadataAddress, + Accounts: make(ag_solanago.AccountMetaSlice, 1), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// NewUpdateMetadataPointerInstruction creates an instruction to update the metadata pointer. +func NewUpdateMetadataPointerInstruction( + metadataAddress *ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *MetadataPointerExtension { + inst := &MetadataPointerExtension{ + SubInstruction: MetadataPointer_Update, + MetadataAddress: metadataAddress, + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + inst.Accounts[1] = ag_solanago.Meta(authority) + if len(multisigSigners) == 0 { + inst.Accounts[1].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} diff --git a/programs/token-2022/PausableExtension.go b/programs/token-2022/PausableExtension.go new file mode 100644 index 00000000..08fb8da1 --- /dev/null +++ b/programs/token-2022/PausableExtension.go @@ -0,0 +1,161 @@ +package token2022 + +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" +) + +// Pausable sub-instruction IDs. +const ( + Pausable_Initialize uint8 = iota + Pausable_Pause + Pausable_Resume +) + +// PausableExtension is the instruction wrapper for the Pausable extension (ID 44). +// Sub-instructions: Initialize, Pause, Resume. +type PausableExtension struct { + SubInstruction uint8 + + // For Initialize: + // [0] = [WRITE] mint - The mint to initialize. + // + // For Pause/Resume: + // [0] = [WRITE] mint - The mint. + // [1] = [] authority - The pause authority or multisig. + // [2...] = [SIGNER] signers + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *PausableExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + if obj.SubInstruction == Pausable_Initialize { + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + } else { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + } + return nil +} + +func (slice PausableExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst PausableExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_PausableExtension), + }} +} + +func (inst PausableExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *PausableExtension) Validate() error { + if len(inst.Accounts) == 0 || inst.Accounts[0] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.SubInstruction == Pausable_Pause || inst.SubInstruction == Pausable_Resume { + if len(inst.Accounts) < 2 || inst.Accounts[1] == nil { + return errors.New("accounts.Authority is not set") + } + if !inst.Accounts[1].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + } + return nil +} + +func (inst *PausableExtension) EncodeToTree(parent ag_treeout.Branches) { + names := []string{"Initialize", "Pause", "Resume"} + name := "Unknown" + if int(inst.SubInstruction) < len(names) { + name = names[inst.SubInstruction] + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("Pausable." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" mint", inst.Accounts[0])) + if len(inst.Accounts) > 1 && inst.Accounts[1] != nil { + accountsBranch.Child(ag_format.Meta("authority", inst.Accounts[1])) + } + }) + }) + }) +} + +func (obj PausableExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return encoder.WriteUint8(obj.SubInstruction) +} + +func (obj *PausableExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + return err +} + +// NewInitializePausableInstruction creates an instruction to initialize the pausable extension. +func NewInitializePausableInstruction( + mint ag_solanago.PublicKey, +) *PausableExtension { + inst := &PausableExtension{ + SubInstruction: Pausable_Initialize, + Accounts: make(ag_solanago.AccountMetaSlice, 1), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +func newPausableToggleInstruction( + subInstruction uint8, + mint ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *PausableExtension { + inst := &PausableExtension{ + SubInstruction: subInstruction, + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + inst.Accounts[1] = ag_solanago.Meta(authority) + if len(multisigSigners) == 0 { + inst.Accounts[1].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} + +// NewPauseInstruction creates an instruction to pause the mint. +func NewPauseInstruction( + mint ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *PausableExtension { + return newPausableToggleInstruction(Pausable_Pause, mint, authority, multisigSigners) +} + +// NewResumeInstruction creates an instruction to resume the mint. +func NewResumeInstruction( + mint ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *PausableExtension { + return newPausableToggleInstruction(Pausable_Resume, mint, authority, multisigSigners) +} diff --git a/programs/token-2022/PermissionedBurnExtension.go b/programs/token-2022/PermissionedBurnExtension.go new file mode 100644 index 00000000..a40768d4 --- /dev/null +++ b/programs/token-2022/PermissionedBurnExtension.go @@ -0,0 +1,94 @@ +package token2022 + +import ( + "errors" + + 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" +) + +// PermissionedBurn sub-instruction IDs. +const ( + PermissionedBurn_Initialize uint8 = iota +) + +// PermissionedBurnExtension is the instruction wrapper for the PermissionedBurn extension (ID 46). +// Sub-instructions: Initialize. +type PermissionedBurnExtension struct { + SubInstruction uint8 + + // [0] = [WRITE] mint + // ··········· The mint to initialize. + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func NewPermissionedBurnExtensionInstructionBuilder() *PermissionedBurnExtension { + nd := &PermissionedBurnExtension{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1), + } + return nd +} + +func (inst *PermissionedBurnExtension) SetMintAccount(mint ag_solanago.PublicKey) *PermissionedBurnExtension { + inst.AccountMetaSlice[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +func (inst *PermissionedBurnExtension) GetMintAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +func (inst PermissionedBurnExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_PermissionedBurnExtension), + }} +} + +func (inst PermissionedBurnExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *PermissionedBurnExtension) Validate() error { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Mint is not set") + } + return nil +} + +func (inst *PermissionedBurnExtension) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("PermissionedBurn.Initialize")). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("mint", inst.AccountMetaSlice[0])) + }) + }) + }) +} + +func (obj PermissionedBurnExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return encoder.WriteUint8(obj.SubInstruction) +} + +func (obj *PermissionedBurnExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + return err +} + +// NewInitializePermissionedBurnInstruction creates an instruction to initialize the permissioned burn extension. +func NewInitializePermissionedBurnInstruction( + mint ag_solanago.PublicKey, +) *PermissionedBurnExtension { + inst := NewPermissionedBurnExtensionInstructionBuilder() + inst.SubInstruction = PermissionedBurn_Initialize + inst.SetMintAccount(mint) + return inst +} diff --git a/programs/token-2022/Reallocate.go b/programs/token-2022/Reallocate.go new file mode 100644 index 00000000..788b9e68 --- /dev/null +++ b/programs/token-2022/Reallocate.go @@ -0,0 +1,191 @@ +package token2022 + +import ( + "encoding/binary" + "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" +) + +// Reallocate an account to hold the given list of extensions. +type Reallocate struct { + // The extension types to reallocate for. + ExtensionTypes []ExtensionType + + // [0] = [WRITE] account + // ··········· The account to reallocate. + // + // [1] = [WRITE, SIGNER] payer + // ··········· The payer for the additional rent. + // + // [2] = [] systemProgram + // ··········· System program for reallocation. + // + // [3] = [] owner + // ··········· The account's owner or its multisignature account. + // + // [4...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *Reallocate) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(4) + return nil +} + +func (slice Reallocate) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func NewReallocateInstructionBuilder() *Reallocate { + nd := &Reallocate{ + Accounts: make(ag_solanago.AccountMetaSlice, 4), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +func (inst *Reallocate) SetExtensionTypes(extensionTypes []ExtensionType) *Reallocate { + inst.ExtensionTypes = extensionTypes + return inst +} + +func (inst *Reallocate) SetAccountAccount(account ag_solanago.PublicKey) *Reallocate { + inst.Accounts[0] = ag_solanago.Meta(account).WRITE() + return inst +} + +func (inst *Reallocate) GetAccountAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +func (inst *Reallocate) SetPayerAccount(payer ag_solanago.PublicKey) *Reallocate { + inst.Accounts[1] = ag_solanago.Meta(payer).WRITE().SIGNER() + return inst +} + +func (inst *Reallocate) GetPayerAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +func (inst *Reallocate) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Reallocate { + inst.Accounts[2] = ag_solanago.Meta(systemProgram) + return inst +} + +func (inst *Reallocate) GetSystemProgramAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst *Reallocate) SetOwnerAccount(owner ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *Reallocate { + inst.Accounts[3] = ag_solanago.Meta(owner) + if len(multisigSigners) == 0 { + inst.Accounts[3].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} + +func (inst *Reallocate) GetOwnerAccount() *ag_solanago.AccountMeta { + return inst.Accounts[3] +} + +func (inst Reallocate) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_Reallocate), + }} +} + +func (inst Reallocate) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *Reallocate) Validate() error { + if inst.Accounts[0] == nil { + return errors.New("accounts.Account is not set") + } + if inst.Accounts[1] == nil { + return errors.New("accounts.Payer is not set") + } + if inst.Accounts[2] == nil { + return errors.New("accounts.SystemProgram is not set") + } + if inst.Accounts[3] == nil { + return errors.New("accounts.Owner is not set") + } + if !inst.Accounts[3].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + if len(inst.Signers) > MAX_SIGNERS { + return fmt.Errorf("too many signers; got %v, but max is 11", len(inst.Signers)) + } + return nil +} + +func (inst *Reallocate) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("Reallocate")). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("ExtensionTypes", inst.ExtensionTypes)) + }) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" account", inst.Accounts[0])) + accountsBranch.Child(ag_format.Meta(" payer", inst.Accounts[1])) + accountsBranch.Child(ag_format.Meta("systemProgram", inst.Accounts[2])) + accountsBranch.Child(ag_format.Meta(" owner", inst.Accounts[3])) + }) + }) + }) +} + +func (obj Reallocate) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + for _, et := range obj.ExtensionTypes { + err = encoder.WriteUint16(uint16(et), binary.LittleEndian) + if err != nil { + return err + } + } + return nil +} + +func (obj *Reallocate) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + for { + val, err := decoder.ReadUint16(binary.LittleEndian) + if err != nil { + break + } + obj.ExtensionTypes = append(obj.ExtensionTypes, ExtensionType(val)) + } + return nil +} + +func NewReallocateInstruction( + extensionTypes []ExtensionType, + account ag_solanago.PublicKey, + payer ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *Reallocate { + return NewReallocateInstructionBuilder(). + SetExtensionTypes(extensionTypes). + SetAccountAccount(account). + SetPayerAccount(payer). + SetSystemProgramAccount(ag_solanago.SystemProgramID). + SetOwnerAccount(owner, multisigSigners...) +} diff --git a/programs/token-2022/ScaledUiAmountExtension.go b/programs/token-2022/ScaledUiAmountExtension.go new file mode 100644 index 00000000..85d9cbb8 --- /dev/null +++ b/programs/token-2022/ScaledUiAmountExtension.go @@ -0,0 +1,210 @@ +package token2022 + +import ( + "encoding/binary" + "errors" + "fmt" + "math" + + 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" +) + +// ScaledUiAmount sub-instruction IDs. +const ( + ScaledUiAmount_Initialize uint8 = iota + ScaledUiAmount_UpdateMultiplier +) + +// ScaledUiAmountExtension is the instruction wrapper for the ScaledUiAmount extension (ID 43). +type ScaledUiAmountExtension struct { + SubInstruction uint8 + + // The authority that can update the multiplier. + Authority *ag_solanago.PublicKey `bin:"-"` + // The multiplier. + Multiplier float64 `bin:"-"` + // For UpdateMultiplier: effective timestamp for the new multiplier. + EffectiveTimestamp int64 `bin:"-"` + + // For Initialize: + // [0] = [WRITE] mint + // + // For UpdateMultiplier: + // [0] = [WRITE] mint + // [1] = [] authority + // [2...] = [SIGNER] signers + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *ScaledUiAmountExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + if obj.SubInstruction == ScaledUiAmount_Initialize { + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + } else { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + } + return nil +} + +func (slice ScaledUiAmountExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst ScaledUiAmountExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_ScaledUiAmountExtension), + }} +} + +func (inst ScaledUiAmountExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *ScaledUiAmountExtension) Validate() error { + if len(inst.Accounts) == 0 || inst.Accounts[0] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.SubInstruction == ScaledUiAmount_UpdateMultiplier { + if len(inst.Accounts) < 2 || inst.Accounts[1] == nil { + return errors.New("accounts.Authority is not set") + } + if !inst.Accounts[1].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + } + return nil +} + +func (inst *ScaledUiAmountExtension) EncodeToTree(parent ag_treeout.Branches) { + name := "Initialize" + if inst.SubInstruction == ScaledUiAmount_UpdateMultiplier { + name = "UpdateMultiplier" + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("ScaledUiAmount." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + if inst.SubInstruction == ScaledUiAmount_Initialize { + paramsBranch.Child(ag_format.Param("Authority (OPT)", inst.Authority)) + } + paramsBranch.Child(ag_format.Param("Multiplier", inst.Multiplier)) + }) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" mint", inst.Accounts[0])) + if inst.SubInstruction == ScaledUiAmount_UpdateMultiplier && len(inst.Accounts) > 1 { + accountsBranch.Child(ag_format.Meta("authority", inst.Accounts[1])) + } + }) + }) + }) +} + +func (obj ScaledUiAmountExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + err = encoder.WriteUint8(obj.SubInstruction) + if err != nil { + return err + } + switch obj.SubInstruction { + case ScaledUiAmount_Initialize: + if err = writeOptionalPubkey(encoder, obj.Authority); err != nil { + return err + } + err = encoder.WriteUint64(math.Float64bits(obj.Multiplier), binary.LittleEndian) + if err != nil { + return err + } + case ScaledUiAmount_UpdateMultiplier: + err = encoder.WriteUint64(math.Float64bits(obj.Multiplier), binary.LittleEndian) + if err != nil { + return err + } + err = encoder.WriteInt64(obj.EffectiveTimestamp, binary.LittleEndian) + if err != nil { + return err + } + } + return nil +} + +func (obj *ScaledUiAmountExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + if err != nil { + return err + } + switch obj.SubInstruction { + case ScaledUiAmount_Initialize: + obj.Authority, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + bits, err := decoder.ReadUint64(binary.LittleEndian) + if err != nil { + return err + } + obj.Multiplier = math.Float64frombits(bits) + case ScaledUiAmount_UpdateMultiplier: + bits, err := decoder.ReadUint64(binary.LittleEndian) + if err != nil { + return err + } + obj.Multiplier = math.Float64frombits(bits) + obj.EffectiveTimestamp, err = decoder.ReadInt64(binary.LittleEndian) + if err != nil { + return err + } + } + return nil +} + +// NewInitializeScaledUiAmountInstruction creates an instruction to initialize the scaled UI amount extension. +func NewInitializeScaledUiAmountInstruction( + authority *ag_solanago.PublicKey, + multiplier float64, + mint ag_solanago.PublicKey, +) *ScaledUiAmountExtension { + inst := &ScaledUiAmountExtension{ + SubInstruction: ScaledUiAmount_Initialize, + Authority: authority, + Multiplier: multiplier, + Accounts: make(ag_solanago.AccountMetaSlice, 1), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// NewUpdateScaledUiAmountMultiplierInstruction creates an instruction to update the multiplier. +func NewUpdateScaledUiAmountMultiplierInstruction( + multiplier float64, + effectiveTimestamp int64, + mint ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *ScaledUiAmountExtension { + inst := &ScaledUiAmountExtension{ + SubInstruction: ScaledUiAmount_UpdateMultiplier, + Multiplier: multiplier, + EffectiveTimestamp: effectiveTimestamp, + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + inst.Accounts[1] = ag_solanago.Meta(authority) + if len(multisigSigners) == 0 { + inst.Accounts[1].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} diff --git a/programs/token-2022/TransferFeeExtension.go b/programs/token-2022/TransferFeeExtension.go new file mode 100644 index 00000000..af8776df --- /dev/null +++ b/programs/token-2022/TransferFeeExtension.go @@ -0,0 +1,430 @@ +package token2022 + +import ( + "encoding/binary" + "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" +) + +// TransferFee sub-instruction IDs. +const ( + TransferFee_InitializeTransferFeeConfig uint8 = iota + TransferFee_TransferCheckedWithFee + TransferFee_WithdrawWithheldTokensFromMint + TransferFee_WithdrawWithheldTokensFromAccounts + TransferFee_HarvestWithheldTokensToMint + TransferFee_SetTransferFee +) + +// TransferFeeExtension is the instruction wrapper for the TransferFee extension (ID 26). +type TransferFeeExtension struct { + SubInstruction uint8 + + // Sub-instruction data (only the relevant fields are set based on SubInstruction). + TransferFeeConfigAuthority *ag_solanago.PublicKey `bin:"-"` + WithdrawWithheldAuthority *ag_solanago.PublicKey `bin:"-"` + TransferFeeBasisPoints uint16 `bin:"-"` + MaximumFee uint64 `bin:"-"` + + // For TransferCheckedWithFee: + Amount *uint64 `bin:"-"` + Decimals *uint8 `bin:"-"` + Fee *uint64 `bin:"-"` + + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *TransferFeeExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + switch obj.SubInstruction { + case TransferFee_InitializeTransferFeeConfig: + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + case TransferFee_TransferCheckedWithFee: + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(4) + case TransferFee_WithdrawWithheldTokensFromMint: + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + case TransferFee_WithdrawWithheldTokensFromAccounts: + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + case TransferFee_HarvestWithheldTokensToMint: + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + case TransferFee_SetTransferFee: + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + default: + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + } + return nil +} + +func (slice TransferFeeExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst TransferFeeExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_TransferFeeExtension), + }} +} + +func (inst TransferFeeExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *TransferFeeExtension) Validate() error { + if len(inst.Accounts) == 0 || inst.Accounts[0] == nil { + return errors.New("accounts[0] is not set") + } + switch inst.SubInstruction { + case TransferFee_TransferCheckedWithFee: + if inst.Amount == nil { + return errors.New("Amount parameter is not set") + } + if inst.Decimals == nil { + return errors.New("Decimals parameter is not set") + } + if inst.Fee == nil { + return errors.New("Fee parameter is not set") + } + for i := 0; i < 4; i++ { + if len(inst.Accounts) <= i || inst.Accounts[i] == nil { + return fmt.Errorf("accounts[%d] is not set", i) + } + } + if !inst.Accounts[3].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + case TransferFee_WithdrawWithheldTokensFromMint: + for i := 0; i < 3; i++ { + if len(inst.Accounts) <= i || inst.Accounts[i] == nil { + return fmt.Errorf("accounts[%d] is not set", i) + } + } + if !inst.Accounts[2].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + case TransferFee_WithdrawWithheldTokensFromAccounts: + for i := 0; i < 3; i++ { + if len(inst.Accounts) <= i || inst.Accounts[i] == nil { + return fmt.Errorf("accounts[%d] is not set", i) + } + } + if !inst.Accounts[2].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + case TransferFee_SetTransferFee: + for i := 0; i < 2; i++ { + if len(inst.Accounts) <= i || inst.Accounts[i] == nil { + return fmt.Errorf("accounts[%d] is not set", i) + } + } + if !inst.Accounts[1].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + } + if len(inst.Signers) > MAX_SIGNERS { + return fmt.Errorf("too many signers; got %v, but max is 11", len(inst.Signers)) + } + return nil +} + +func (inst *TransferFeeExtension) EncodeToTree(parent ag_treeout.Branches) { + names := []string{ + "InitializeTransferFeeConfig", + "TransferCheckedWithFee", + "WithdrawWithheldTokensFromMint", + "WithdrawWithheldTokensFromAccounts", + "HarvestWithheldTokensToMint", + "SetTransferFee", + } + name := "Unknown" + if int(inst.SubInstruction) < len(names) { + name = names[inst.SubInstruction] + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("TransferFee." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + switch inst.SubInstruction { + case TransferFee_InitializeTransferFeeConfig: + paramsBranch.Child(ag_format.Param("TransferFeeBasisPoints", inst.TransferFeeBasisPoints)) + paramsBranch.Child(ag_format.Param(" MaximumFee", inst.MaximumFee)) + case TransferFee_TransferCheckedWithFee: + paramsBranch.Child(ag_format.Param(" Amount", *inst.Amount)) + paramsBranch.Child(ag_format.Param("Decimals", *inst.Decimals)) + paramsBranch.Child(ag_format.Param(" Fee", *inst.Fee)) + case TransferFee_SetTransferFee: + paramsBranch.Child(ag_format.Param("TransferFeeBasisPoints", inst.TransferFeeBasisPoints)) + paramsBranch.Child(ag_format.Param(" MaximumFee", inst.MaximumFee)) + } + }) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + for i, acct := range inst.Accounts { + if acct != nil { + accountsBranch.Child(ag_format.Meta(fmt.Sprintf("[%d]", i), acct)) + } + } + }) + }) + }) +} + +func (obj TransferFeeExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + err = encoder.WriteUint8(obj.SubInstruction) + if err != nil { + return err + } + switch obj.SubInstruction { + case TransferFee_InitializeTransferFeeConfig: + if err = writeOptionalPubkey(encoder, obj.TransferFeeConfigAuthority); err != nil { + return err + } + if err = writeOptionalPubkey(encoder, obj.WithdrawWithheldAuthority); err != nil { + return err + } + err = encoder.WriteUint16(obj.TransferFeeBasisPoints, binary.LittleEndian) + if err != nil { + return err + } + err = encoder.WriteUint64(obj.MaximumFee, binary.LittleEndian) + if err != nil { + return err + } + case TransferFee_TransferCheckedWithFee: + err = encoder.WriteUint64(*obj.Amount, binary.LittleEndian) + if err != nil { + return err + } + err = encoder.WriteUint8(*obj.Decimals) + if err != nil { + return err + } + err = encoder.WriteUint64(*obj.Fee, binary.LittleEndian) + if err != nil { + return err + } + case TransferFee_SetTransferFee: + err = encoder.WriteUint16(obj.TransferFeeBasisPoints, binary.LittleEndian) + if err != nil { + return err + } + err = encoder.WriteUint64(obj.MaximumFee, binary.LittleEndian) + if err != nil { + return err + } + // WithdrawWithheldTokensFromMint, WithdrawWithheldTokensFromAccounts, + // HarvestWithheldTokensToMint have no additional data + } + return nil +} + +func (obj *TransferFeeExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + if err != nil { + return err + } + switch obj.SubInstruction { + case TransferFee_InitializeTransferFeeConfig: + obj.TransferFeeConfigAuthority, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + obj.WithdrawWithheldAuthority, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + obj.TransferFeeBasisPoints, err = decoder.ReadUint16(binary.LittleEndian) + if err != nil { + return err + } + obj.MaximumFee, err = decoder.ReadUint64(binary.LittleEndian) + if err != nil { + return err + } + case TransferFee_TransferCheckedWithFee: + amount, err := decoder.ReadUint64(binary.LittleEndian) + if err != nil { + return err + } + obj.Amount = &amount + decimals, err := decoder.ReadUint8() + if err != nil { + return err + } + obj.Decimals = &decimals + fee, err := decoder.ReadUint64(binary.LittleEndian) + if err != nil { + return err + } + obj.Fee = &fee + case TransferFee_SetTransferFee: + obj.TransferFeeBasisPoints, err = decoder.ReadUint16(binary.LittleEndian) + if err != nil { + return err + } + obj.MaximumFee, err = decoder.ReadUint64(binary.LittleEndian) + if err != nil { + return err + } + } + return nil +} + +// NewInitializeTransferFeeConfigInstruction creates an instruction to initialize the transfer fee configuration. +func NewInitializeTransferFeeConfigInstruction( + transferFeeConfigAuthority *ag_solanago.PublicKey, + withdrawWithheldAuthority *ag_solanago.PublicKey, + transferFeeBasisPoints uint16, + maximumFee uint64, + mint ag_solanago.PublicKey, +) *TransferFeeExtension { + inst := &TransferFeeExtension{ + SubInstruction: TransferFee_InitializeTransferFeeConfig, + TransferFeeConfigAuthority: transferFeeConfigAuthority, + WithdrawWithheldAuthority: withdrawWithheldAuthority, + TransferFeeBasisPoints: transferFeeBasisPoints, + MaximumFee: maximumFee, + Accounts: make(ag_solanago.AccountMetaSlice, 1), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// NewTransferCheckedWithFeeInstruction creates an instruction to transfer tokens with fee. +func NewTransferCheckedWithFeeInstruction( + amount uint64, + decimals uint8, + fee uint64, + source ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + destination ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *TransferFeeExtension { + inst := &TransferFeeExtension{ + SubInstruction: TransferFee_TransferCheckedWithFee, + Amount: &amount, + Decimals: &decimals, + Fee: &fee, + Accounts: make(ag_solanago.AccountMetaSlice, 4), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(source).WRITE() + inst.Accounts[1] = ag_solanago.Meta(mint) + inst.Accounts[2] = ag_solanago.Meta(destination).WRITE() + inst.Accounts[3] = ag_solanago.Meta(authority) + if len(multisigSigners) == 0 { + inst.Accounts[3].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} + +// NewWithdrawWithheldTokensFromMintInstruction creates an instruction to withdraw withheld tokens from the mint. +func NewWithdrawWithheldTokensFromMintInstruction( + mint ag_solanago.PublicKey, + destination ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *TransferFeeExtension { + inst := &TransferFeeExtension{ + SubInstruction: TransferFee_WithdrawWithheldTokensFromMint, + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + inst.Accounts[1] = ag_solanago.Meta(destination).WRITE() + inst.Accounts[2] = ag_solanago.Meta(authority) + if len(multisigSigners) == 0 { + inst.Accounts[2].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} + +// NewWithdrawWithheldTokensFromAccountsInstruction creates an instruction to withdraw withheld tokens +// from specific token accounts. +func NewWithdrawWithheldTokensFromAccountsInstruction( + mint ag_solanago.PublicKey, + destination ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, + sourceAccounts ...ag_solanago.PublicKey, +) *TransferFeeExtension { + inst := &TransferFeeExtension{ + SubInstruction: TransferFee_WithdrawWithheldTokensFromAccounts, + Accounts: make(ag_solanago.AccountMetaSlice, 3+len(sourceAccounts)), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint) + inst.Accounts[1] = ag_solanago.Meta(destination).WRITE() + inst.Accounts[2] = ag_solanago.Meta(authority) + if len(multisigSigners) == 0 { + inst.Accounts[2].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + for i, src := range sourceAccounts { + inst.Accounts[3+i] = ag_solanago.Meta(src).WRITE() + } + return inst +} + +// NewHarvestWithheldTokensToMintInstruction creates an instruction to harvest withheld tokens to the mint. +func NewHarvestWithheldTokensToMintInstruction( + mint ag_solanago.PublicKey, + sourceAccounts ...ag_solanago.PublicKey, +) *TransferFeeExtension { + inst := &TransferFeeExtension{ + SubInstruction: TransferFee_HarvestWithheldTokensToMint, + Accounts: make(ag_solanago.AccountMetaSlice, 1+len(sourceAccounts)), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + for i, src := range sourceAccounts { + inst.Accounts[1+i] = ag_solanago.Meta(src).WRITE() + } + return inst +} + +// NewSetTransferFeeInstruction creates an instruction to set the transfer fee. +func NewSetTransferFeeInstruction( + transferFeeBasisPoints uint16, + maximumFee uint64, + mint ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *TransferFeeExtension { + inst := &TransferFeeExtension{ + SubInstruction: TransferFee_SetTransferFee, + TransferFeeBasisPoints: transferFeeBasisPoints, + MaximumFee: maximumFee, + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + inst.Accounts[1] = ag_solanago.Meta(authority) + if len(multisigSigners) == 0 { + inst.Accounts[1].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} diff --git a/programs/token-2022/TransferHookExtension.go b/programs/token-2022/TransferHookExtension.go new file mode 100644 index 00000000..d50e4358 --- /dev/null +++ b/programs/token-2022/TransferHookExtension.go @@ -0,0 +1,223 @@ +package token2022 + +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" +) + +// TransferHook sub-instruction IDs. +const ( + TransferHook_Initialize uint8 = iota + TransferHook_Update +) + +// TransferHookExtension is the instruction wrapper for the TransferHook extension (ID 36). +type TransferHookExtension struct { + SubInstruction uint8 + + // The authority that can update the transfer hook. + Authority *ag_solanago.PublicKey `bin:"-"` + // The transfer hook program ID. + HookProgramID *ag_solanago.PublicKey `bin:"-"` + + // For Initialize: + // [0] = [WRITE] mint + // + // For Update: + // [0] = [WRITE] mint + // [1] = [] authority + // [2...] = [SIGNER] signers + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *TransferHookExtension) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + if obj.SubInstruction == TransferHook_Initialize { + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + } else { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + } + return nil +} + +func (slice TransferHookExtension) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func (inst TransferHookExtension) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_TransferHookExtension), + }} +} + +func (inst TransferHookExtension) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *TransferHookExtension) Validate() error { + if len(inst.Accounts) == 0 || inst.Accounts[0] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.SubInstruction == TransferHook_Update { + if len(inst.Accounts) < 2 || inst.Accounts[1] == nil { + return errors.New("accounts.Authority is not set") + } + if !inst.Accounts[1].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + } + return nil +} + +func (inst *TransferHookExtension) EncodeToTree(parent ag_treeout.Branches) { + name := "Initialize" + if inst.SubInstruction == TransferHook_Update { + name = "Update" + } + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("TransferHook." + name)). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + if inst.SubInstruction == TransferHook_Initialize { + paramsBranch.Child(ag_format.Param(" Authority (OPT)", inst.Authority)) + } + paramsBranch.Child(ag_format.Param("HookProgramID (OPT)", inst.HookProgramID)) + }) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" mint", inst.Accounts[0])) + if inst.SubInstruction == TransferHook_Update && len(inst.Accounts) > 1 { + accountsBranch.Child(ag_format.Meta("authority", inst.Accounts[1])) + } + }) + }) + }) +} + +func writeOptionalPubkey(encoder *ag_binary.Encoder, pk *ag_solanago.PublicKey) error { + if pk == nil { + if err := encoder.WriteBool(false); err != nil { + return err + } + empty := ag_solanago.PublicKey{} + return encoder.WriteBytes(empty[:], false) + } + if err := encoder.WriteBool(true); err != nil { + return err + } + return encoder.WriteBytes(pk[:], false) +} + +func readOptionalPubkey(decoder *ag_binary.Decoder) (*ag_solanago.PublicKey, error) { + ok, err := decoder.ReadBool() + if err != nil { + return nil, err + } + if ok { + v, err := decoder.ReadNBytes(32) + if err != nil { + return nil, err + } + pk := ag_solanago.PublicKeyFromBytes(v) + return &pk, nil + } + _, _ = decoder.ReadNBytes(32) + return nil, nil +} + +func (obj TransferHookExtension) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + err = encoder.WriteUint8(obj.SubInstruction) + if err != nil { + return err + } + switch obj.SubInstruction { + case TransferHook_Initialize: + if err = writeOptionalPubkey(encoder, obj.Authority); err != nil { + return err + } + if err = writeOptionalPubkey(encoder, obj.HookProgramID); err != nil { + return err + } + case TransferHook_Update: + if err = writeOptionalPubkey(encoder, obj.HookProgramID); err != nil { + return err + } + } + return nil +} + +func (obj *TransferHookExtension) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + obj.SubInstruction, err = decoder.ReadUint8() + if err != nil { + return err + } + switch obj.SubInstruction { + case TransferHook_Initialize: + obj.Authority, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + obj.HookProgramID, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + case TransferHook_Update: + obj.HookProgramID, err = readOptionalPubkey(decoder) + if err != nil { + return err + } + } + return nil +} + +// NewInitializeTransferHookInstruction creates an instruction to initialize the transfer hook extension. +func NewInitializeTransferHookInstruction( + authority *ag_solanago.PublicKey, + hookProgramId *ag_solanago.PublicKey, + mint ag_solanago.PublicKey, +) *TransferHookExtension { + inst := &TransferHookExtension{ + SubInstruction: TransferHook_Initialize, + Authority: authority, + HookProgramID: hookProgramId, + Accounts: make(ag_solanago.AccountMetaSlice, 1), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// NewUpdateTransferHookInstruction creates an instruction to update the transfer hook program ID. +func NewUpdateTransferHookInstruction( + hookProgramId *ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *TransferHookExtension { + inst := &TransferHookExtension{ + SubInstruction: TransferHook_Update, + HookProgramID: hookProgramId, + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + inst.Accounts[1] = ag_solanago.Meta(authority) + if len(multisigSigners) == 0 { + inst.Accounts[1].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} diff --git a/programs/token-2022/UnwrapLamports.go b/programs/token-2022/UnwrapLamports.go new file mode 100644 index 00000000..d008a0bc --- /dev/null +++ b/programs/token-2022/UnwrapLamports.go @@ -0,0 +1,191 @@ +package token2022 + +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" +) + +// UnwrapLamports unwraps native SOL tokens by burning the wrapped tokens and +// transferring the underlying lamports to a destination account. +// If amount is nil, all tokens are unwrapped. +type UnwrapLamports struct { + // The amount of tokens to unwrap, or nil to unwrap all. + Amount *uint64 `bin:"optional"` + + // [0] = [WRITE] account + // ··········· The token account to unwrap from. + // + // [1] = [WRITE] destination + // ··········· The destination account for the lamports. + // + // [2] = [] owner + // ··········· The account's owner or its multisignature account. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *UnwrapLamports) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice UnwrapLamports) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func NewUnwrapLamportsInstructionBuilder() *UnwrapLamports { + nd := &UnwrapLamports{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +func (inst *UnwrapLamports) SetAmount(amount uint64) *UnwrapLamports { + inst.Amount = &amount + return inst +} + +func (inst *UnwrapLamports) SetAccountAccount(account ag_solanago.PublicKey) *UnwrapLamports { + inst.Accounts[0] = ag_solanago.Meta(account).WRITE() + return inst +} + +func (inst *UnwrapLamports) GetAccountAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +func (inst *UnwrapLamports) SetDestinationAccount(destination ag_solanago.PublicKey) *UnwrapLamports { + inst.Accounts[1] = ag_solanago.Meta(destination).WRITE() + return inst +} + +func (inst *UnwrapLamports) GetDestinationAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +func (inst *UnwrapLamports) SetOwnerAccount(owner ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *UnwrapLamports { + inst.Accounts[2] = ag_solanago.Meta(owner) + if len(multisigSigners) == 0 { + inst.Accounts[2].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} + +func (inst *UnwrapLamports) GetOwnerAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst UnwrapLamports) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_UnwrapLamports), + }} +} + +func (inst UnwrapLamports) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *UnwrapLamports) Validate() error { + if inst.Accounts[0] == nil { + return errors.New("accounts.Account is not set") + } + if inst.Accounts[1] == nil { + return errors.New("accounts.Destination is not set") + } + if inst.Accounts[2] == nil { + return errors.New("accounts.Owner is not set") + } + if !inst.Accounts[2].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + if len(inst.Signers) > MAX_SIGNERS { + return fmt.Errorf("too many signers; got %v, but max is 11", len(inst.Signers)) + } + return nil +} + +func (inst *UnwrapLamports) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("UnwrapLamports")). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Amount (OPT)", inst.Amount)) + }) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" account", inst.Accounts[0])) + accountsBranch.Child(ag_format.Meta("destination", inst.Accounts[1])) + accountsBranch.Child(ag_format.Meta(" owner", inst.Accounts[2])) + }) + }) + }) +} + +func (obj UnwrapLamports) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // COption: 1 byte discriminator + optional 8 bytes + if obj.Amount == nil { + err = encoder.WriteBool(false) + if err != nil { + return err + } + } else { + err = encoder.WriteBool(true) + if err != nil { + return err + } + err = encoder.Encode(obj.Amount) + if err != nil { + return err + } + } + return nil +} + +func (obj *UnwrapLamports) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + ok, err := decoder.ReadBool() + if err != nil { + return err + } + if ok { + err = decoder.Decode(&obj.Amount) + if err != nil { + return err + } + } + return nil +} + +func NewUnwrapLamportsInstruction( + amount *uint64, + account ag_solanago.PublicKey, + destination ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *UnwrapLamports { + inst := NewUnwrapLamportsInstructionBuilder(). + SetAccountAccount(account). + SetDestinationAccount(destination). + SetOwnerAccount(owner, multisigSigners...) + if amount != nil { + inst.SetAmount(*amount) + } + return inst +} diff --git a/programs/token-2022/WithdrawExcessLamports.go b/programs/token-2022/WithdrawExcessLamports.go new file mode 100644 index 00000000..a763b1d9 --- /dev/null +++ b/programs/token-2022/WithdrawExcessLamports.go @@ -0,0 +1,148 @@ +package token2022 + +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" +) + +// WithdrawExcessLamports withdraws excess lamports from a Token-2022 account. +type WithdrawExcessLamports struct { + // [0] = [WRITE] source + // ··········· The source account to withdraw from. + // + // [1] = [WRITE] destination + // ··········· The destination account for the excess lamports. + // + // [2] = [] authority + // ··········· The source account's owner or multisignature account. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *WithdrawExcessLamports) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice WithdrawExcessLamports) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func NewWithdrawExcessLamportsInstructionBuilder() *WithdrawExcessLamports { + nd := &WithdrawExcessLamports{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +func (inst *WithdrawExcessLamports) SetSourceAccount(source ag_solanago.PublicKey) *WithdrawExcessLamports { + inst.Accounts[0] = ag_solanago.Meta(source).WRITE() + return inst +} + +func (inst *WithdrawExcessLamports) GetSourceAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +func (inst *WithdrawExcessLamports) SetDestinationAccount(destination ag_solanago.PublicKey) *WithdrawExcessLamports { + inst.Accounts[1] = ag_solanago.Meta(destination).WRITE() + return inst +} + +func (inst *WithdrawExcessLamports) GetDestinationAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +func (inst *WithdrawExcessLamports) SetAuthorityAccount(authority ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *WithdrawExcessLamports { + inst.Accounts[2] = ag_solanago.Meta(authority) + if len(multisigSigners) == 0 { + inst.Accounts[2].SIGNER() + } + for _, signer := range multisigSigners { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} + +func (inst *WithdrawExcessLamports) GetAuthorityAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst WithdrawExcessLamports) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_WithdrawExcessLamports), + }} +} + +func (inst WithdrawExcessLamports) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *WithdrawExcessLamports) Validate() error { + if inst.Accounts[0] == nil { + return errors.New("accounts.Source is not set") + } + if inst.Accounts[1] == nil { + return errors.New("accounts.Destination is not set") + } + if inst.Accounts[2] == nil { + return errors.New("accounts.Authority is not set") + } + if !inst.Accounts[2].IsSigner && len(inst.Signers) == 0 { + return fmt.Errorf("accounts.Signers is not set") + } + if len(inst.Signers) > MAX_SIGNERS { + return fmt.Errorf("too many signers; got %v, but max is 11", len(inst.Signers)) + } + return nil +} + +func (inst *WithdrawExcessLamports) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("WithdrawExcessLamports")). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" source", inst.Accounts[0])) + accountsBranch.Child(ag_format.Meta("destination", inst.Accounts[1])) + accountsBranch.Child(ag_format.Meta(" authority", inst.Accounts[2])) + }) + }) + }) +} + +func (obj WithdrawExcessLamports) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} + +func (obj *WithdrawExcessLamports) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +func NewWithdrawExcessLamportsInstruction( + source ag_solanago.PublicKey, + destination ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *WithdrawExcessLamports { + return NewWithdrawExcessLamportsInstructionBuilder(). + SetSourceAccount(source). + SetDestinationAccount(destination). + SetAuthorityAccount(authority, multisigSigners...) +} diff --git a/programs/token-2022/extension_states.go b/programs/token-2022/extension_states.go new file mode 100644 index 00000000..fe1f7d97 --- /dev/null +++ b/programs/token-2022/extension_states.go @@ -0,0 +1,586 @@ +package token2022 + +import ( + "encoding/binary" + "fmt" + + bin "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" +) + +// TLV entry header for extensions in Token-2022 account data. +// Each extension is preceded by: [u16 extension_type] [u16 length] [data...] +const ( + // AccountTypeUninitialized is used before the account type is determined. + AccountTypeUninitialized uint8 = 0 + // AccountTypeMint marks the account as a Mint. + AccountTypeMint uint8 = 1 + // AccountTypeAccount marks the account as a Token Account. + AccountTypeAccount uint8 = 2 +) + +// Base sizes of Token-2022 account types (without extensions). +// MINT_SIZE is also defined in rpc.go as 82. +const ACCOUNT_SIZE = 165 + +// Limits for decoding untrusted metadata to prevent OOM from malformed data. +const ( + maxMetadataStringLen = 10 * 1024 * 1024 // 10 MB per string field + maxMetadataFields = 1024 // max additional metadata key-value pairs +) + +// OptionalPubkey represents an optional public key stored as 32 bytes where +// all zeros means None (used in extension state Pod types). +type OptionalPubkey struct { + Key solana.PublicKey +} + +func (o OptionalPubkey) IsNone() bool { + return o.Key == solana.PublicKey{} +} + +func (o OptionalPubkey) Get() *solana.PublicKey { + if o.IsNone() { + return nil + } + return &o.Key +} + +func NewOptionalPubkey(key *solana.PublicKey) OptionalPubkey { + if key == nil { + return OptionalPubkey{} + } + return OptionalPubkey{Key: *key} +} + +// --- TransferFee types --- + +// TransferFee represents a transfer fee configuration epoch entry. +type TransferFee struct { + // First epoch where the fee takes effect. + Epoch uint64 + // Maximum fee assessed on transfers, in token amount. + MaximumFee uint64 + // Amount of transfer collected as fees, expressed as basis points (1/100 of a percent). + TransferFeeBasisPoints uint16 +} + +// TransferFeeConfigState is the extension state for ExtensionTransferFeeConfig (mint extension). +type TransferFeeConfigState struct { + // Authority that can set the fee. + TransferFeeConfigAuthority OptionalPubkey + // Authority that can withdraw withheld fees. + WithdrawWithheldAuthority OptionalPubkey + // Withheld amount of fees in the mint. + WithheldAmount uint64 + // Older transfer fee, used before newer_transfer_fee epoch. + OlderTransferFee TransferFee + // Newer transfer fee, used after its epoch. + NewerTransferFee TransferFee +} + +// TransferFeeAmountState is the extension state for ExtensionTransferFeeAmount (account extension). +type TransferFeeAmountState struct { + // Amount of fees withheld on this account. + WithheldAmount uint64 +} + +// --- MintCloseAuthority --- + +// MintCloseAuthorityState is the extension state for ExtensionMintCloseAuthority. +type MintCloseAuthorityState struct { + CloseAuthority OptionalPubkey +} + +// --- ConfidentialTransferMint --- + +// ConfidentialTransferMintState is the extension state for ExtensionConfidentialTransferMint. +type ConfidentialTransferMintState struct { + // Authority to modify the ConfidentialTransferMint configuration. + Authority OptionalPubkey + // Determines if newly configured accounts must be approved before they can be used. + AutoApproveNewAccounts bool + // ElGamal pubkey used to encrypt data for the auditor. + AuditorElGamalPubkey [32]byte +} + +// ConfidentialTransferAccountState is the extension state for ExtensionConfidentialTransferAccount. +type ConfidentialTransferAccountState struct { + // `true` if this account has been approved for use. + Approved bool + // The ElGamal public key associated with the account. + ElGamalPubkey [32]byte + // The pending balance (encrypted by ElGamal). + PendingBalanceLo [64]byte + PendingBalanceHi [64]byte + // The available balance (encrypted by ElGamal). + AvailableBalance [64]byte + // The decryptable available balance. + DecryptableAvailableBalance [36]byte + // If `true`, the extended deposit side-car needs to be checked for pending deposits. + AllowConfidentialCredits bool + // If `true`, credits to this account must have an ElGamal-encrypted memo. + AllowNonConfidentialCredits bool + // The total number of `Deposit` and `Transfer` instructions applied. + PendingBalanceCreditCounter uint64 + // The maximum number of `Deposit` and `Transfer` instructions that can be applied + // before the `ApplyPendingBalance` instruction is used. + MaximumPendingBalanceCreditCounter uint64 + // The `expected_pending_balance_credit_counter` value used in the last `ApplyPendingBalance`. + ExpectedPendingBalanceCreditCounter uint64 + // The actual `pending_balance_credit_counter` when the last `ApplyPendingBalance` was applied. + ActualPendingBalanceCreditCounter uint64 +} + +// --- DefaultAccountState --- + +// DefaultAccountStateConfig is the extension state for ExtensionDefaultAccountState. +type DefaultAccountStateConfig struct { + // The default state for new accounts. + State AccountState +} + +// --- ImmutableOwner --- +// ImmutableOwner is a marker extension with no state data. + +// --- MemoTransfer --- + +// MemoTransferState is the extension state for ExtensionMemoTransfer (account extension). +type MemoTransferState struct { + // If true, incoming transfers must have a memo. + RequireIncomingTransferMemos bool +} + +// --- NonTransferable --- +// NonTransferable and NonTransferableAccount are marker extensions with no state data. + +// --- InterestBearingConfig --- + +// InterestBearingConfigState is the extension state for ExtensionInterestBearingConfig. +type InterestBearingConfigState struct { + // Authority that can set the interest rate. + RateAuthority OptionalPubkey + // Timestamp of initialization, from which interest is accrued. + InitializationTimestamp int64 + // Pre-update average rate, in basis points. + PreUpdateAverageRate int16 + // Timestamp of the last update. + LastUpdateTimestamp int64 + // Current rate, in basis points. + CurrentRate int16 +} + +// --- CpiGuard --- + +// CpiGuardState is the extension state for ExtensionCpiGuard (account extension). +type CpiGuardState struct { + // If true, CPI is locked for privileged operations. + LockCpi bool +} + +// --- PermanentDelegate --- + +// PermanentDelegateState is the extension state for ExtensionPermanentDelegate. +type PermanentDelegateState struct { + // The permanent delegate. + Delegate OptionalPubkey +} + +// --- TransferHook --- + +// TransferHookState is the extension state for ExtensionTransferHook (mint extension). +type TransferHookState struct { + // Authority that can set the transfer hook program ID. + Authority OptionalPubkey + // The transfer hook program ID. + ProgramID OptionalPubkey +} + +// TransferHookAccountState is the extension state for ExtensionTransferHookAccount (account extension). +type TransferHookAccountState struct { + // Whether the account is currently in the middle of a transfer. + Transferring bool +} + +// --- ConfidentialTransferFee --- + +// ConfidentialTransferFeeConfigState is the extension state for ExtensionConfidentialTransferFeeConfig. +type ConfidentialTransferFeeConfigState struct { + // Authority to set the withdraw withheld authority ElGamal key. + Authority OptionalPubkey + // ElGamal pubkey used to encrypt withheld fees. + WithdrawWithheldAuthorityElGamalPubkey [32]byte + // If true, harvest to mint is enabled. + HarvestToMintEnabled bool + // Withheld amount encrypted by the withdraw withheld authority. + WithheldAmount [64]byte +} + +// ConfidentialTransferFeeAmountState is the extension state for ExtensionConfidentialTransferFeeAmount. +type ConfidentialTransferFeeAmountState struct { + // Encrypted withheld fees on this account. + WithheldAmount [64]byte +} + +// --- MetadataPointer --- + +// MetadataPointerState is the extension state for ExtensionMetadataPointer. +type MetadataPointerState struct { + // Authority that can set the metadata address. + Authority OptionalPubkey + // Account address that holds the metadata. + MetadataAddress OptionalPubkey +} + +// --- TokenMetadata --- + +// TokenMetadataState is the extension state for ExtensionTokenMetadata. +// This is a variable-length extension. +type TokenMetadataState struct { + // The authority that can update the metadata. + UpdateAuthority OptionalPubkey + // The associated mint. + Mint solana.PublicKey + // The name of the token. + Name string + // The symbol of the token. + Symbol string + // The URI of the token metadata. + Uri string + // Additional metadata as key-value pairs. + AdditionalMetadata []MetadataField +} + +// MetadataField is a key-value pair for additional token metadata. +type MetadataField struct { + Key string + Value string +} + +func (m *TokenMetadataState) UnmarshalWithDecoder(dec *bin.Decoder) error { + // UpdateAuthority: 32 bytes OptionalNonZeroPubkey + { + v, err := dec.ReadNBytes(32) + if err != nil { + return err + } + m.UpdateAuthority = OptionalPubkey{Key: solana.PublicKeyFromBytes(v)} + } + // Mint + { + v, err := dec.ReadNBytes(32) + if err != nil { + return err + } + m.Mint = solana.PublicKeyFromBytes(v) + } + // Name (borsh string: u32 length prefix + bytes) + { + length, err := dec.ReadUint32(binary.LittleEndian) + if err != nil { + return err + } + if length > maxMetadataStringLen { + return fmt.Errorf("token metadata name too long: %d", length) + } + v, err := dec.ReadNBytes(int(length)) + if err != nil { + return err + } + m.Name = string(v) + } + // Symbol + { + length, err := dec.ReadUint32(binary.LittleEndian) + if err != nil { + return err + } + if length > maxMetadataStringLen { + return fmt.Errorf("token metadata symbol too long: %d", length) + } + v, err := dec.ReadNBytes(int(length)) + if err != nil { + return err + } + m.Symbol = string(v) + } + // Uri + { + length, err := dec.ReadUint32(binary.LittleEndian) + if err != nil { + return err + } + if length > maxMetadataStringLen { + return fmt.Errorf("token metadata uri too long: %d", length) + } + v, err := dec.ReadNBytes(int(length)) + if err != nil { + return err + } + m.Uri = string(v) + } + // AdditionalMetadata: borsh Vec<(String, String)> + { + count, err := dec.ReadUint32(binary.LittleEndian) + if err != nil { + return err + } + if count > maxMetadataFields { + return fmt.Errorf("token metadata additional fields count too large: %d", count) + } + m.AdditionalMetadata = make([]MetadataField, count) + for i := uint32(0); i < count; i++ { + // Key + kLen, err := dec.ReadUint32(binary.LittleEndian) + if err != nil { + return err + } + if kLen > maxMetadataStringLen { + return fmt.Errorf("token metadata field key too long: %d", kLen) + } + kBytes, err := dec.ReadNBytes(int(kLen)) + if err != nil { + return err + } + // Value + vLen, err := dec.ReadUint32(binary.LittleEndian) + if err != nil { + return err + } + if vLen > maxMetadataStringLen { + return fmt.Errorf("token metadata field value too long: %d", vLen) + } + vBytes, err := dec.ReadNBytes(int(vLen)) + if err != nil { + return err + } + m.AdditionalMetadata[i] = MetadataField{Key: string(kBytes), Value: string(vBytes)} + } + } + return nil +} + +func (m TokenMetadataState) MarshalWithEncoder(enc *bin.Encoder) error { + // UpdateAuthority + if err := enc.WriteBytes(m.UpdateAuthority.Key[:], false); err != nil { + return err + } + // Mint + if err := enc.WriteBytes(m.Mint[:], false); err != nil { + return err + } + // Name + if err := enc.WriteUint32(uint32(len(m.Name)), binary.LittleEndian); err != nil { + return err + } + if err := enc.WriteBytes([]byte(m.Name), false); err != nil { + return err + } + // Symbol + if err := enc.WriteUint32(uint32(len(m.Symbol)), binary.LittleEndian); err != nil { + return err + } + if err := enc.WriteBytes([]byte(m.Symbol), false); err != nil { + return err + } + // Uri + if err := enc.WriteUint32(uint32(len(m.Uri)), binary.LittleEndian); err != nil { + return err + } + if err := enc.WriteBytes([]byte(m.Uri), false); err != nil { + return err + } + // AdditionalMetadata + if err := enc.WriteUint32(uint32(len(m.AdditionalMetadata)), binary.LittleEndian); err != nil { + return err + } + for _, field := range m.AdditionalMetadata { + if err := enc.WriteUint32(uint32(len(field.Key)), binary.LittleEndian); err != nil { + return err + } + if err := enc.WriteBytes([]byte(field.Key), false); err != nil { + return err + } + if err := enc.WriteUint32(uint32(len(field.Value)), binary.LittleEndian); err != nil { + return err + } + if err := enc.WriteBytes([]byte(field.Value), false); err != nil { + return err + } + } + return nil +} + +// --- GroupPointer --- + +// GroupPointerState is the extension state for ExtensionGroupPointer. +type GroupPointerState struct { + // Authority that can set the group address. + Authority OptionalPubkey + // Account address that holds the group. + GroupAddress OptionalPubkey +} + +// --- TokenGroup --- + +// TokenGroupState is the extension state for ExtensionTokenGroup. +type TokenGroupState struct { + // The authority that can update the group. + UpdateAuthority OptionalPubkey + // The associated mint. + Mint solana.PublicKey + // The current number of group members. + Size uint32 + // The maximum number of group members. + MaxSize uint32 +} + +// --- GroupMemberPointer --- + +// GroupMemberPointerState is the extension state for ExtensionGroupMemberPointer. +type GroupMemberPointerState struct { + // Authority that can set the member address. + Authority OptionalPubkey + // Account address that holds the member. + MemberAddress OptionalPubkey +} + +// --- TokenGroupMember --- + +// TokenGroupMemberState is the extension state for ExtensionTokenGroupMember. +type TokenGroupMemberState struct { + // The associated mint. + Mint solana.PublicKey + // The parent group. + Group solana.PublicKey + // The member number. + MemberNumber uint32 +} + +// --- ConfidentialMintBurn --- + +// ConfidentialMintBurnState is the extension state for ExtensionConfidentialMintBurn. +type ConfidentialMintBurnState struct { + // Authority to modify the confidential mint burn configuration. + ConfidentialSupply [64]byte + // The decryptable supply. + DecryptableSupply [36]byte + // The ElGamal pubkey used for supply encryption. + SupplyElGamalPubkey [32]byte +} + +// --- ScaledUiAmount --- + +// ScaledUiAmountState is the extension state for ExtensionScaledUiAmount. +type ScaledUiAmountState struct { + // Authority that can set the multiplier. + Authority OptionalPubkey + // The current multiplier. + Multiplier float64 + // The timestamp at which the new multiplier takes effect. + NewMultiplierEffectiveTimestamp int64 + // The new multiplier. + NewMultiplier float64 +} + +// --- Pausable --- + +// PausableState is the extension state for ExtensionPausable. +type PausableState struct { + // Authority that can pause/resume. + Authority OptionalPubkey + // If true, minting/burning/transferring is paused. + Paused bool +} + +// PausableAccountState is the extension state for ExtensionPausableAccount. +// This is a marker extension with no additional state. + +// --- PermissionedBurn --- + +// PermissionedBurnState is the extension state for ExtensionPermissionedBurn. +type PermissionedBurnState struct { + // Authority that must approve burns. + Authority OptionalPubkey +} + +// --- Extension TLV Parsing --- + +// ExtensionTLV represents a parsed extension from account data. +type ExtensionTLV struct { + Type ExtensionType + Length uint16 + Data []byte +} + +// ParseExtensions parses extension TLV entries from raw account data. +// The data should start after the base account/mint data and the account type byte. +func ParseExtensions(data []byte) ([]ExtensionTLV, error) { + var extensions []ExtensionTLV + offset := 0 + for offset+4 <= len(data) { + extType := ExtensionType(binary.LittleEndian.Uint16(data[offset:])) + extLen := binary.LittleEndian.Uint16(data[offset+2:]) + offset += 4 + if offset+int(extLen) > len(data) { + return extensions, fmt.Errorf("extension data truncated: need %d bytes, have %d", extLen, len(data)-offset) + } + extensions = append(extensions, ExtensionTLV{ + Type: extType, + Length: extLen, + Data: data[offset : offset+int(extLen)], + }) + offset += int(extLen) + } + return extensions, nil +} + +// ParseMintWithExtensions parses a Mint and its extensions from raw account data. +func ParseMintWithExtensions(data []byte) (*Mint, []ExtensionTLV, error) { + if len(data) < MINT_SIZE { + return nil, nil, fmt.Errorf("data too short for mint: %d < %d", len(data), MINT_SIZE) + } + mint := new(Mint) + dec := bin.NewBinDecoder(data[:MINT_SIZE]) + if err := mint.UnmarshalWithDecoder(dec); err != nil { + return nil, nil, fmt.Errorf("unable to decode mint: %w", err) + } + if len(data) <= MINT_SIZE { + return mint, nil, nil + } + // Skip padding to multisig boundary and account type byte. + extStart := ACCOUNT_SIZE + 1 + if extStart > len(data) { + return mint, nil, nil + } + extensions, err := ParseExtensions(data[extStart:]) + if err != nil { + return mint, extensions, err + } + return mint, extensions, nil +} + +// ParseAccountWithExtensions parses a Token Account and its extensions from raw account data. +func ParseAccountWithExtensions(data []byte) (*Account, []ExtensionTLV, error) { + if len(data) < ACCOUNT_SIZE { + return nil, nil, fmt.Errorf("data too short for account: %d < %d", len(data), ACCOUNT_SIZE) + } + acct := new(Account) + dec := bin.NewBinDecoder(data[:ACCOUNT_SIZE]) + if err := acct.UnmarshalWithDecoder(dec); err != nil { + return nil, nil, fmt.Errorf("unable to decode account: %w", err) + } + if len(data) <= ACCOUNT_SIZE { + return acct, nil, nil + } + // Account type byte is at offset ACCOUNT_SIZE. + extStart := ACCOUNT_SIZE + 1 + if extStart > len(data) { + return acct, nil, nil + } + extensions, err := ParseExtensions(data[extStart:]) + if err != nil { + return acct, extensions, err + } + return acct, extensions, nil +} diff --git a/programs/token-2022/extension_test.go b/programs/token-2022/extension_test.go new file mode 100644 index 00000000..fbcf7dae --- /dev/null +++ b/programs/token-2022/extension_test.go @@ -0,0 +1,755 @@ +package token2022 + +import ( + "bytes" + "encoding/binary" + "testing" + + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_require "github.com/stretchr/testify/require" +) + +// Helper: create a [32]byte filled with a single value. +func pubkeyOf(v byte) ag_solanago.PublicKey { + var pk ag_solanago.PublicKey + for i := range pk { + pk[i] = v + } + return pk +} + +// Helper: build expected bytes from parts. +func concat(parts ...[]byte) []byte { + var out []byte + for _, p := range parts { + out = append(out, p...) + } + return out +} + +func repeatByte(b byte, n int) []byte { + out := make([]byte, n) + for i := range out { + out[i] = b + } + return out +} + +func u64LE(v uint64) []byte { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, v) + return b +} + +func u16LE(v uint16) []byte { + b := make([]byte, 2) + binary.LittleEndian.PutUint16(b, v) + return b +} + +// optionalPubkeyBytes encodes a COption for instruction data: +// 1 byte discriminator + 32 bytes. +func optionalPubkeyBytes(pk *ag_solanago.PublicKey) []byte { + if pk == nil { + return concat([]byte{0}, repeatByte(0, 32)) + } + return concat([]byte{1}, pk[:]) +} + +// =================================================================== +// Tests ported from solana-program/token-2022 interface/src/instruction.rs +// =================================================================== + +// Test vectors from the official Rust tests for token-2022-specific instructions. +// The existing instruction_test.go covers base token instructions (0-24) via fuzz. +// These tests verify exact byte-level compatibility with the Rust implementation. + +func TestOfficialVector_InitializeMintCloseAuthority(t *testing.T) { + pk10 := pubkeyOf(10) + inst := NewInitializeMintCloseAuthorityInstruction(pk10, pubkeyOf(0)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + expected := concat([]byte{25}, optionalPubkeyBytes(&pk10)) + ag_require.Equal(t, expected, data) +} + +func TestOfficialVector_CreateNativeMint(t *testing.T) { + inst := NewCreateNativeMintInstructionBuilder(). + SetPayerAccount(pubkeyOf(1)). + SetNativeMintAccount(pubkeyOf(2)). + SetSystemProgramAccount(ag_solanago.SystemProgramID) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + expected := []byte{31} + ag_require.Equal(t, expected, data) +} + +func TestOfficialVector_InitializePermanentDelegate(t *testing.T) { + pk11 := pubkeyOf(11) + inst := NewInitializePermanentDelegateInstruction(pk11, pubkeyOf(0)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + expected := concat([]byte{35}, pk11[:]) + ag_require.Equal(t, expected, data) +} + +func TestOfficialVector_GetAccountDataSize_Empty(t *testing.T) { + inst := NewGetAccountDataSizeInstruction(nil, pubkeyOf(0)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + expected := []byte{21} + ag_require.Equal(t, expected, data) +} + +func TestOfficialVector_GetAccountDataSize_WithExtensions(t *testing.T) { + inst := NewGetAccountDataSizeInstruction( + []ExtensionType{ExtensionTransferFeeConfig, ExtensionTransferFeeAmount}, + pubkeyOf(0), + ) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + // [21, 1, 0, 2, 0] -- tag 21, then u16 LE for each extension type + expected := concat([]byte{21}, u16LE(1), u16LE(2)) + ag_require.Equal(t, expected, data) +} + +func TestOfficialVector_UnwrapLamports_None(t *testing.T) { + inst := NewUnwrapLamportsInstruction(nil, pubkeyOf(1), pubkeyOf(2), pubkeyOf(3), nil) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + expected := []byte{45, 0} + ag_require.Equal(t, expected, data) +} + +func TestOfficialVector_UnwrapLamports_Some(t *testing.T) { + amount := uint64(1) + inst := NewUnwrapLamportsInstruction(&amount, pubkeyOf(1), pubkeyOf(2), pubkeyOf(3), nil) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + expected := concat([]byte{45, 1}, u64LE(1)) + ag_require.Equal(t, expected, data) +} + +// =================================================================== +// TransferFee extension instruction tests +// Ported from interface/src/extension/transfer_fee/instruction.rs +// =================================================================== + +func TestOfficialVector_TransferFee_InitializeConfig(t *testing.T) { + pk11 := pubkeyOf(11) + inst := NewInitializeTransferFeeConfigInstruction( + &pk11, // authority + nil, // withdraw_withheld_authority = None + 111, // basis_points + ^uint64(0), // max_fee = u64::MAX + pubkeyOf(0), + ) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + // [26, 0, , , bps_u16, max_fee_u64] + expected := concat( + []byte{26, 0}, // instruction tag 26, sub-instruction 0 + optionalPubkeyBytes(&pk11), + optionalPubkeyBytes(nil), + u16LE(111), + u64LE(^uint64(0)), + ) + ag_require.Equal(t, expected, data) +} + +func TestOfficialVector_TransferFee_TransferCheckedWithFee(t *testing.T) { + inst := NewTransferCheckedWithFeeInstruction( + 24, 24, 23, + pubkeyOf(1), pubkeyOf(2), pubkeyOf(3), pubkeyOf(4), nil, + ) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + expected := concat( + []byte{26, 1}, // instruction tag 26, sub-instruction 1 + u64LE(24), // amount + []byte{24}, // decimals + u64LE(23), // fee + ) + ag_require.Equal(t, expected, data) +} + +func TestOfficialVector_TransferFee_WithdrawFromMint(t *testing.T) { + inst := NewWithdrawWithheldTokensFromMintInstruction( + pubkeyOf(1), pubkeyOf(2), pubkeyOf(3), nil, + ) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + expected := []byte{26, 2} + ag_require.Equal(t, expected, data) +} + +func TestOfficialVector_TransferFee_HarvestToMint(t *testing.T) { + inst := NewHarvestWithheldTokensToMintInstruction(pubkeyOf(1)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + expected := []byte{26, 4} + ag_require.Equal(t, expected, data) +} + +func TestOfficialVector_TransferFee_SetTransferFee(t *testing.T) { + inst := NewSetTransferFeeInstruction( + ^uint16(0), ^uint64(0), + pubkeyOf(1), pubkeyOf(2), nil, + ) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + expected := concat( + []byte{26, 5}, + u16LE(^uint16(0)), + u64LE(^uint64(0)), + ) + ag_require.Equal(t, expected, data) +} + +// =================================================================== +// Extension instruction round-trip tests +// =================================================================== + +func TestRoundTrip_DefaultAccountState(t *testing.T) { + t.Run("Initialize", func(t *testing.T) { + inst := NewInitializeDefaultAccountStateInstruction(AccountStateFrozen, pubkeyOf(1)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + ag_require.Equal(t, byte(Instruction_DefaultAccountStateExtension), data[0]) + ag_require.Equal(t, byte(DefaultAccountState_Initialize), data[1]) + ag_require.Equal(t, byte(AccountStateFrozen), data[2]) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*DefaultAccountStateExtension) + ag_require.Equal(t, DefaultAccountState_Initialize, ext.SubInstruction) + ag_require.Equal(t, AccountStateFrozen, *ext.State) + }) + + t.Run("Update", func(t *testing.T) { + inst := NewUpdateDefaultAccountStateInstruction( + AccountStateInitialized, pubkeyOf(1), pubkeyOf(2), nil, + ) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*DefaultAccountStateExtension) + ag_require.Equal(t, DefaultAccountState_Update, ext.SubInstruction) + ag_require.Equal(t, AccountStateInitialized, *ext.State) + }) +} + +func TestRoundTrip_MemoTransfer(t *testing.T) { + inst := NewEnableMemoTransferInstruction(pubkeyOf(1), pubkeyOf(2), nil) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + ag_require.Equal(t, byte(Instruction_MemoTransferExtension), data[0]) + ag_require.Equal(t, byte(MemoTransfer_Enable), data[1]) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*MemoTransferExtension) + ag_require.Equal(t, MemoTransfer_Enable, ext.SubInstruction) +} + +func TestRoundTrip_CpiGuard(t *testing.T) { + inst := NewDisableCpiGuardInstruction(pubkeyOf(1), pubkeyOf(2), nil) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + ag_require.Equal(t, byte(Instruction_CpiGuardExtension), data[0]) + ag_require.Equal(t, byte(CpiGuard_Disable), data[1]) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*CpiGuardExtension) + ag_require.Equal(t, CpiGuard_Disable, ext.SubInstruction) +} + +func TestRoundTrip_InterestBearing(t *testing.T) { + t.Run("Initialize", func(t *testing.T) { + pk := pubkeyOf(5) + inst := NewInitializeInterestBearingMintInstruction(&pk, 100, pubkeyOf(1)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*InterestBearingMintExtension) + ag_require.Equal(t, InterestBearingMint_Initialize, ext.SubInstruction) + ag_require.Equal(t, &pk, ext.RateAuthority) + ag_require.Equal(t, int16(100), ext.Rate) + }) + + t.Run("UpdateRate", func(t *testing.T) { + inst := NewUpdateInterestRateInstruction(250, pubkeyOf(1), pubkeyOf(2), nil) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*InterestBearingMintExtension) + ag_require.Equal(t, InterestBearingMint_UpdateRate, ext.SubInstruction) + ag_require.Equal(t, int16(250), ext.Rate) + }) +} + +func TestRoundTrip_TransferHook(t *testing.T) { + t.Run("Initialize", func(t *testing.T) { + auth := pubkeyOf(5) + hookProg := pubkeyOf(6) + inst := NewInitializeTransferHookInstruction(&auth, &hookProg, pubkeyOf(1)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*TransferHookExtension) + ag_require.Equal(t, TransferHook_Initialize, ext.SubInstruction) + ag_require.Equal(t, &auth, ext.Authority) + ag_require.Equal(t, &hookProg, ext.HookProgramID) + }) + + t.Run("Update", func(t *testing.T) { + hookProg := pubkeyOf(7) + inst := NewUpdateTransferHookInstruction(&hookProg, pubkeyOf(1), pubkeyOf(2), nil) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*TransferHookExtension) + ag_require.Equal(t, TransferHook_Update, ext.SubInstruction) + ag_require.Equal(t, &hookProg, ext.HookProgramID) + }) +} + +func TestRoundTrip_MetadataPointer(t *testing.T) { + auth := pubkeyOf(3) + metaAddr := pubkeyOf(4) + inst := NewInitializeMetadataPointerInstruction(&auth, &metaAddr, pubkeyOf(1)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*MetadataPointerExtension) + ag_require.Equal(t, MetadataPointer_Initialize, ext.SubInstruction) + ag_require.Equal(t, &auth, ext.Authority) + ag_require.Equal(t, &metaAddr, ext.MetadataAddress) +} + +func TestRoundTrip_GroupPointer(t *testing.T) { + auth := pubkeyOf(3) + groupAddr := pubkeyOf(4) + inst := NewInitializeGroupPointerInstruction(&auth, &groupAddr, pubkeyOf(1)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*GroupPointerExtension) + ag_require.Equal(t, GroupPointer_Initialize, ext.SubInstruction) + ag_require.Equal(t, &auth, ext.Authority) + ag_require.Equal(t, &groupAddr, ext.GroupAddress) +} + +func TestRoundTrip_GroupMemberPointer(t *testing.T) { + auth := pubkeyOf(3) + memberAddr := pubkeyOf(4) + inst := NewInitializeGroupMemberPointerInstruction(&auth, &memberAddr, pubkeyOf(1)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*GroupMemberPointerExtension) + ag_require.Equal(t, GroupMemberPointer_Initialize, ext.SubInstruction) + ag_require.Equal(t, &auth, ext.Authority) + ag_require.Equal(t, &memberAddr, ext.MemberAddress) +} + +func TestRoundTrip_Pausable(t *testing.T) { + t.Run("Initialize", func(t *testing.T) { + inst := NewInitializePausableInstruction(pubkeyOf(1)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + ag_require.Equal(t, byte(Instruction_PausableExtension), data[0]) + ag_require.Equal(t, byte(Pausable_Initialize), data[1]) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*PausableExtension) + ag_require.Equal(t, Pausable_Initialize, ext.SubInstruction) + }) + + t.Run("Pause", func(t *testing.T) { + inst := NewPauseInstruction(pubkeyOf(1), pubkeyOf(2), nil) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*PausableExtension) + ag_require.Equal(t, Pausable_Pause, ext.SubInstruction) + }) + + t.Run("Resume", func(t *testing.T) { + inst := NewResumeInstruction(pubkeyOf(1), pubkeyOf(2), nil) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*PausableExtension) + ag_require.Equal(t, Pausable_Resume, ext.SubInstruction) + }) +} + +func TestRoundTrip_PermissionedBurn(t *testing.T) { + inst := NewInitializePermissionedBurnInstruction(pubkeyOf(1)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + ag_require.Equal(t, byte(Instruction_PermissionedBurnExtension), data[0]) + ag_require.Equal(t, byte(PermissionedBurn_Initialize), data[1]) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*PermissionedBurnExtension) + ag_require.Equal(t, PermissionedBurn_Initialize, ext.SubInstruction) +} + +func TestRoundTrip_InitializeNonTransferableMint(t *testing.T) { + inst := NewInitializeNonTransferableMintInstruction(pubkeyOf(1)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + expected := []byte{32} + ag_require.Equal(t, expected, data) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ag_require.Equal(t, "InitializeNonTransferableMint", InstructionIDToName(decoded.TypeID.Uint8())) +} + +func TestRoundTrip_Reallocate(t *testing.T) { + inst := NewReallocateInstruction( + []ExtensionType{ExtensionTransferFeeConfig, ExtensionMemoTransfer}, + pubkeyOf(1), pubkeyOf(2), pubkeyOf(3), nil, + ) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + ag_require.Equal(t, byte(Instruction_Reallocate), data[0]) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + realloc := decoded.Impl.(*Reallocate) + ag_require.Equal(t, []ExtensionType{ExtensionTransferFeeConfig, ExtensionMemoTransfer}, realloc.ExtensionTypes) +} + +func TestRoundTrip_WithdrawExcessLamports(t *testing.T) { + inst := NewWithdrawExcessLamportsInstruction( + pubkeyOf(1), pubkeyOf(2), pubkeyOf(3), nil, + ) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + expected := []byte{38} + ag_require.Equal(t, expected, data) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ag_require.Equal(t, "WithdrawExcessLamports", InstructionIDToName(decoded.TypeID.Uint8())) +} + +func TestRoundTrip_ScaledUiAmount(t *testing.T) { + t.Run("Initialize", func(t *testing.T) { + auth := pubkeyOf(5) + inst := NewInitializeScaledUiAmountInstruction(&auth, 1.5, pubkeyOf(1)) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*ScaledUiAmountExtension) + ag_require.Equal(t, ScaledUiAmount_Initialize, ext.SubInstruction) + ag_require.Equal(t, &auth, ext.Authority) + ag_require.Equal(t, 1.5, ext.Multiplier) + }) + + t.Run("UpdateMultiplier", func(t *testing.T) { + inst := NewUpdateScaledUiAmountMultiplierInstruction( + 2.0, 1000000, pubkeyOf(1), pubkeyOf(2), nil, + ) + built := inst.Build() + data, err := built.Data() + ag_require.NoError(t, err) + + decoded, err := DecodeInstruction(nil, data) + ag_require.NoError(t, err) + ext := decoded.Impl.(*ScaledUiAmountExtension) + ag_require.Equal(t, ScaledUiAmount_UpdateMultiplier, ext.SubInstruction) + ag_require.Equal(t, 2.0, ext.Multiplier) + ag_require.Equal(t, int64(1000000), ext.EffectiveTimestamp) + }) +} + +// =================================================================== +// Account/Mint state deserialization tests +// Test vectors from interface/src/state.rs and extension/mod.rs +// =================================================================== + +// TEST_MINT_SLICE from the official repo: +// mint_authority=Some([1;32]), supply=42, decimals=7, is_initialized=true, freeze_authority=Some([2;32]) +var testMintSlice = []byte{ + 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 42, 0, 0, 0, 0, 0, 0, 0, 7, 1, 1, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +} + +func TestOfficialVector_MintDeserialization(t *testing.T) { + ag_require.Equal(t, 82, len(testMintSlice)) + + mint := new(Mint) + err := mint.UnmarshalWithDecoder(ag_binary.NewBinDecoder(testMintSlice)) + ag_require.NoError(t, err) + + expectedAuthority := pubkeyOf(1) + ag_require.NotNil(t, mint.MintAuthority) + ag_require.Equal(t, expectedAuthority, *mint.MintAuthority) + ag_require.Equal(t, uint64(42), mint.Supply) + ag_require.Equal(t, uint8(7), mint.Decimals) + ag_require.True(t, mint.IsInitialized) + expectedFreeze := pubkeyOf(2) + ag_require.NotNil(t, mint.FreezeAuthority) + ag_require.Equal(t, expectedFreeze, *mint.FreezeAuthority) + + // Round-trip + buf := new(bytes.Buffer) + err = mint.MarshalWithEncoder(ag_binary.NewBinEncoder(buf)) + ag_require.NoError(t, err) + ag_require.Equal(t, testMintSlice, buf.Bytes()) +} + +// TEST_ACCOUNT_SLICE from the official repo: +// mint=[1;32], owner=[2;32], amount=3, delegate=Some([4;32]), state=Frozen(2), +// is_native=Some(5), delegated_amount=6, close_authority=Some([7;32]) +var testAccountSlice = []byte{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 1, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, + 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +} + +func TestOfficialVector_AccountDeserialization(t *testing.T) { + ag_require.Equal(t, 165, len(testAccountSlice)) + + acct := new(Account) + err := acct.UnmarshalWithDecoder(ag_binary.NewBinDecoder(testAccountSlice)) + ag_require.NoError(t, err) + + ag_require.Equal(t, pubkeyOf(1), acct.Mint) + ag_require.Equal(t, pubkeyOf(2), acct.Owner) + ag_require.Equal(t, uint64(3), acct.Amount) + expectedDelegate := pubkeyOf(4) + ag_require.NotNil(t, acct.Delegate) + ag_require.Equal(t, expectedDelegate, *acct.Delegate) + ag_require.Equal(t, AccountStateFrozen, acct.State) + ag_require.NotNil(t, acct.IsNative) + ag_require.Equal(t, uint64(5), *acct.IsNative) + ag_require.Equal(t, uint64(6), acct.DelegatedAmount) + expectedClose := pubkeyOf(7) + ag_require.NotNil(t, acct.CloseAuthority) + ag_require.Equal(t, expectedClose, *acct.CloseAuthority) + + // Round-trip + buf := new(bytes.Buffer) + err = acct.MarshalWithEncoder(ag_binary.NewBinEncoder(buf)) + ag_require.NoError(t, err) + ag_require.Equal(t, testAccountSlice, buf.Bytes()) +} + +// =================================================================== +// Extension TLV parsing tests +// From interface/src/extension/mod.rs +// =================================================================== + +func TestParseMintWithExtensions(t *testing.T) { + // Build MINT_WITH_EXTENSION: base mint + padding to 165 + account type byte + TLV + // 82 bytes mint + 83 bytes zero padding + 1 byte AccountType::Mint + 4 byte TLV header + 32 byte data + mintData := make([]byte, 0, 215) + mintData = append(mintData, testMintSlice...) + mintData = append(mintData, make([]byte, 83)...) // zero padding to offset 165 + mintData = append(mintData, AccountTypeMint) // account type byte at offset 165 + // TLV entry: type=3 (MintCloseAuthority), length=32 + mintData = append(mintData, 3, 0, 32, 0) // u16 LE type, u16 LE length + mintData = append(mintData, repeatByte(1, 32)...) + + ag_require.Equal(t, 202, len(mintData)) + + mint, extensions, err := ParseMintWithExtensions(mintData) + ag_require.NoError(t, err) + ag_require.NotNil(t, mint) + ag_require.Equal(t, uint64(42), mint.Supply) + ag_require.Len(t, extensions, 1) + ag_require.Equal(t, ExtensionMintCloseAuthority, extensions[0].Type) + ag_require.Equal(t, uint16(32), extensions[0].Length) + ag_require.Equal(t, repeatByte(1, 32), extensions[0].Data) +} + +func TestParseAccountWithExtensions(t *testing.T) { + // Build ACCOUNT_WITH_EXTENSION: base account + AccountType::Account + TLV + acctData := make([]byte, 0, 171) + acctData = append(acctData, testAccountSlice...) + acctData = append(acctData, AccountTypeAccount) // account type byte at offset 165 + // TLV entry: type=15 (TransferHookAccount), length=1 + acctData = append(acctData, 15, 0, 1, 0) // u16 LE type, u16 LE length + acctData = append(acctData, 1) // transferring=true + + ag_require.Equal(t, 171, len(acctData)) + + acct, extensions, err := ParseAccountWithExtensions(acctData) + ag_require.NoError(t, err) + ag_require.NotNil(t, acct) + ag_require.Equal(t, uint64(3), acct.Amount) + ag_require.Equal(t, AccountStateFrozen, acct.State) + ag_require.Len(t, extensions, 1) + ag_require.Equal(t, ExtensionTransferHookAccount, extensions[0].Type) + ag_require.Equal(t, uint16(1), extensions[0].Length) + ag_require.Equal(t, []byte{1}, extensions[0].Data) +} + +// =================================================================== +// TokenMetadata state serialization round-trip +// =================================================================== + +func TestTokenMetadata_RoundTrip(t *testing.T) { + meta := TokenMetadataState{ + UpdateAuthority: NewOptionalPubkey(&ag_solanago.PublicKey{1, 2, 3}), + Mint: pubkeyOf(10), + Name: "Test Token", + Symbol: "TST", + Uri: "https://example.com/metadata.json", + AdditionalMetadata: []MetadataField{ + {Key: "description", Value: "A test token"}, + {Key: "image", Value: "https://example.com/image.png"}, + }, + } + + buf := new(bytes.Buffer) + err := meta.MarshalWithEncoder(ag_binary.NewBinEncoder(buf)) + ag_require.NoError(t, err) + + got := new(TokenMetadataState) + err = got.UnmarshalWithDecoder(ag_binary.NewBinDecoder(buf.Bytes())) + ag_require.NoError(t, err) + + ag_require.Equal(t, meta.UpdateAuthority, got.UpdateAuthority) + ag_require.Equal(t, meta.Mint, got.Mint) + ag_require.Equal(t, meta.Name, got.Name) + ag_require.Equal(t, meta.Symbol, got.Symbol) + ag_require.Equal(t, meta.Uri, got.Uri) + ag_require.Equal(t, meta.AdditionalMetadata, got.AdditionalMetadata) +} + +// =================================================================== +// Instruction ID constants verification +// Ensure our iota-based constants match the official program values. +// =================================================================== + +func TestInstructionIDValues(t *testing.T) { + ag_require.Equal(t, uint8(0), Instruction_InitializeMint) + ag_require.Equal(t, uint8(1), Instruction_InitializeAccount) + ag_require.Equal(t, uint8(2), Instruction_InitializeMultisig) + ag_require.Equal(t, uint8(3), Instruction_Transfer) + ag_require.Equal(t, uint8(4), Instruction_Approve) + ag_require.Equal(t, uint8(5), Instruction_Revoke) + ag_require.Equal(t, uint8(6), Instruction_SetAuthority) + ag_require.Equal(t, uint8(7), Instruction_MintTo) + ag_require.Equal(t, uint8(8), Instruction_Burn) + ag_require.Equal(t, uint8(9), Instruction_CloseAccount) + ag_require.Equal(t, uint8(10), Instruction_FreezeAccount) + ag_require.Equal(t, uint8(11), Instruction_ThawAccount) + ag_require.Equal(t, uint8(12), Instruction_TransferChecked) + ag_require.Equal(t, uint8(13), Instruction_ApproveChecked) + ag_require.Equal(t, uint8(14), Instruction_MintToChecked) + ag_require.Equal(t, uint8(15), Instruction_BurnChecked) + ag_require.Equal(t, uint8(16), Instruction_InitializeAccount2) + ag_require.Equal(t, uint8(17), Instruction_SyncNative) + ag_require.Equal(t, uint8(18), Instruction_InitializeAccount3) + ag_require.Equal(t, uint8(19), Instruction_InitializeMultisig2) + ag_require.Equal(t, uint8(20), Instruction_InitializeMint2) + ag_require.Equal(t, uint8(21), Instruction_GetAccountDataSize) + ag_require.Equal(t, uint8(22), Instruction_InitializeImmutableOwner) + ag_require.Equal(t, uint8(23), Instruction_AmountToUiAmount) + ag_require.Equal(t, uint8(24), Instruction_UiAmountToAmount) + ag_require.Equal(t, uint8(25), Instruction_InitializeMintCloseAuthority) + ag_require.Equal(t, uint8(26), Instruction_TransferFeeExtension) + ag_require.Equal(t, uint8(27), Instruction_ConfidentialTransferExtension) + ag_require.Equal(t, uint8(28), Instruction_DefaultAccountStateExtension) + ag_require.Equal(t, uint8(29), Instruction_Reallocate) + ag_require.Equal(t, uint8(30), Instruction_MemoTransferExtension) + ag_require.Equal(t, uint8(31), Instruction_CreateNativeMint) + ag_require.Equal(t, uint8(32), Instruction_InitializeNonTransferableMint) + ag_require.Equal(t, uint8(33), Instruction_InterestBearingMintExtension) + ag_require.Equal(t, uint8(34), Instruction_CpiGuardExtension) + ag_require.Equal(t, uint8(35), Instruction_InitializePermanentDelegate) + ag_require.Equal(t, uint8(36), Instruction_TransferHookExtension) + ag_require.Equal(t, uint8(37), Instruction_ConfidentialTransferFeeExtension) + ag_require.Equal(t, uint8(38), Instruction_WithdrawExcessLamports) + ag_require.Equal(t, uint8(39), Instruction_MetadataPointerExtension) + ag_require.Equal(t, uint8(40), Instruction_GroupPointerExtension) + ag_require.Equal(t, uint8(41), Instruction_GroupMemberPointerExtension) + ag_require.Equal(t, uint8(42), Instruction_ConfidentialMintBurnExtension) + ag_require.Equal(t, uint8(43), Instruction_ScaledUiAmountExtension) + ag_require.Equal(t, uint8(44), Instruction_PausableExtension) + ag_require.Equal(t, uint8(45), Instruction_UnwrapLamports) + ag_require.Equal(t, uint8(46), Instruction_PermissionedBurnExtension) +} diff --git a/programs/token-2022/instructions.go b/programs/token-2022/instructions.go index 620c1f43..f49bd8a6 100644 --- a/programs/token-2022/instructions.go +++ b/programs/token-2022/instructions.go @@ -134,6 +134,69 @@ const ( // Initialize the close account authority on a new mint. Instruction_InitializeMintCloseAuthority + + // Transfer fee extension instructions. + Instruction_TransferFeeExtension + + // Confidential transfer extension instructions. + Instruction_ConfidentialTransferExtension + + // Default account state extension instructions. + Instruction_DefaultAccountStateExtension + + // Reallocate an account to hold additional extensions. + Instruction_Reallocate + + // Memo transfer extension instructions. + Instruction_MemoTransferExtension + + // Create the native mint for Token-2022. + Instruction_CreateNativeMint + + // Initialize the non-transferable extension for a mint. + Instruction_InitializeNonTransferableMint + + // Interest-bearing mint extension instructions. + Instruction_InterestBearingMintExtension + + // CPI guard extension instructions. + Instruction_CpiGuardExtension + + // Initialize a permanent delegate for a mint. + Instruction_InitializePermanentDelegate + + // Transfer hook extension instructions. + Instruction_TransferHookExtension + + // Confidential transfer fee extension instructions. + Instruction_ConfidentialTransferFeeExtension + + // Withdraw excess lamports from an account. + Instruction_WithdrawExcessLamports + + // Metadata pointer extension instructions. + Instruction_MetadataPointerExtension + + // Group pointer extension instructions. + Instruction_GroupPointerExtension + + // Group member pointer extension instructions. + Instruction_GroupMemberPointerExtension + + // Confidential mint/burn extension instructions. + Instruction_ConfidentialMintBurnExtension + + // Scaled UI amount extension instructions. + Instruction_ScaledUiAmountExtension + + // Pausable extension instructions. + Instruction_PausableExtension + + // Unwrap native SOL lamports. + Instruction_UnwrapLamports + + // Permissioned burn extension instructions. + Instruction_PermissionedBurnExtension ) // InstructionIDToName returns the name of the instruction given its ID. @@ -191,6 +254,48 @@ func InstructionIDToName(id uint8) string { return "UiAmountToAmount" case Instruction_InitializeMintCloseAuthority: return "InitializeMintCloseAuthority" + case Instruction_TransferFeeExtension: + return "TransferFeeExtension" + case Instruction_ConfidentialTransferExtension: + return "ConfidentialTransferExtension" + case Instruction_DefaultAccountStateExtension: + return "DefaultAccountStateExtension" + case Instruction_Reallocate: + return "Reallocate" + case Instruction_MemoTransferExtension: + return "MemoTransferExtension" + case Instruction_CreateNativeMint: + return "CreateNativeMint" + case Instruction_InitializeNonTransferableMint: + return "InitializeNonTransferableMint" + case Instruction_InterestBearingMintExtension: + return "InterestBearingMintExtension" + case Instruction_CpiGuardExtension: + return "CpiGuardExtension" + case Instruction_InitializePermanentDelegate: + return "InitializePermanentDelegate" + case Instruction_TransferHookExtension: + return "TransferHookExtension" + case Instruction_ConfidentialTransferFeeExtension: + return "ConfidentialTransferFeeExtension" + case Instruction_WithdrawExcessLamports: + return "WithdrawExcessLamports" + case Instruction_MetadataPointerExtension: + return "MetadataPointerExtension" + case Instruction_GroupPointerExtension: + return "GroupPointerExtension" + case Instruction_GroupMemberPointerExtension: + return "GroupMemberPointerExtension" + case Instruction_ConfidentialMintBurnExtension: + return "ConfidentialMintBurnExtension" + case Instruction_ScaledUiAmountExtension: + return "ScaledUiAmountExtension" + case Instruction_PausableExtension: + return "PausableExtension" + case Instruction_UnwrapLamports: + return "UnwrapLamports" + case Instruction_PermissionedBurnExtension: + return "PermissionedBurnExtension" default: return "" } @@ -289,6 +394,69 @@ var InstructionImplDef = ag_binary.NewVariantDefinition( { "InitializeMintCloseAuthority", (*InitializeMintCloseAuthority)(nil), }, + { + "TransferFeeExtension", (*TransferFeeExtension)(nil), + }, + { + "ConfidentialTransferExtension", (*ConfidentialTransferExtension)(nil), + }, + { + "DefaultAccountStateExtension", (*DefaultAccountStateExtension)(nil), + }, + { + "Reallocate", (*Reallocate)(nil), + }, + { + "MemoTransferExtension", (*MemoTransferExtension)(nil), + }, + { + "CreateNativeMint", (*CreateNativeMint)(nil), + }, + { + "InitializeNonTransferableMint", (*InitializeNonTransferableMint)(nil), + }, + { + "InterestBearingMintExtension", (*InterestBearingMintExtension)(nil), + }, + { + "CpiGuardExtension", (*CpiGuardExtension)(nil), + }, + { + "InitializePermanentDelegate", (*InitializePermanentDelegate)(nil), + }, + { + "TransferHookExtension", (*TransferHookExtension)(nil), + }, + { + "ConfidentialTransferFeeExtension", (*ConfidentialTransferFeeExtension)(nil), + }, + { + "WithdrawExcessLamports", (*WithdrawExcessLamports)(nil), + }, + { + "MetadataPointerExtension", (*MetadataPointerExtension)(nil), + }, + { + "GroupPointerExtension", (*GroupPointerExtension)(nil), + }, + { + "GroupMemberPointerExtension", (*GroupMemberPointerExtension)(nil), + }, + { + "ConfidentialMintBurnExtension", (*ConfidentialMintBurnExtension)(nil), + }, + { + "ScaledUiAmountExtension", (*ScaledUiAmountExtension)(nil), + }, + { + "PausableExtension", (*PausableExtension)(nil), + }, + { + "UnwrapLamports", (*UnwrapLamports)(nil), + }, + { + "PermissionedBurnExtension", (*PermissionedBurnExtension)(nil), + }, }, )