From 88249c400cf77dfc1a6ba94d4614d7883364c68b Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Thu, 17 Jul 2025 10:41:34 -0400 Subject: [PATCH] token2022: add transfer-fee extension --- .../HarvestWithheldTokensToMint.go | 130 +++++++++ .../InitializeTransferFeeConfig.go | 193 +++++++++++++ .../extension/transfer-fee/SetTransferFee.go | 204 ++++++++++++++ .../transfer-fee/TransferCheckedWithFee.go | 253 ++++++++++++++++++ .../WithdrawWithheldTokensFromAccounts.go | 232 ++++++++++++++++ .../WithdrawWithheldTokensFromMint.go | 177 ++++++++++++ .../extension/transfer-fee/instructions.go | 184 +++++++++++++ 7 files changed, 1373 insertions(+) create mode 100644 programs/token/extension/transfer-fee/HarvestWithheldTokensToMint.go create mode 100644 programs/token/extension/transfer-fee/InitializeTransferFeeConfig.go create mode 100644 programs/token/extension/transfer-fee/SetTransferFee.go create mode 100644 programs/token/extension/transfer-fee/TransferCheckedWithFee.go create mode 100644 programs/token/extension/transfer-fee/WithdrawWithheldTokensFromAccounts.go create mode 100644 programs/token/extension/transfer-fee/WithdrawWithheldTokensFromMint.go create mode 100644 programs/token/extension/transfer-fee/instructions.go diff --git a/programs/token/extension/transfer-fee/HarvestWithheldTokensToMint.go b/programs/token/extension/transfer-fee/HarvestWithheldTokensToMint.go new file mode 100644 index 000000000..4f41adde4 --- /dev/null +++ b/programs/token/extension/transfer-fee/HarvestWithheldTokensToMint.go @@ -0,0 +1,130 @@ +package transferfee + +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" +) + +// HarvestWithheldTokensToMint is a permissionless instruction to transfer all withheld tokens to the mint. +// +// Succeeds for frozen accounts. +// Accounts provided should include the TransferFeeAmount extension. If not, the account is skipped. +// +// Accounts expected by this instruction: +// - [writable] The mint. +// - [writable] The source accounts to harvest from. +type HarvestWithheldTokensToMint struct { + // [0] = [WRITE] mint + // ··········· The mint. + // + // [1...] = [WRITE] source_accounts + // ··········· The source accounts to harvest from. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *HarvestWithheldTokensToMint) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + if len(accounts) < 1 { + return errors.New("not enough accounts for HarvestWithheldTokensToMint") + } + obj.Accounts = ag_solanago.AccountMetaSlice(accounts) + return nil +} + +func (slice HarvestWithheldTokensToMint) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + return +} + +func NewHarvestWithheldTokensToMintInstructionBuilder() *HarvestWithheldTokensToMint { + nd := &HarvestWithheldTokensToMint{ + Accounts: make(ag_solanago.AccountMetaSlice, 1), + } + return nd +} + +// SetMintAccount sets the "mint" account. +// The mint. +func (inst *HarvestWithheldTokensToMint) SetMintAccount(mint ag_solanago.PublicKey) *HarvestWithheldTokensToMint { + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// GetMintAccount gets the "mint" account. +// The mint. +func (inst *HarvestWithheldTokensToMint) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// AddSourceAccount adds a source account to harvest from. +// The source accounts to harvest from. +func (inst *HarvestWithheldTokensToMint) AddSourceAccount(source ag_solanago.PublicKey) *HarvestWithheldTokensToMint { + inst.Accounts = append(inst.Accounts, ag_solanago.Meta(source).WRITE()) + return inst +} + +func (inst HarvestWithheldTokensToMint) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_HarvestWithheldTokensToMint), + }} +} + +func (inst HarvestWithheldTokensToMint) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *HarvestWithheldTokensToMint) Validate() error { + // Check whether all (required) accounts are set: + { + if inst.Accounts[0] == nil { + return errors.New("accounts.Mint is not set") + } + if len(inst.Accounts) < 2 { + return errors.New("at least one source account must be provided") + } + } + return nil +} + +func (inst *HarvestWithheldTokensToMint) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("HarvestWithheldTokensToMint")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("mint", inst.Accounts[0])) + + sourceAccountsBranch := accountsBranch.Child(fmt.Sprintf("source_accounts[len=%v]", len(inst.Accounts)-1)) + for i, v := range inst.Accounts[1:] { + if len(inst.Accounts)-1 > 9 && i < 10 { + sourceAccountsBranch.Child(ag_format.Meta(fmt.Sprintf(" [%v]", i), v)) + } else { + sourceAccountsBranch.Child(ag_format.Meta(fmt.Sprintf("[%v]", i), v)) + } + } + }) + }) + }) +} + +func (obj HarvestWithheldTokensToMint) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // This instruction has no parameters to serialize + return nil +} + +func (obj *HarvestWithheldTokensToMint) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // This instruction has no parameters to deserialize + return nil +} diff --git a/programs/token/extension/transfer-fee/InitializeTransferFeeConfig.go b/programs/token/extension/transfer-fee/InitializeTransferFeeConfig.go new file mode 100644 index 000000000..e2c0d4d7e --- /dev/null +++ b/programs/token/extension/transfer-fee/InitializeTransferFeeConfig.go @@ -0,0 +1,193 @@ +package transferfee + +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" +) + +// InitializeTransferFeeConfig initializes the transfer fee on a new mint. +// +// Fails if the mint has already been initialized, so must be called before InitializeMint. +// The mint must have exactly enough space allocated for the base mint (82 bytes), +// plus 83 bytes of padding, 1 byte reserved for the account type, then space required for this extension, plus any others. +type InitializeTransferFeeConfig struct { + // Pubkey that may update the fees (COption) + TransferFeeConfigAuthority *ag_solanago.PublicKey + + // Withdraw instructions must be signed by this key (COption) + WithdrawWithheldAuthority *ag_solanago.PublicKey + + // Amount of transfer collected as fees, expressed as basis points of the transfer amount (u16) + TransferFeeBasisPoints *uint16 + + // Maximum fee assessed on transfers (u64) + MaximumFee *uint64 + + // [0] = [WRITE] mint + // ··········· The mint to initialize. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *InitializeTransferFeeConfig) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + if len(accounts) < 1 { + return errors.New("not enough accounts for InitializeTransferFeeConfig") + } + obj.Accounts = ag_solanago.AccountMetaSlice(accounts[:1]) + return nil +} + +func (slice InitializeTransferFeeConfig) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + return +} + +func NewInitializeTransferFeeConfigInstructionBuilder() *InitializeTransferFeeConfig { + nd := &InitializeTransferFeeConfig{ + Accounts: make(ag_solanago.AccountMetaSlice, 1), + } + return nd +} + +func (inst *InitializeTransferFeeConfig) SetTransferFeeConfigAuthority(authority ag_solanago.PublicKey) *InitializeTransferFeeConfig { + inst.TransferFeeConfigAuthority = &authority + return inst +} + +func (inst *InitializeTransferFeeConfig) SetWithdrawWithheldAuthority(authority ag_solanago.PublicKey) *InitializeTransferFeeConfig { + inst.WithdrawWithheldAuthority = &authority + return inst +} + +func (inst *InitializeTransferFeeConfig) SetTransferFeeBasisPoints(basisPoints uint16) *InitializeTransferFeeConfig { + inst.TransferFeeBasisPoints = &basisPoints + return inst +} + +func (inst *InitializeTransferFeeConfig) SetMaximumFee(maxFee uint64) *InitializeTransferFeeConfig { + inst.MaximumFee = &maxFee + return inst +} + +// SetMintAccount sets the "mint" account. +// The mint to initialize. +func (inst *InitializeTransferFeeConfig) SetMintAccount(mint ag_solanago.PublicKey) *InitializeTransferFeeConfig { + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// GetMintAccount gets the "mint" account. +// The mint to initialize. +func (inst *InitializeTransferFeeConfig) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +func (inst InitializeTransferFeeConfig) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_InitializeTransferFeeConfig), + }} +} + +func (inst InitializeTransferFeeConfig) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InitializeTransferFeeConfig) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.TransferFeeConfigAuthority == nil { + return errors.New("TransferFeeConfigAuthority parameter is not set") + } + if inst.WithdrawWithheldAuthority == nil { + return errors.New("WithdrawWithheldAuthority parameter is not set") + } + if inst.TransferFeeBasisPoints == nil { + return errors.New("TransferFeeBasisPoints parameter is not set") + } + if inst.MaximumFee == nil { + return errors.New("MaximumFee parameter is not set") + } + } + // Check whether all (required) accounts are set: + { + if inst.Accounts[0] == nil { + return errors.New("accounts.Mint is not set") + } + } + return nil +} + +func (inst *InitializeTransferFeeConfig) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InitializeTransferFeeConfig")). + ParentFunc(func(instructionBranch ag_treeout.Branches) { + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("TransferFeeConfigAuthority", inst.TransferFeeConfigAuthority)) + paramsBranch.Child(ag_format.Param("WithdrawWithheldAuthority", inst.WithdrawWithheldAuthority)) + paramsBranch.Child(ag_format.Param("TransferFeeBasisPoints", *inst.TransferFeeBasisPoints)) + paramsBranch.Child(ag_format.Param("MaximumFee", *inst.MaximumFee)) + }) + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("mint", inst.Accounts[0])) + }) + }) + }) +} + +func (obj InitializeTransferFeeConfig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `TransferFeeConfigAuthority` param: + err = encoder.Encode(obj.TransferFeeConfigAuthority) + if err != nil { + return err + } + // Serialize `WithdrawWithheldAuthority` param: + err = encoder.Encode(obj.WithdrawWithheldAuthority) + if err != nil { + return err + } + // Serialize `TransferFeeBasisPoints` param: + err = encoder.Encode(obj.TransferFeeBasisPoints) + if err != nil { + return err + } + // Serialize `MaximumFee` param: + err = encoder.Encode(obj.MaximumFee) + if err != nil { + return err + } + return nil +} + +func (obj *InitializeTransferFeeConfig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `TransferFeeConfigAuthority`: + err = decoder.Decode(&obj.TransferFeeConfigAuthority) + if err != nil { + return err + } + // Deserialize `WithdrawWithheldAuthority`: + err = decoder.Decode(&obj.WithdrawWithheldAuthority) + if err != nil { + return err + } + // Deserialize `TransferFeeBasisPoints`: + err = decoder.Decode(&obj.TransferFeeBasisPoints) + if err != nil { + return err + } + // Deserialize `MaximumFee`: + err = decoder.Decode(&obj.MaximumFee) + if err != nil { + return err + } + return nil +} diff --git a/programs/token/extension/transfer-fee/SetTransferFee.go b/programs/token/extension/transfer-fee/SetTransferFee.go new file mode 100644 index 000000000..a0b16ec8b --- /dev/null +++ b/programs/token/extension/transfer-fee/SetTransferFee.go @@ -0,0 +1,204 @@ +package transferfee + +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" +) + +// SetTransferFee sets the transfer fee. Only supported for mints that include the TransferFeeConfig extension. +// +// Single authority: +// - [writable] The mint. +// - [signer] The mint's fee account owner. +// +// Multisignature authority: +// - [writable] The mint. +// - [] The mint's multisignature fee account owner. +// - [signer] M signer accounts. +type SetTransferFee struct { + // Amount of transfer collected as fees, expressed as basis points of the transfer amount + TransferFeeBasisPoints *uint16 + + // Maximum fee assessed on transfers + MaximumFee *uint64 + + // [0] = [WRITE] mint + // ··········· The mint. + // + // [1] = [] authority + // ··········· The mint's fee account owner. + // + // [2...] = [SIGNER] signers + // ··········· M signer accounts (for multisig). + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *SetTransferFee) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + return nil +} + +func (slice SetTransferFee) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func NewSetTransferFeeInstructionBuilder() *SetTransferFee { + nd := &SetTransferFee{ + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +func (inst *SetTransferFee) SetTransferFeeBasisPoints(basisPoints uint16) *SetTransferFee { + inst.TransferFeeBasisPoints = &basisPoints + return inst +} + +func (inst *SetTransferFee) SetMaximumFee(maxFee uint64) *SetTransferFee { + inst.MaximumFee = &maxFee + return inst +} + +// SetMintAccount sets the "mint" account. +// The mint. +func (inst *SetTransferFee) SetMintAccount(mint ag_solanago.PublicKey) *SetTransferFee { + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// GetMintAccount gets the "mint" account. +// The mint. +func (inst *SetTransferFee) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetAuthorityAccount sets the "authority" account. +// The mint's fee account owner. +func (inst *SetTransferFee) SetAuthorityAccount(authority ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *SetTransferFee { + 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 +} + +// GetAuthorityAccount gets the "authority" account. +// The mint's fee account owner. +func (inst *SetTransferFee) GetAuthorityAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +func (inst SetTransferFee) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_SetTransferFee), + }} +} + +func (inst SetTransferFee) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *SetTransferFee) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.TransferFeeBasisPoints == nil { + return errors.New("TransferFeeBasisPoints parameter is not set") + } + if inst.MaximumFee == nil { + return errors.New("MaximumFee parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.Accounts[0] == nil { + return errors.New("accounts.Mint is not set") + } + if 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") + } + 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 *SetTransferFee) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("SetTransferFee")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("TransferFeeBasisPoints", *inst.TransferFeeBasisPoints)) + paramsBranch.Child(ag_format.Param("MaximumFee", *inst.MaximumFee)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" mint", inst.Accounts[0])) + accountsBranch.Child(ag_format.Meta(" authority", inst.Accounts[1])) + + signersBranch := accountsBranch.Child(fmt.Sprintf("signers[len=%v]", len(inst.Signers))) + for i, v := range inst.Signers { + if len(inst.Signers) > 9 && i < 10 { + signersBranch.Child(ag_format.Meta(fmt.Sprintf(" [%v]", i), v)) + } else { + signersBranch.Child(ag_format.Meta(fmt.Sprintf("[%v]", i), v)) + } + } + }) + }) + }) +} + +func (obj SetTransferFee) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `TransferFeeBasisPoints` param: + err = encoder.Encode(obj.TransferFeeBasisPoints) + if err != nil { + return err + } + // Serialize `MaximumFee` param: + err = encoder.Encode(obj.MaximumFee) + if err != nil { + return err + } + return nil +} + +func (obj *SetTransferFee) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `TransferFeeBasisPoints`: + err = decoder.Decode(&obj.TransferFeeBasisPoints) + if err != nil { + return err + } + // Deserialize `MaximumFee`: + err = decoder.Decode(&obj.MaximumFee) + if err != nil { + return err + } + return nil +} diff --git a/programs/token/extension/transfer-fee/TransferCheckedWithFee.go b/programs/token/extension/transfer-fee/TransferCheckedWithFee.go new file mode 100644 index 000000000..8a894d77e --- /dev/null +++ b/programs/token/extension/transfer-fee/TransferCheckedWithFee.go @@ -0,0 +1,253 @@ +package transferfee + +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" +) + +// Transfers tokens from one account to another either directly or via a +// delegate. If this account is associated with the native mint then equal +// amounts of SOL and Tokens will be transferred to the destination +// account. +// +// This instruction differs from Transfer in that the token mint and +// decimals value is checked by the caller. This may be useful when +// creating transactions offline or within a hardware wallet. +type TransferCheckedWithFee struct { + // The amount of tokens to transfer. + Amount *uint64 + + // Expected number of base 10 digits to the right of the decimal place. + Decimals *uint8 + + // Expected fee assessed on this transfer, calculated off-chain based on the transfer_fee_basis_points and maximum_fee of the mint. + // May be 0 for a mint without a configured transfer fee. + Fee *uint64 + + // [0] = [WRITE] source + // ··········· The source account. + // + // [1] = [] mint + // ··········· The token mint. + // + // [2] = [WRITE] destination + // ··········· The destination account. + // + // [3] = [] owner + // ··········· The source account's owner/delegate. + // + // [4...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *TransferCheckedWithFee) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(4) + return nil +} + +func (slice TransferCheckedWithFee) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func NewTransferCheckedWithFeeInstructionBuilder() *TransferCheckedWithFee { + nd := &TransferCheckedWithFee{ + Accounts: make(ag_solanago.AccountMetaSlice, 4), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +func (inst *TransferCheckedWithFee) SetAmount(amount uint64) *TransferCheckedWithFee { + inst.Amount = &amount + return inst +} + +func (inst *TransferCheckedWithFee) SetDecimals(decimals uint8) *TransferCheckedWithFee { + inst.Decimals = &decimals + return inst +} + +func (inst *TransferCheckedWithFee) SetFee(fee uint64) *TransferCheckedWithFee { + inst.Fee = &fee + return inst +} + +// SetSourceAccount sets the "source" account. +// The source account. +func (inst *TransferCheckedWithFee) SetSourceAccount(source ag_solanago.PublicKey) *TransferCheckedWithFee { + inst.Accounts[0] = ag_solanago.Meta(source).WRITE() + return inst +} + +// GetSourceAccount gets the "source" account. +// The source account. +func (inst *TransferCheckedWithFee) GetSourceAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetMintAccount sets the "mint" account. +// The token mint. +func (inst *TransferCheckedWithFee) SetMintAccount(mint ag_solanago.PublicKey) *TransferCheckedWithFee { + inst.Accounts[1] = ag_solanago.Meta(mint) + return inst +} + +// GetMintAccount gets the "mint" account. +// The token mint. +func (inst *TransferCheckedWithFee) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +func (inst *TransferCheckedWithFee) SetDestinationAccount(destination ag_solanago.PublicKey) *TransferCheckedWithFee { + inst.Accounts[2] = ag_solanago.Meta(destination).WRITE() + return inst +} + +func (inst *TransferCheckedWithFee) GetDestinationAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst *TransferCheckedWithFee) SetOwnerAccount(owner ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *TransferCheckedWithFee { + 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 *TransferCheckedWithFee) GetOwnerAccount() *ag_solanago.AccountMeta { + return inst.Accounts[3] +} + +func (inst TransferCheckedWithFee) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_TransferCheckedWithFee), + }} +} + +func (inst TransferCheckedWithFee) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *TransferCheckedWithFee) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Amount == nil { + return errors.New("Amount parameter is not set") + } + if inst.Decimals == nil { + return errors.New("Decimals parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.Accounts[0] == nil { + return errors.New("accounts.Source is not set") + } + if inst.Accounts[1] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.Accounts[2] == nil { + return errors.New("accounts.Destination 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 *TransferCheckedWithFee) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("TransferCheckedWithFee")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param(" Amount", *inst.Amount)) + paramsBranch.Child(ag_format.Param("Decimals", *inst.Decimals)) + paramsBranch.Child(ag_format.Param(" Fee", *inst.Fee)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" source", inst.Accounts[0])) + accountsBranch.Child(ag_format.Meta(" mint", inst.Accounts[1])) + accountsBranch.Child(ag_format.Meta("destination", inst.Accounts[2])) + accountsBranch.Child(ag_format.Meta(" owner", inst.Accounts[3])) + + signersBranch := accountsBranch.Child(fmt.Sprintf("signers[len=%v]", len(inst.Signers))) + for i, v := range inst.Signers { + if len(inst.Signers) > 9 && i < 10 { + signersBranch.Child(ag_format.Meta(fmt.Sprintf(" [%v]", i), v)) + } else { + signersBranch.Child(ag_format.Meta(fmt.Sprintf("[%v]", i), v)) + } + } + }) + }) + }) +} + +func (obj TransferCheckedWithFee) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `Amount` param: + err = encoder.Encode(obj.Amount) + if err != nil { + return err + } + // Serialize `Decimals` param: + err = encoder.Encode(obj.Decimals) + if err != nil { + return err + } + // Serialize `Fee` param: + err = encoder.Encode(obj.Fee) + if err != nil { + return err + } + return nil +} +func (obj *TransferCheckedWithFee) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `Amount`: + err = decoder.Decode(&obj.Amount) + if err != nil { + return err + } + // Deserialize `Decimals`: + err = decoder.Decode(&obj.Decimals) + if err != nil { + return err + } + // Deserialize `Fee`: + err = decoder.Decode(&obj.Fee) + if err != nil { + return err + } + return nil +} diff --git a/programs/token/extension/transfer-fee/WithdrawWithheldTokensFromAccounts.go b/programs/token/extension/transfer-fee/WithdrawWithheldTokensFromAccounts.go new file mode 100644 index 000000000..27f4ac0d6 --- /dev/null +++ b/programs/token/extension/transfer-fee/WithdrawWithheldTokensFromAccounts.go @@ -0,0 +1,232 @@ +package transferfee + +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" +) + +// WithdrawWithheldTokensFromAccounts transfers all withheld tokens to an account. +// Signed by the mint's withdraw withheld tokens authority. +// +// Single owner/delegate: +// - [] The token mint. Must include the TransferFeeConfig extension. +// - [writable] The fee receiver account. Must include the TransferFeeAmount extension and be associated with the provided mint. +// - [signer] The mint's withdraw_withheld_authority. +// - [writable] The source accounts to withdraw from. +// +// Multisignature owner/delegate: +// - [] The token mint. +// - [writable] The destination account. +// - [] The mint's multisig withdraw_withheld_authority. +// - [signer] M signer accounts. +// - [writable] The source accounts to withdraw from. +type WithdrawWithheldTokensFromAccounts struct { + // Number of token accounts harvested + NumTokenAccounts *uint8 + + // [0] = [] mint + // ··········· The token mint. Must include the TransferFeeConfig extension. + // + // [1] = [WRITE] destination + // ··········· The fee receiver account (single owner) or destination account (multisig). + // + // [2] = [] authority + // ··········· The mint's withdraw_withheld_authority. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts (for multisig). + // + // [3+M...] = [WRITE] source_accounts + // ··········· The source accounts to withdraw from. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *WithdrawWithheldTokensFromAccounts) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice WithdrawWithheldTokensFromAccounts) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func NewWithdrawWithheldTokensFromAccountsInstructionBuilder() *WithdrawWithheldTokensFromAccounts { + nd := &WithdrawWithheldTokensFromAccounts{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +func (inst *WithdrawWithheldTokensFromAccounts) SetNumTokenAccounts(numAccounts uint8) *WithdrawWithheldTokensFromAccounts { + inst.NumTokenAccounts = &numAccounts + return inst +} + +// SetMintAccount sets the "mint" account. +// The token mint. Must include the TransferFeeConfig extension. +func (inst *WithdrawWithheldTokensFromAccounts) SetMintAccount(mint ag_solanago.PublicKey) *WithdrawWithheldTokensFromAccounts { + inst.Accounts[0] = ag_solanago.Meta(mint) + return inst +} + +// GetMintAccount gets the "mint" account. +// The token mint. Must include the TransferFeeConfig extension. +func (inst *WithdrawWithheldTokensFromAccounts) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetDestinationAccount sets the "destination" account. +// The fee receiver account (single owner) or destination account (multisig). +func (inst *WithdrawWithheldTokensFromAccounts) SetDestinationAccount(destination ag_solanago.PublicKey) *WithdrawWithheldTokensFromAccounts { + inst.Accounts[1] = ag_solanago.Meta(destination).WRITE() + return inst +} + +// GetDestinationAccount gets the "destination" account. +// The fee receiver account (single owner) or destination account (multisig). +func (inst *WithdrawWithheldTokensFromAccounts) GetDestinationAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetAuthorityAccount sets the "authority" account. +// The mint's withdraw_withheld_authority. +func (inst *WithdrawWithheldTokensFromAccounts) SetAuthorityAccount(authority ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *WithdrawWithheldTokensFromAccounts { + 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 +} + +// GetAuthorityAccount gets the "authority" account. +// The mint's withdraw_withheld_authority. +func (inst *WithdrawWithheldTokensFromAccounts) GetAuthorityAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +// AddSourceAccount adds a source account to withdraw from. +// The source accounts to withdraw from. +func (inst *WithdrawWithheldTokensFromAccounts) AddSourceAccount(source ag_solanago.PublicKey) *WithdrawWithheldTokensFromAccounts { + inst.Accounts = append(inst.Accounts, ag_solanago.Meta(source).WRITE()) + return inst +} + +func (inst WithdrawWithheldTokensFromAccounts) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_WithdrawWithheldTokensFromAccounts), + }} +} + +func (inst WithdrawWithheldTokensFromAccounts) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *WithdrawWithheldTokensFromAccounts) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.NumTokenAccounts == nil { + return errors.New("NumTokenAccounts parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.Accounts[0] == nil { + return errors.New("accounts.Mint 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)) + } + // Check that we have the expected number of source accounts + expectedSourceAccounts := int(*inst.NumTokenAccounts) + actualSourceAccounts := len(inst.Accounts) - 3 + if actualSourceAccounts != expectedSourceAccounts { + return fmt.Errorf("expected %v source accounts, got %v", expectedSourceAccounts, actualSourceAccounts) + } + } + return nil +} + +func (inst *WithdrawWithheldTokensFromAccounts) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("WithdrawWithheldTokensFromAccounts")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("NumTokenAccounts", *inst.NumTokenAccounts)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" mint", inst.Accounts[0])) + accountsBranch.Child(ag_format.Meta("destination", inst.Accounts[1])) + accountsBranch.Child(ag_format.Meta(" authority", inst.Accounts[2])) + + signersBranch := accountsBranch.Child(fmt.Sprintf("signers[len=%v]", len(inst.Signers))) + for i, v := range inst.Signers { + if len(inst.Signers) > 9 && i < 10 { + signersBranch.Child(ag_format.Meta(fmt.Sprintf(" [%v]", i), v)) + } else { + signersBranch.Child(ag_format.Meta(fmt.Sprintf("[%v]", i), v)) + } + } + + sourceAccountsBranch := accountsBranch.Child(fmt.Sprintf("source_accounts[len=%v]", len(inst.Accounts)-3)) + for i, v := range inst.Accounts[3:] { + if len(inst.Accounts)-3 > 9 && i < 10 { + sourceAccountsBranch.Child(ag_format.Meta(fmt.Sprintf(" [%v]", i), v)) + } else { + sourceAccountsBranch.Child(ag_format.Meta(fmt.Sprintf("[%v]", i), v)) + } + } + }) + }) + }) +} + +func (obj WithdrawWithheldTokensFromAccounts) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `NumTokenAccounts` param: + err = encoder.Encode(obj.NumTokenAccounts) + if err != nil { + return err + } + return nil +} + +func (obj *WithdrawWithheldTokensFromAccounts) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `NumTokenAccounts`: + err = decoder.Decode(&obj.NumTokenAccounts) + if err != nil { + return err + } + return nil +} diff --git a/programs/token/extension/transfer-fee/WithdrawWithheldTokensFromMint.go b/programs/token/extension/transfer-fee/WithdrawWithheldTokensFromMint.go new file mode 100644 index 000000000..b6ee5b30e --- /dev/null +++ b/programs/token/extension/transfer-fee/WithdrawWithheldTokensFromMint.go @@ -0,0 +1,177 @@ +package transferfee + +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" +) + +// WithdrawWithheldTokensFromMint transfers all withheld tokens in the mint to an account. +// Signed by the mint's withdraw withheld tokens authority. +// +// Single owner/delegate: +// - [writable] The token mint. Must include the TransferFeeConfig extension. +// - [writable] The fee receiver account. Must include the TransferFeeAmount extension associated with the provided mint. +// - [signer] The mint's withdraw_withheld_authority. +// +// Multisignature owner/delegate: +// - [writable] The token mint. +// - [writable] The destination account. +// - [] The mint's multisig withdraw_withheld_authority. +// - [signer] M signer accounts. +type WithdrawWithheldTokensFromMint struct { + // [0] = [WRITE] mint + // ··········· The token mint. Must include the TransferFeeConfig extension. + // + // [1] = [WRITE] destination + // ··········· The fee receiver account (single owner) or destination account (multisig). + // + // [2] = [] authority + // ··········· The mint's withdraw_withheld_authority. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts (for multisig). + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *WithdrawWithheldTokensFromMint) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice WithdrawWithheldTokensFromMint) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +func NewWithdrawWithheldTokensFromMintInstructionBuilder() *WithdrawWithheldTokensFromMint { + nd := &WithdrawWithheldTokensFromMint{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetMintAccount sets the "mint" account. +// The token mint. Must include the TransferFeeConfig extension. +func (inst *WithdrawWithheldTokensFromMint) SetMintAccount(mint ag_solanago.PublicKey) *WithdrawWithheldTokensFromMint { + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// GetMintAccount gets the "mint" account. +// The token mint. Must include the TransferFeeConfig extension. +func (inst *WithdrawWithheldTokensFromMint) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetDestinationAccount sets the "destination" account. +// The fee receiver account (single owner) or destination account (multisig). +func (inst *WithdrawWithheldTokensFromMint) SetDestinationAccount(destination ag_solanago.PublicKey) *WithdrawWithheldTokensFromMint { + inst.Accounts[1] = ag_solanago.Meta(destination).WRITE() + return inst +} + +// GetDestinationAccount gets the "destination" account. +// The fee receiver account (single owner) or destination account (multisig). +func (inst *WithdrawWithheldTokensFromMint) GetDestinationAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetAuthorityAccount sets the "authority" account. +// The mint's withdraw_withheld_authority. +func (inst *WithdrawWithheldTokensFromMint) SetAuthorityAccount(authority ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *WithdrawWithheldTokensFromMint { + 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 +} + +// GetAuthorityAccount gets the "authority" account. +// The mint's withdraw_withheld_authority. +func (inst *WithdrawWithheldTokensFromMint) GetAuthorityAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst WithdrawWithheldTokensFromMint) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_WithdrawWithheldTokensFromMint), + }} +} + +func (inst WithdrawWithheldTokensFromMint) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *WithdrawWithheldTokensFromMint) Validate() error { + // Check whether all (required) accounts are set: + { + if inst.Accounts[0] == nil { + return errors.New("accounts.Mint 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 *WithdrawWithheldTokensFromMint) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("WithdrawWithheldTokensFromMint")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" mint", inst.Accounts[0])) + accountsBranch.Child(ag_format.Meta("destination", inst.Accounts[1])) + accountsBranch.Child(ag_format.Meta(" authority", inst.Accounts[2])) + + signersBranch := accountsBranch.Child(fmt.Sprintf("signers[len=%v]", len(inst.Signers))) + for i, v := range inst.Signers { + if len(inst.Signers) > 9 && i < 10 { + signersBranch.Child(ag_format.Meta(fmt.Sprintf(" [%v]", i), v)) + } else { + signersBranch.Child(ag_format.Meta(fmt.Sprintf("[%v]", i), v)) + } + } + }) + }) + }) +} + +func (obj WithdrawWithheldTokensFromMint) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // This instruction has no parameters to serialize + return nil +} + +func (obj *WithdrawWithheldTokensFromMint) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // This instruction has no parameters to deserialize + return nil +} diff --git a/programs/token/extension/transfer-fee/instructions.go b/programs/token/extension/transfer-fee/instructions.go new file mode 100644 index 000000000..70c172ba4 --- /dev/null +++ b/programs/token/extension/transfer-fee/instructions.go @@ -0,0 +1,184 @@ +package transferfee + +// Copyright 2021 github.com/gagliardetto +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import ( + "bytes" + "fmt" + + ag_spew "github.com/davecgh/go-spew/spew" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_text "github.com/gagliardetto/solana-go/text" + ag_treeout "github.com/gagliardetto/treeout" +) + +const MAX_SIGNERS = 11 + +const SharedInstructionPrefix uint8 = 26 + +var ProgramID ag_solanago.PublicKey = ag_solanago.Token2022ProgramID + +func SetProgramID(pubkey ag_solanago.PublicKey) { + ProgramID = pubkey + ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) +} + +const ProgramName = "Token2022" + +func init() { + if !ProgramID.IsZero() { + ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) + } +} + +const ( + // Initialize the transfer fee on a new mint. + // Fails if the mint has already been initialized, so must be called before InitializeMint. + // The mint must have exactly enough space allocated for the base mint (82 bytes), + // plus 83 bytes of padding, 1 byte reserved for the account type, then space required for this extension, plus any others. + Instruction_InitializeTransferFeeConfig uint8 = iota + + // Transfer, providing expected mint information and fees + // This instruction succeeds if the mint has no configured transfer fee and the provided fee is 0. + // This allows applications to use TransferCheckedWithFee with any mint. + Instruction_TransferCheckedWithFee + + // Transfer all withheld tokens in the mint to an account. Signed by the mint’s withdraw withheld tokens authority. + Instruction_WithdrawWithheldTokensFromMint + + // Transfer all withheld tokens to an account. Signed by the mint’s withdraw withheld tokens authority. + Instruction_WithdrawWithheldTokensFromAccounts + + // Permissionless instruction to transfer all withheld tokens to the mint. + Instruction_HarvestWithheldTokensToMint + + // Set transfer fee. Only supported for mints that include the TransferFeeConfig extension. + Instruction_SetTransferFee +) + +// InstructionIDToName returns the name of the instruction given its ID. +func InstructionIDToName(id uint8) string { + switch id { + case Instruction_InitializeTransferFeeConfig: + return "InitializeTransferFeeConfig" + case Instruction_TransferCheckedWithFee: + return "TransferCheckedWithFee" + case Instruction_WithdrawWithheldTokensFromMint: + return "WithdrawWithheldTokensFromMint" + case Instruction_WithdrawWithheldTokensFromAccounts: + return "WithdrawWithheldTokensFromAccounts" + case Instruction_HarvestWithheldTokensToMint: + return "HarvestWithheldTokensToMint" + case Instruction_SetTransferFee: + return "SetTransferFee" + default: + return "" + } +} + +type Instruction struct { + ag_binary.BaseVariant +} + +func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) { + if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok { + enToTree.EncodeToTree(parent) + } else { + parent.Child(ag_spew.Sdump(inst)) + } +} + +var InstructionImplDef = ag_binary.NewVariantDefinition( + ag_binary.Uint8TypeIDEncoding, + []ag_binary.VariantType{ + { + "InitializeTransferFeeConfig", (*InitializeTransferFeeConfig)(nil), + }, + { + "TransferCheckedWithFee", (*TransferCheckedWithFee)(nil), + }, + { + "WithdrawWithheldTokensFromMint", (*WithdrawWithheldTokensFromMint)(nil), + }, + { + "WithdrawWithheldTokensFromAccounts", (*WithdrawWithheldTokensFromAccounts)(nil), + }, + { + "HarvestWithheldTokensToMint", (*HarvestWithheldTokensToMint)(nil), + }, + { + "SetTransferFee", (*SetTransferFee)(nil), + }, + }, +) + +func (inst *Instruction) ProgramID() ag_solanago.PublicKey { + return ProgramID +} + +func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) { + return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts() +} + +func (inst *Instruction) Data() ([]byte, error) { + buf := new(bytes.Buffer) + if err := ag_binary.NewBinEncoder(buf).Encode(inst); err != nil { + return nil, fmt.Errorf("unable to encode instruction: %w", err) + } + return buf.Bytes(), nil +} + +func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error { + return encoder.Encode(inst.Impl, option) +} + +func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef) +} + +func (inst Instruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error { + err := encoder.WriteUint8(inst.TypeID.Uint8()) + if err != nil { + return fmt.Errorf("unable to write variant type: %w", err) + } + return encoder.Encode(inst.Impl) +} + +func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) { + inst, err := DecodeInstruction(accounts, data) + if err != nil { + return nil, err + } + return inst, nil +} + +func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) { + inst := new(Instruction) + if data[0] != SharedInstructionPrefix { + return nil, fmt.Errorf("unexpected instruction prefix for transfer-fee-extension: %d", data[0]) + } + data = data[1:] + if err := ag_binary.NewBinDecoder(data).Decode(inst); err != nil { + return nil, fmt.Errorf("unable to decode instruction: %w", err) + } + if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok { + err := v.SetAccounts(accounts) + if err != nil { + return nil, fmt.Errorf("unable to set accounts for instruction: %w", err) + } + } + return inst, nil +}