diff --git a/README.md b/README.md index 5adb6bd4..1a817088 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Solana SDK library for Go -[![GoDoc](https://pkg.go.dev/badge/github.com/gagliardetto/solana-go?status.svg)](https://pkg.go.dev/github.com/gagliardetto/solana-go@v0.4.3?tab=doc) +[![GoDoc](https://pkg.go.dev/badge/github.com/gagliardetto/solana-go?status.svg)](https://pkg.go.dev/github.com/gagliardetto/solana-go@v0.4.4?tab=doc) [![GitHub tag (latest SemVer pre-release)](https://img.shields.io/github/v/tag/gagliardetto/solana-go?include_prereleases&label=release-tag)](https://github.com/gagliardetto/solana-go/releases) [![Build Status](https://github.com/gagliardetto/solana-go/workflows/tests/badge.svg?branch=main)](https://github.com/gagliardetto/solana-go/actions?query=branch%3Amain) [![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/gagliardetto/solana-go/main)](https://www.tickgit.com/browse?repo=github.com/gagliardetto/solana-go&branch=main) @@ -41,13 +41,23 @@ More contracts to come. - [x] Full WebSocket JSON streaming API - [ ] Wallet, account, and keys management - [ ] Clients for native programs -- [ ] Clients for Solana Program Library + - [x] system + - [ ] config + - [ ] stake + - [ ] vote + - [ ] BPF Loader + - [ ] Secp256k1 +- [ ] Clients for Solana Program Library (SPL) + - [ ] token: WIP + - [ ] memo + - [ ] name-service + - [ ] ... - [ ] Client for Serum - [ ] More programs ## Current development status -There is currently **no stable release**. The SDK is actively developed and latest is `v0.4.3` which is an `alpha` release. +There is currently **no stable release**. The SDK is actively developed and latest is `v0.4.4` which is an `alpha` release. The RPC and WS client implementation is based on [this RPC spec](https://github.com/solana-labs/solana/blob/dff9c88193da142693cabebfcd3bf68fa8e8b873/docs/src/developing/clients/jsonrpc-api.md). @@ -61,7 +71,7 @@ The RPC and WS client implementation is based on [this RPC spec](https://github. ```bash $ cd my-project -$ go get github.com/gagliardetto/solana-go@v0.4.3 +$ go get github.com/gagliardetto/solana-go@v0.4.4 $ go get github.com/gagliardetto/binary@v0.4.1 $ go get github.com/gagliardetto/gofuzz@v1.2.2 ``` diff --git a/account.go b/account.go index fdb8485c..a37ddf24 100644 --- a/account.go +++ b/account.go @@ -110,3 +110,39 @@ func (slice AccountMetaSlice) GetSigners() []*AccountMeta { } return signers } + +func (slice AccountMetaSlice) Len() int { + return len(slice) +} + +func (slice AccountMetaSlice) SplitFrom(index int) (AccountMetaSlice, AccountMetaSlice) { + if index < 0 { + panic("negative index") + } + if index == 0 { + return AccountMetaSlice{}, slice + } + if index > len(slice)-1 { + return slice, AccountMetaSlice{} + } + + firstLen, secondLen := calcSplitAtLengths(len(slice), index) + + first := make(AccountMetaSlice, firstLen) + copy(first, slice[:index]) + + second := make(AccountMetaSlice, secondLen) + copy(second, slice[index:]) + + return first, second +} + +func calcSplitAtLengths(total int, index int) (int, int) { + if index == 0 { + return 0, total + } + if index > total-1 { + return total, 0 + } + return index, total - index +} diff --git a/account_test.go b/account_test.go index 1b4361a4..95de56dc 100644 --- a/account_test.go +++ b/account_test.go @@ -140,3 +140,64 @@ func TestMeta(t *testing.T) { require.True(t, meta.IsSigner) require.True(t, meta.IsWritable) } + +func TestSplitFrom(t *testing.T) { + slice := make(AccountMetaSlice, 0) + slice = append(slice, Meta(BPFLoaderDeprecatedProgramID)) + slice = append(slice, Meta(TokenProgramID)) + slice = append(slice, Meta(TokenLendingProgramID)) + slice = append(slice, Meta(SPLAssociatedTokenAccountProgramID)) + slice = append(slice, Meta(MemoProgramID)) + + require.Len(t, slice, 5) + + { + part1, part2 := slice.SplitFrom(0) + require.Len(t, part1, 0) + require.Len(t, part2, 5) + } + { + part1, part2 := slice.SplitFrom(1) + require.Len(t, part1, 1) + require.Len(t, part2, 4) + require.Equal(t, Meta(BPFLoaderDeprecatedProgramID), part1[0]) + require.Equal(t, Meta(TokenProgramID), part2[0]) + require.Equal(t, Meta(TokenLendingProgramID), part2[1]) + require.Equal(t, Meta(SPLAssociatedTokenAccountProgramID), part2[2]) + require.Equal(t, Meta(MemoProgramID), part2[3]) + } + { + part1, part2 := slice.SplitFrom(2) + require.Len(t, part1, 2) + require.Len(t, part2, 3) + } + { + part1, part2 := slice.SplitFrom(3) + require.Len(t, part1, 3) + require.Len(t, part2, 2) + } + { + part1, part2 := slice.SplitFrom(4) + require.Len(t, part1, 4) + require.Len(t, part2, 1) + } + { + part1, part2 := slice.SplitFrom(5) + require.Len(t, part1, 5) + require.Len(t, part2, 0) + } + { + part1, part2 := slice.SplitFrom(6) + require.Len(t, part1, 5) + require.Len(t, part2, 0) + } + { + part1, part2 := slice.SplitFrom(10000) + require.Len(t, part1, 5) + require.Len(t, part2, 0) + } + require.Panics(t, + func() { + slice.SplitFrom(-1) + }) +} diff --git a/cmd/slnc/cmd/token_get_mint.go b/cmd/slnc/cmd/token_get_mint.go index 2e65488a..18e1555a 100644 --- a/cmd/slnc/cmd/token_get_mint.go +++ b/cmd/slnc/cmd/token_get_mint.go @@ -57,13 +57,13 @@ var tokenGetMintCmd = &cobra.Command{ out = append(out, fmt.Sprintf("Supply | %d", mint.Supply)) out = append(out, fmt.Sprintf("Decimals | %d", mint.Decimals)) - if mint.MintAuthorityOption != 0 { + if mint.MintAuthority != nil { out = append(out, fmt.Sprintf("Token Authority | %s", mint.MintAuthority)) } else { out = append(out, "No mint authority") } - if mint.FreezeAuthorityOption != 0 { + if mint.FreezeAuthority != nil { out = append(out, fmt.Sprintf("Freeze Authority | %s", mint.FreezeAuthority)) } else { out = append(out, "No freeze authority") diff --git a/cmd/slnc/cmd/token_list_mints.go b/cmd/slnc/cmd/token_list_mints.go index 246f23e6..a4281d78 100644 --- a/cmd/slnc/cmd/token_list_mints.go +++ b/cmd/slnc/cmd/token_list_mints.go @@ -36,16 +36,15 @@ var tokenListMintsCmd = &cobra.Command{ out := []string{"Mint | Decimals | Supply | Token Authority | Freeze Authority"} for _, m := range mints { line := []string{ - fmt.Sprintf("%d", m.MintAuthorityOption), fmt.Sprintf("%d", m.Supply), fmt.Sprintf("%d", m.Decimals), } - if m.MintAuthorityOption != 0 { + if m.MintAuthority != nil { line = append(line, fmt.Sprintf("%s", m.MintAuthority)) } else { line = append(line, "No mint authority") } - if m.FreezeAuthorityOption != 0 { + if m.FreezeAuthority != nil { line = append(line, fmt.Sprintf("%s", m.FreezeAuthority)) } else { line = append(line, "No freeze authority") diff --git a/go.mod b/go.mod index 2adc5bc8..41da33d6 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/fatih/color v1.7.0 github.com/gagliardetto/binary v0.4.1 github.com/gagliardetto/gofuzz v1.2.2 - github.com/gagliardetto/treeout v0.1.2 + github.com/gagliardetto/treeout v0.1.4 github.com/google/go-cmp v0.5.1 github.com/gorilla/rpc v1.2.0 github.com/gorilla/websocket v1.4.2 diff --git a/go.sum b/go.sum index ba847950..a3f543b2 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/gagliardetto/binary v0.4.1 h1:3HXeBOfmR2HHIrF1YBnoqgFKQBtN8+rWA770KyX github.com/gagliardetto/binary v0.4.1/go.mod h1:peJR9PvwamL4YOh1nHWCPLry2VEfeeD1ADvewka7HnQ= github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY= -github.com/gagliardetto/treeout v0.1.2 h1:WXO7LDJTwINO37OQfNlf7s095Z1bAiwN2ACaZQic33Q= -github.com/gagliardetto/treeout v0.1.2/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok= +github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw= +github.com/gagliardetto/treeout v0.1.4/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= diff --git a/keys.go b/keys.go index dcc5351e..4b1e58f4 100644 --- a/keys.go +++ b/keys.go @@ -332,7 +332,7 @@ func findAssociatedTokenAddressAndBumpSeed( ) (PublicKey, uint8, error) { return FindProgramAddress([][]byte{ walletAddress[:], - SPLTokenProgramID[:], + TokenProgramID[:], splTokenMintAddress[:], }, programID, diff --git a/program_ids.go b/program_ids.go index a37a62fd..13aaaf97 100644 --- a/program_ids.go +++ b/program_ids.go @@ -29,15 +29,15 @@ var ( var ( // A Token program on the Solana blockchain. // This program defines a common implementation for Fungible and Non Fungible tokens. - SPLTokenProgramID = MustPublicKeyFromBase58("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") + TokenProgramID = MustPublicKeyFromBase58("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") // A Uniswap-like exchange for the Token program on the Solana blockchain, // implementing multiple automated market maker (AMM) curves. - SPLTokenSwapProgramID = MustPublicKeyFromBase58("SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8") - SPLTokenSwapFeeOwner = MustPublicKeyFromBase58("HfoTxFR1Tm6kGmWgYWD6J7YHVy1UwqSULUGVLXkJqaKN") + TokenSwapProgramID = MustPublicKeyFromBase58("SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8") + TokenSwapFeeOwner = MustPublicKeyFromBase58("HfoTxFR1Tm6kGmWgYWD6J7YHVy1UwqSULUGVLXkJqaKN") // A lending protocol for the Token program on the Solana blockchain inspired by Aave and Compound. - SPLTokenLendingProgramID = MustPublicKeyFromBase58("LendZqTs8gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi") + TokenLendingProgramID = MustPublicKeyFromBase58("LendZqTs8gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi") // This program defines the convention and provides the mechanism for mapping // the user's wallet address to the associated token accounts they hold. @@ -49,5 +49,14 @@ var ( // to the transaction log, so that anyone can easily observe memos // and know they were approved by zero or more addresses // by inspecting the transaction log from a trusted provider. - SPLMemoProgramID = MustPublicKeyFromBase58("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr") + MemoProgramID = MustPublicKeyFromBase58("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr") +) + +var ( + // The Mint for native SOL Token accounts + SolMint = MustPublicKeyFromBase58("So11111111111111111111111111111111111111112") +) + +var ( + TokenMetadataProgramID = MustPublicKeyFromBase58("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s") ) diff --git a/programs/system/AdvanceNonceAccount.go b/programs/system/AdvanceNonceAccount.go index 7830fd73..de7133c9 100644 --- a/programs/system/AdvanceNonceAccount.go +++ b/programs/system/AdvanceNonceAccount.go @@ -103,9 +103,9 @@ func (inst *AdvanceNonceAccount) EncodeToTree(parent ag_treeout.Branches) { // Accounts of the instruction: instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta("NonceAccount", inst.AccountMetaSlice[0])) - accountsBranch.Child(ag_format.Meta("$(SysVarRecentBlockHashesPubkey)", inst.AccountMetaSlice[1])) - accountsBranch.Child(ag_format.Meta("NonceAuthorityAccount", inst.AccountMetaSlice[2])) + accountsBranch.Child(ag_format.Meta(" Nonce", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("SysVarRecentBlockHashes", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta(" NonceAuthority", inst.AccountMetaSlice[2])) }) }) }) diff --git a/programs/system/Allocate.go b/programs/system/Allocate.go index 2d06becf..8bba028b 100644 --- a/programs/system/Allocate.go +++ b/programs/system/Allocate.go @@ -94,7 +94,7 @@ func (inst *Allocate) EncodeToTree(parent ag_treeout.Branches) { // Accounts of the instruction: instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta("NewAccount", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("New", inst.AccountMetaSlice[0])) }) }) }) diff --git a/programs/system/AllocateWithSeed.go b/programs/system/AllocateWithSeed.go index 64490601..71a0d341 100644 --- a/programs/system/AllocateWithSeed.go +++ b/programs/system/AllocateWithSeed.go @@ -138,16 +138,16 @@ func (inst *AllocateWithSeed) EncodeToTree(parent ag_treeout.Branches) { // Parameters of the instruction: instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { - paramsBranch.Child(ag_format.Param("Base", *inst.Base)) - paramsBranch.Child(ag_format.Param("Seed", *inst.Seed)) + paramsBranch.Child(ag_format.Param(" Base", *inst.Base)) + paramsBranch.Child(ag_format.Param(" Seed", *inst.Seed)) paramsBranch.Child(ag_format.Param("Space", *inst.Space)) paramsBranch.Child(ag_format.Param("Owner", *inst.Owner)) }) // Accounts of the instruction: instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta("AllocatedAccount", inst.AccountMetaSlice[0])) - accountsBranch.Child(ag_format.Meta("BaseAccount", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("Allocated", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta(" Base", inst.AccountMetaSlice[1])) }) }) }) diff --git a/programs/system/Assign.go b/programs/system/Assign.go index 67b183c8..18e78a1b 100644 --- a/programs/system/Assign.go +++ b/programs/system/Assign.go @@ -94,7 +94,7 @@ func (inst *Assign) EncodeToTree(parent ag_treeout.Branches) { // Accounts of the instruction: instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta("AssignedAccount", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("Assigned", inst.AccountMetaSlice[0])) }) }) }) diff --git a/programs/system/AssignWithSeed.go b/programs/system/AssignWithSeed.go index ba4ec998..1ffc3239 100644 --- a/programs/system/AssignWithSeed.go +++ b/programs/system/AssignWithSeed.go @@ -126,15 +126,15 @@ func (inst *AssignWithSeed) EncodeToTree(parent ag_treeout.Branches) { // Parameters of the instruction: instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { - paramsBranch.Child(ag_format.Param("Base", *inst.Base)) - paramsBranch.Child(ag_format.Param("Seed", *inst.Seed)) + paramsBranch.Child(ag_format.Param(" Base", *inst.Base)) + paramsBranch.Child(ag_format.Param(" Seed", *inst.Seed)) paramsBranch.Child(ag_format.Param("Owner", *inst.Owner)) }) // Accounts of the instruction: instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta("AssignedAccount", inst.AccountMetaSlice[0])) - accountsBranch.Child(ag_format.Meta("BaseAccount", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("Assigned", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta(" Base", inst.AccountMetaSlice[1])) }) }) }) diff --git a/programs/system/AuthorizeNonceAccount.go b/programs/system/AuthorizeNonceAccount.go index 3025b8e1..45be7345 100644 --- a/programs/system/AuthorizeNonceAccount.go +++ b/programs/system/AuthorizeNonceAccount.go @@ -107,8 +107,8 @@ func (inst *AuthorizeNonceAccount) EncodeToTree(parent ag_treeout.Branches) { // Accounts of the instruction: instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta("NonceAccount", inst.AccountMetaSlice[0])) - accountsBranch.Child(ag_format.Meta("NonceAuthorityAccount", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta(" Nonce", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("NonceAuthority", inst.AccountMetaSlice[1])) }) }) }) diff --git a/programs/system/CreateAccount.go b/programs/system/CreateAccount.go index 0ba84047..921626b3 100644 --- a/programs/system/CreateAccount.go +++ b/programs/system/CreateAccount.go @@ -127,14 +127,14 @@ func (inst *CreateAccount) EncodeToTree(parent ag_treeout.Branches) { // Parameters of the instruction: instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { paramsBranch.Child(ag_format.Param("Lamports", *inst.Lamports)) - paramsBranch.Child(ag_format.Param("Space", *inst.Space)) - paramsBranch.Child(ag_format.Param("Owner", *inst.Owner)) + paramsBranch.Child(ag_format.Param(" Space", *inst.Space)) + paramsBranch.Child(ag_format.Param(" Owner", *inst.Owner)) }) // Accounts of the instruction: instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta("FundingAccount", inst.AccountMetaSlice[0])) - accountsBranch.Child(ag_format.Meta("NewAccount", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("Funding", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta(" New", inst.AccountMetaSlice[1])) }) }) }) diff --git a/programs/system/CreateAccountWithSeed.go b/programs/system/CreateAccountWithSeed.go index 56c466c1..c1751031 100644 --- a/programs/system/CreateAccountWithSeed.go +++ b/programs/system/CreateAccountWithSeed.go @@ -171,18 +171,18 @@ func (inst *CreateAccountWithSeed) EncodeToTree(parent ag_treeout.Branches) { // Parameters of the instruction: instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { - paramsBranch.Child(ag_format.Param("Base", *inst.Base)) - paramsBranch.Child(ag_format.Param("Seed", *inst.Seed)) + paramsBranch.Child(ag_format.Param(" Base", *inst.Base)) + paramsBranch.Child(ag_format.Param(" Seed", *inst.Seed)) paramsBranch.Child(ag_format.Param("Lamports", *inst.Lamports)) - paramsBranch.Child(ag_format.Param("Space", *inst.Space)) - paramsBranch.Child(ag_format.Param("Owner", *inst.Owner)) + paramsBranch.Child(ag_format.Param(" Space", *inst.Space)) + paramsBranch.Child(ag_format.Param(" Owner", *inst.Owner)) }) // Accounts of the instruction: instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta("FundingAccount", inst.AccountMetaSlice[0])) - accountsBranch.Child(ag_format.Meta("CreatedAccount", inst.AccountMetaSlice[1])) - accountsBranch.Child(ag_format.Meta("BaseAccount", inst.AccountMetaSlice[2])) + accountsBranch.Child(ag_format.Meta("Funding", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("Created", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta(" Base", inst.AccountMetaSlice[2])) }) }) }) diff --git a/programs/system/InitializeNonceAccount.go b/programs/system/InitializeNonceAccount.go index 3d21c05e..eb7f71b3 100644 --- a/programs/system/InitializeNonceAccount.go +++ b/programs/system/InitializeNonceAccount.go @@ -124,9 +124,9 @@ func (inst *InitializeNonceAccount) EncodeToTree(parent ag_treeout.Branches) { // Accounts of the instruction: instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta("NonceAccount", inst.AccountMetaSlice[0])) - accountsBranch.Child(ag_format.Meta("$(SysVarRecentBlockHashesPubkey)", inst.AccountMetaSlice[1])) - accountsBranch.Child(ag_format.Meta("$(SysVarRentPubkey)", inst.AccountMetaSlice[2])) + accountsBranch.Child(ag_format.Meta(" Nonce", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("SysVarRecentBlockHashes", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta(" SysVarRent", inst.AccountMetaSlice[2])) }) }) }) diff --git a/programs/system/Transfer.go b/programs/system/Transfer.go index 78f96df9..868c51c5 100644 --- a/programs/system/Transfer.go +++ b/programs/system/Transfer.go @@ -107,8 +107,8 @@ func (inst *Transfer) EncodeToTree(parent ag_treeout.Branches) { // Accounts of the instruction: instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta("FundingAccount", inst.AccountMetaSlice[0])) - accountsBranch.Child(ag_format.Meta("RecipientAccount", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta(" Funding", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("Recipient", inst.AccountMetaSlice[1])) }) }) }) diff --git a/programs/system/TransferWithSeed.go b/programs/system/TransferWithSeed.go index ac12f6a4..d24b7876 100644 --- a/programs/system/TransferWithSeed.go +++ b/programs/system/TransferWithSeed.go @@ -139,16 +139,16 @@ func (inst *TransferWithSeed) EncodeToTree(parent ag_treeout.Branches) { // Parameters of the instruction: instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { - paramsBranch.Child(ag_format.Param("Lamports", *inst.Lamports)) - paramsBranch.Child(ag_format.Param("FromSeed", *inst.FromSeed)) + paramsBranch.Child(ag_format.Param(" Lamports", *inst.Lamports)) + paramsBranch.Child(ag_format.Param(" FromSeed", *inst.FromSeed)) paramsBranch.Child(ag_format.Param("FromOwner", *inst.FromOwner)) }) // Accounts of the instruction: instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta("FundingAccount", inst.AccountMetaSlice[0])) - accountsBranch.Child(ag_format.Meta("BaseForFundingAccount", inst.AccountMetaSlice[1])) - accountsBranch.Child(ag_format.Meta("RecipientAccount", inst.AccountMetaSlice[2])) + accountsBranch.Child(ag_format.Meta(" Funding", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("BaseForFunding", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta(" Recipient", inst.AccountMetaSlice[2])) }) }) }) diff --git a/programs/system/WithdrawNonceAccount.go b/programs/system/WithdrawNonceAccount.go index b023da10..f6846d53 100644 --- a/programs/system/WithdrawNonceAccount.go +++ b/programs/system/WithdrawNonceAccount.go @@ -148,11 +148,11 @@ func (inst *WithdrawNonceAccount) EncodeToTree(parent ag_treeout.Branches) { // Accounts of the instruction: instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta("NonceAccount", inst.AccountMetaSlice[0])) - accountsBranch.Child(ag_format.Meta("RecipientAccount", inst.AccountMetaSlice[1])) - accountsBranch.Child(ag_format.Meta("$(SysVarRecentBlockHashesPubkey)", inst.AccountMetaSlice[2])) - accountsBranch.Child(ag_format.Meta("$(SysVarRentPubkey)", inst.AccountMetaSlice[3])) - accountsBranch.Child(ag_format.Meta("NonceAuthorityAccount", inst.AccountMetaSlice[4])) + accountsBranch.Child(ag_format.Meta(" Nonce", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta(" Recipient", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("SysVarRecentBlockHashes", inst.AccountMetaSlice[2])) + accountsBranch.Child(ag_format.Meta(" SysVarRent", inst.AccountMetaSlice[3])) + accountsBranch.Child(ag_format.Meta(" NonceAuthority", inst.AccountMetaSlice[4])) }) }) }) diff --git a/programs/token/Approve.go b/programs/token/Approve.go new file mode 100644 index 00000000..1d8c555d --- /dev/null +++ b/programs/token/Approve.go @@ -0,0 +1,216 @@ +package token + +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" +) + +// Approves a delegate. A delegate is given the authority over tokens on +// behalf of the source account's owner. +type Approve struct { + // The amount of tokens the delegate is approved for. + Amount *uint64 + + // [0] = [WRITE] source + // ··········· The source account. + // + // [1] = [] delegate + // ··········· The delegate. + // + // [2] = [] owner + // ··········· The source account owner. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *Approve) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice Approve) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewApproveInstructionBuilder creates a new `Approve` instruction builder. +func NewApproveInstructionBuilder() *Approve { + nd := &Approve{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetAmount sets the "amount" parameter. +// The amount of tokens the delegate is approved for. +func (inst *Approve) SetAmount(amount uint64) *Approve { + inst.Amount = &amount + return inst +} + +// SetSourceAccount sets the "source" account. +// The source account. +func (inst *Approve) SetSourceAccount(source ag_solanago.PublicKey) *Approve { + inst.Accounts[0] = ag_solanago.Meta(source).WRITE() + return inst +} + +// GetSourceAccount gets the "source" account. +// The source account. +func (inst *Approve) GetSourceAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetDelegateAccount sets the "delegate" account. +// The delegate. +func (inst *Approve) SetDelegateAccount(delegate ag_solanago.PublicKey) *Approve { + inst.Accounts[1] = ag_solanago.Meta(delegate) + return inst +} + +// GetDelegateAccount gets the "delegate" account. +// The delegate. +func (inst *Approve) GetDelegateAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetOwnerAccount sets the "owner" account. +// The source account owner. +func (inst *Approve) SetOwnerAccount(owner ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *Approve { + 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 +} + +// GetOwnerAccount gets the "owner" account. +// The source account owner. +func (inst *Approve) GetOwnerAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst Approve) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_Approve), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst Approve) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *Approve) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Amount == nil { + return errors.New("Amount 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.Delegate 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 *Approve) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("Approve")). + // + 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)) + }) + + // 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("delegate", inst.Accounts[1])) + accountsBranch.Child(ag_format.Meta(" owner", 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 Approve) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `Amount` param: + err = encoder.Encode(obj.Amount) + if err != nil { + return err + } + return nil +} +func (obj *Approve) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `Amount`: + err = decoder.Decode(&obj.Amount) + if err != nil { + return err + } + return nil +} + +// NewApproveInstruction declares a new Approve instruction with the provided parameters and accounts. +func NewApproveInstruction( + // Parameters: + amount uint64, + // Accounts: + source ag_solanago.PublicKey, + delegate ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *Approve { + return NewApproveInstructionBuilder(). + SetAmount(amount). + SetSourceAccount(source). + SetDelegateAccount(delegate). + SetOwnerAccount(owner, multisigSigners...) +} diff --git a/programs/token/ApproveChecked.go b/programs/token/ApproveChecked.go new file mode 100644 index 00000000..2dbb05e0 --- /dev/null +++ b/programs/token/ApproveChecked.go @@ -0,0 +1,268 @@ +package token + +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" +) + +// Approves a delegate. A delegate is given the authority over tokens on +// behalf of the source account's owner. +// +// This instruction differs from Approve 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 ApproveChecked struct { + // The amount of tokens the delegate is approved for. + Amount *uint64 + + // Expected number of base 10 digits to the right of the decimal place. + Decimals *uint8 + + // [0] = [WRITE] source + // ··········· The source account. + // + // [1] = [] mint + // ··········· The token mint. + // + // [2] = [] delegate + // ··········· The delegate. + // + // [3] = [] owner + // ··········· The source account owner. + // + // [4...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *ApproveChecked) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(4) + return nil +} + +func (slice ApproveChecked) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewApproveCheckedInstructionBuilder creates a new `ApproveChecked` instruction builder. +func NewApproveCheckedInstructionBuilder() *ApproveChecked { + nd := &ApproveChecked{ + Accounts: make(ag_solanago.AccountMetaSlice, 4), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetAmount sets the "amount" parameter. +// The amount of tokens the delegate is approved for. +func (inst *ApproveChecked) SetAmount(amount uint64) *ApproveChecked { + inst.Amount = &amount + return inst +} + +// SetDecimals sets the "decimals" parameter. +// Expected number of base 10 digits to the right of the decimal place. +func (inst *ApproveChecked) SetDecimals(decimals uint8) *ApproveChecked { + inst.Decimals = &decimals + return inst +} + +// SetSourceAccount sets the "source" account. +// The source account. +func (inst *ApproveChecked) SetSourceAccount(source ag_solanago.PublicKey) *ApproveChecked { + inst.Accounts[0] = ag_solanago.Meta(source).WRITE() + return inst +} + +// GetSourceAccount gets the "source" account. +// The source account. +func (inst *ApproveChecked) GetSourceAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetMintAccount sets the "mint" account. +// The token mint. +func (inst *ApproveChecked) SetMintAccount(mint ag_solanago.PublicKey) *ApproveChecked { + inst.Accounts[1] = ag_solanago.Meta(mint) + return inst +} + +// GetMintAccount gets the "mint" account. +// The token mint. +func (inst *ApproveChecked) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetDelegateAccount sets the "delegate" account. +// The delegate. +func (inst *ApproveChecked) SetDelegateAccount(delegate ag_solanago.PublicKey) *ApproveChecked { + inst.Accounts[2] = ag_solanago.Meta(delegate) + return inst +} + +// GetDelegateAccount gets the "delegate" account. +// The delegate. +func (inst *ApproveChecked) GetDelegateAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +// SetOwnerAccount sets the "owner" account. +// The source account owner. +func (inst *ApproveChecked) SetOwnerAccount(owner ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *ApproveChecked { + 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 +} + +// GetOwnerAccount gets the "owner" account. +// The source account owner. +func (inst *ApproveChecked) GetOwnerAccount() *ag_solanago.AccountMeta { + return inst.Accounts[3] +} + +func (inst ApproveChecked) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_ApproveChecked), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst ApproveChecked) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *ApproveChecked) 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.Delegate 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 *ApproveChecked) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("ApproveChecked")). + // + 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)) + }) + + // 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("delegate", 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 ApproveChecked) 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 + } + return nil +} +func (obj *ApproveChecked) 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 + } + return nil +} + +// NewApproveCheckedInstruction declares a new ApproveChecked instruction with the provided parameters and accounts. +func NewApproveCheckedInstruction( + // Parameters: + amount uint64, + decimals uint8, + // Accounts: + source ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + delegate ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *ApproveChecked { + return NewApproveCheckedInstructionBuilder(). + SetAmount(amount). + SetDecimals(decimals). + SetSourceAccount(source). + SetMintAccount(mint). + SetDelegateAccount(delegate). + SetOwnerAccount(owner, multisigSigners...) +} diff --git a/programs/token/ApproveChecked_test.go b/programs/token/ApproveChecked_test.go new file mode 100644 index 00000000..69de9f78 --- /dev/null +++ b/programs/token/ApproveChecked_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_ApproveChecked(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("ApproveChecked"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(ApproveChecked) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(ApproveChecked) + err = decodeT(got, buf.Bytes()) + params.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/Approve_test.go b/programs/token/Approve_test.go new file mode 100644 index 00000000..048955fe --- /dev/null +++ b/programs/token/Approve_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_Approve(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("Approve"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(Approve) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(Approve) + err = decodeT(got, buf.Bytes()) + got.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/Burn.go b/programs/token/Burn.go new file mode 100644 index 00000000..d0a6946a --- /dev/null +++ b/programs/token/Burn.go @@ -0,0 +1,216 @@ +package token + +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" +) + +// Burns tokens by removing them from an account. `Burn` does not support +// accounts associated with the native mint, use `CloseAccount` instead. +type Burn struct { + // The amount of tokens to burn. + Amount *uint64 + + // [0] = [WRITE] source + // ··········· The account to burn from. + // + // [1] = [WRITE] mint + // ··········· The token mint. + // + // [2] = [] owner + // ··········· The account's owner/delegate. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *Burn) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice Burn) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewBurnInstructionBuilder creates a new `Burn` instruction builder. +func NewBurnInstructionBuilder() *Burn { + nd := &Burn{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetAmount sets the "amount" parameter. +// The amount of tokens to burn. +func (inst *Burn) SetAmount(amount uint64) *Burn { + inst.Amount = &amount + return inst +} + +// SetSourceAccount sets the "source" account. +// The account to burn from. +func (inst *Burn) SetSourceAccount(source ag_solanago.PublicKey) *Burn { + inst.Accounts[0] = ag_solanago.Meta(source).WRITE() + return inst +} + +// GetSourceAccount gets the "source" account. +// The account to burn from. +func (inst *Burn) GetSourceAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetMintAccount sets the "mint" account. +// The token mint. +func (inst *Burn) SetMintAccount(mint ag_solanago.PublicKey) *Burn { + inst.Accounts[1] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// GetMintAccount gets the "mint" account. +// The token mint. +func (inst *Burn) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetOwnerAccount sets the "owner" account. +// The account's owner/delegate. +func (inst *Burn) SetOwnerAccount(owner ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *Burn { + 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 +} + +// GetOwnerAccount gets the "owner" account. +// The account's owner/delegate. +func (inst *Burn) GetOwnerAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst Burn) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_Burn), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst Burn) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *Burn) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Amount == nil { + return errors.New("Amount 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.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 *Burn) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("Burn")). + // + 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)) + }) + + // 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(" owner", 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 Burn) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `Amount` param: + err = encoder.Encode(obj.Amount) + if err != nil { + return err + } + return nil +} +func (obj *Burn) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `Amount`: + err = decoder.Decode(&obj.Amount) + if err != nil { + return err + } + return nil +} + +// NewBurnInstruction declares a new Burn instruction with the provided parameters and accounts. +func NewBurnInstruction( + // Parameters: + amount uint64, + // Accounts: + source ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *Burn { + return NewBurnInstructionBuilder(). + SetAmount(amount). + SetSourceAccount(source). + SetMintAccount(mint). + SetOwnerAccount(owner, multisigSigners...) +} diff --git a/programs/token/BurnChecked.go b/programs/token/BurnChecked.go new file mode 100644 index 00000000..19dbf366 --- /dev/null +++ b/programs/token/BurnChecked.go @@ -0,0 +1,247 @@ +package token + +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" +) + +// Burns tokens by removing them from an account. `BurnChecked` does not +// support accounts associated with the native mint, use `CloseAccount` +// instead. +// +// This instruction differs from Burn in that the decimals value is checked +// by the caller. This may be useful when creating transactions offline or +// within a hardware wallet. +type BurnChecked struct { + // The amount of tokens to burn. + Amount *uint64 + + // Expected number of base 10 digits to the right of the decimal place. + Decimals *uint8 + + // [0] = [WRITE] source + // ··········· The account to burn from. + // + // [1] = [WRITE] mint + // ··········· The token mint. + // + // [2] = [] owner + // ··········· The account's owner/delegate. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *BurnChecked) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice BurnChecked) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewBurnCheckedInstructionBuilder creates a new `BurnChecked` instruction builder. +func NewBurnCheckedInstructionBuilder() *BurnChecked { + nd := &BurnChecked{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetAmount sets the "amount" parameter. +// The amount of tokens to burn. +func (inst *BurnChecked) SetAmount(amount uint64) *BurnChecked { + inst.Amount = &amount + return inst +} + +// SetDecimals sets the "decimals" parameter. +// Expected number of base 10 digits to the right of the decimal place. +func (inst *BurnChecked) SetDecimals(decimals uint8) *BurnChecked { + inst.Decimals = &decimals + return inst +} + +// SetSourceAccount sets the "source" account. +// The account to burn from. +func (inst *BurnChecked) SetSourceAccount(source ag_solanago.PublicKey) *BurnChecked { + inst.Accounts[0] = ag_solanago.Meta(source).WRITE() + return inst +} + +// GetSourceAccount gets the "source" account. +// The account to burn from. +func (inst *BurnChecked) GetSourceAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetMintAccount sets the "mint" account. +// The token mint. +func (inst *BurnChecked) SetMintAccount(mint ag_solanago.PublicKey) *BurnChecked { + inst.Accounts[1] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// GetMintAccount gets the "mint" account. +// The token mint. +func (inst *BurnChecked) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetOwnerAccount sets the "owner" account. +// The account's owner/delegate. +func (inst *BurnChecked) SetOwnerAccount(owner ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *BurnChecked { + 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 +} + +// GetOwnerAccount gets the "owner" account. +// The account's owner/delegate. +func (inst *BurnChecked) GetOwnerAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst BurnChecked) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_BurnChecked), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst BurnChecked) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *BurnChecked) 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.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 *BurnChecked) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("BurnChecked")). + // + 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)) + }) + + // 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(" owner", 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 BurnChecked) 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 + } + return nil +} +func (obj *BurnChecked) 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 + } + return nil +} + +// NewBurnCheckedInstruction declares a new BurnChecked instruction with the provided parameters and accounts. +func NewBurnCheckedInstruction( + // Parameters: + amount uint64, + decimals uint8, + // Accounts: + source ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *BurnChecked { + return NewBurnCheckedInstructionBuilder(). + SetAmount(amount). + SetDecimals(decimals). + SetSourceAccount(source). + SetMintAccount(mint). + SetOwnerAccount(owner, multisigSigners...) +} diff --git a/programs/token/BurnChecked_test.go b/programs/token/BurnChecked_test.go new file mode 100644 index 00000000..3f116c08 --- /dev/null +++ b/programs/token/BurnChecked_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_BurnChecked(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("BurnChecked"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(BurnChecked) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(BurnChecked) + err = decodeT(got, buf.Bytes()) + params.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/Burn_test.go b/programs/token/Burn_test.go new file mode 100644 index 00000000..10d6ad37 --- /dev/null +++ b/programs/token/Burn_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_Burn(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("Burn"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(Burn) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(Burn) + err = decodeT(got, buf.Bytes()) + params.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/CloseAccount.go b/programs/token/CloseAccount.go new file mode 100644 index 00000000..a9e4a448 --- /dev/null +++ b/programs/token/CloseAccount.go @@ -0,0 +1,185 @@ +package token + +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" +) + +// Close an account by transferring all its SOL to the destination account. +// Non-native accounts may only be closed if its token amount is zero. +type CloseAccount struct { + + // [0] = [WRITE] account + // ··········· The account to close. + // + // [1] = [WRITE] destination + // ··········· The destination account. + // + // [2] = [] owner + // ··········· The account's owner. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *CloseAccount) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice CloseAccount) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewCloseAccountInstructionBuilder creates a new `CloseAccount` instruction builder. +func NewCloseAccountInstructionBuilder() *CloseAccount { + nd := &CloseAccount{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetAccount sets the "account" account. +// The account to close. +func (inst *CloseAccount) SetAccount(account ag_solanago.PublicKey) *CloseAccount { + inst.Accounts[0] = ag_solanago.Meta(account).WRITE() + return inst +} + +// GetAccount gets the "account" account. +// The account to close. +func (inst *CloseAccount) GetAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetDestinationAccount sets the "destination" account. +// The destination account. +func (inst *CloseAccount) SetDestinationAccount(destination ag_solanago.PublicKey) *CloseAccount { + inst.Accounts[1] = ag_solanago.Meta(destination).WRITE() + return inst +} + +// GetDestinationAccount gets the "destination" account. +// The destination account. +func (inst *CloseAccount) GetDestinationAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetOwnerAccount sets the "owner" account. +// The account's owner. +func (inst *CloseAccount) SetOwnerAccount(owner ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *CloseAccount { + 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 +} + +// GetOwnerAccount gets the "owner" account. +// The account's owner. +func (inst *CloseAccount) GetOwnerAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst CloseAccount) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_CloseAccount), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst CloseAccount) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *CloseAccount) Validate() error { + // Check whether all (required) accounts are set: + { + 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 *CloseAccount) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("CloseAccount")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // Accounts of the instruction: + 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])) + + 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 CloseAccount) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} +func (obj *CloseAccount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +// NewCloseAccountInstruction declares a new CloseAccount instruction with the provided parameters and accounts. +func NewCloseAccountInstruction( + // Accounts: + account ag_solanago.PublicKey, + destination ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *CloseAccount { + return NewCloseAccountInstructionBuilder(). + SetAccount(account). + SetDestinationAccount(destination). + SetOwnerAccount(owner, multisigSigners...) +} diff --git a/programs/token/CloseAccount_test.go b/programs/token/CloseAccount_test.go new file mode 100644 index 00000000..db8beda8 --- /dev/null +++ b/programs/token/CloseAccount_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_CloseAccount(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("CloseAccount"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(CloseAccount) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(CloseAccount) + err = decodeT(got, buf.Bytes()) + params.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/FreezeAccount.go b/programs/token/FreezeAccount.go new file mode 100644 index 00000000..679287e0 --- /dev/null +++ b/programs/token/FreezeAccount.go @@ -0,0 +1,184 @@ +package token + +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" +) + +// Freeze an Initialized account using the Mint's freeze_authority (if set). +type FreezeAccount struct { + + // [0] = [WRITE] account + // ··········· The account to freeze. + // + // [1] = [] mint + // ··········· The token mint. + // + // [2] = [] authority + // ··········· The mint freeze authority. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *FreezeAccount) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice FreezeAccount) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewFreezeAccountInstructionBuilder creates a new `FreezeAccount` instruction builder. +func NewFreezeAccountInstructionBuilder() *FreezeAccount { + nd := &FreezeAccount{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetAccount sets the "account" account. +// The account to freeze. +func (inst *FreezeAccount) SetAccount(account ag_solanago.PublicKey) *FreezeAccount { + inst.Accounts[0] = ag_solanago.Meta(account).WRITE() + return inst +} + +// GetAccount gets the "account" account. +// The account to freeze. +func (inst *FreezeAccount) GetAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetMintAccount sets the "mint" account. +// The token mint. +func (inst *FreezeAccount) SetMintAccount(mint ag_solanago.PublicKey) *FreezeAccount { + inst.Accounts[1] = ag_solanago.Meta(mint) + return inst +} + +// GetMintAccount gets the "mint" account. +// The token mint. +func (inst *FreezeAccount) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetAuthorityAccount sets the "authority" account. +// The mint freeze authority. +func (inst *FreezeAccount) SetAuthorityAccount(authority ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *FreezeAccount { + 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 freeze authority. +func (inst *FreezeAccount) GetAuthorityAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst FreezeAccount) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_FreezeAccount), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst FreezeAccount) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *FreezeAccount) Validate() error { + // Check whether all (required) accounts are set: + { + if inst.Accounts[0] == nil { + return errors.New("accounts.Account 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.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 *FreezeAccount) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("FreezeAccount")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" account", inst.Accounts[0])) + accountsBranch.Child(ag_format.Meta(" mint", 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 FreezeAccount) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} +func (obj *FreezeAccount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +// NewFreezeAccountInstruction declares a new FreezeAccount instruction with the provided parameters and accounts. +func NewFreezeAccountInstruction( + // Accounts: + account ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *FreezeAccount { + return NewFreezeAccountInstructionBuilder(). + SetAccount(account). + SetMintAccount(mint). + SetAuthorityAccount(authority, multisigSigners...) +} diff --git a/programs/token/FreezeAccount_test.go b/programs/token/FreezeAccount_test.go new file mode 100644 index 00000000..3aae0e50 --- /dev/null +++ b/programs/token/FreezeAccount_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_FreezeAccount(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("FreezeAccount"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(FreezeAccount) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(FreezeAccount) + err = decodeT(got, buf.Bytes()) + params.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/InitializeAccount.go b/programs/token/InitializeAccount.go new file mode 100644 index 00000000..24706b0f --- /dev/null +++ b/programs/token/InitializeAccount.go @@ -0,0 +1,177 @@ +package token + +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" +) + +// Initializes a new account to hold tokens. If this account is associated +// with the native mint then the token balance of the initialized account +// will be equal to the amount of SOL in the account. If this account is +// associated with another mint, that mint must be initialized before this +// command can succeed. +// +// The `InitializeAccount` instruction requires no signers and MUST be +// included within the same Transaction as the system program's +// `CreateAccount` instruction that creates the account being initialized. +// Otherwise another party can acquire ownership of the uninitialized +// account. +type InitializeAccount struct { + + // [0] = [WRITE] account + // ··········· The account to initialize. + // + // [1] = [] mint + // ··········· The mint this account will be associated with. + // + // [2] = [] owner + // ··········· The new account's owner/multisignature. + // + // [3] = [] $(SysVarRentPubkey) + // ··········· Rent sysvar. + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewInitializeAccountInstructionBuilder creates a new `InitializeAccount` instruction builder. +func NewInitializeAccountInstructionBuilder() *InitializeAccount { + nd := &InitializeAccount{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 4), + } + nd.AccountMetaSlice[3] = ag_solanago.Meta(ag_solanago.SysVarRentPubkey) + return nd +} + +// SetAccount sets the "account" account. +// The account to initialize. +func (inst *InitializeAccount) SetAccount(account ag_solanago.PublicKey) *InitializeAccount { + inst.AccountMetaSlice[0] = ag_solanago.Meta(account).WRITE() + return inst +} + +// GetAccount gets the "account" account. +// The account to initialize. +func (inst *InitializeAccount) GetAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// SetMintAccount sets the "mint" account. +// The mint this account will be associated with. +func (inst *InitializeAccount) SetMintAccount(mint ag_solanago.PublicKey) *InitializeAccount { + inst.AccountMetaSlice[1] = ag_solanago.Meta(mint) + return inst +} + +// GetMintAccount gets the "mint" account. +// The mint this account will be associated with. +func (inst *InitializeAccount) GetMintAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +// SetOwnerAccount sets the "owner" account. +// The new account's owner/multisignature. +func (inst *InitializeAccount) SetOwnerAccount(owner ag_solanago.PublicKey) *InitializeAccount { + inst.AccountMetaSlice[2] = ag_solanago.Meta(owner) + return inst +} + +// GetOwnerAccount gets the "owner" account. +// The new account's owner/multisignature. +func (inst *InitializeAccount) GetOwnerAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + +// SetSysVarRentPubkeyAccount sets the "$(SysVarRentPubkey)" account. +// Rent sysvar. +func (inst *InitializeAccount) SetSysVarRentPubkeyAccount(SysVarRentPubkey ag_solanago.PublicKey) *InitializeAccount { + inst.AccountMetaSlice[3] = ag_solanago.Meta(SysVarRentPubkey) + return inst +} + +// GetSysVarRentPubkeyAccount gets the "$(SysVarRentPubkey)" account. +// Rent sysvar. +func (inst *InitializeAccount) GetSysVarRentPubkeyAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[3] +} + +func (inst InitializeAccount) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_InitializeAccount), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst InitializeAccount) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InitializeAccount) Validate() error { + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Account is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.Owner is not set") + } + if inst.AccountMetaSlice[3] == nil { + return errors.New("accounts.SysVarRentPubkey is not set") + } + } + return nil +} + +func (inst *InitializeAccount) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InitializeAccount")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" account", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta(" owner", inst.AccountMetaSlice[2])) + accountsBranch.Child(ag_format.Meta("SysVarRent", inst.AccountMetaSlice[3])) + }) + }) + }) +} + +func (obj InitializeAccount) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} +func (obj *InitializeAccount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +// NewInitializeAccountInstruction declares a new InitializeAccount instruction with the provided parameters and accounts. +func NewInitializeAccountInstruction( + // Accounts: + account ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + SysVarRentPubkey ag_solanago.PublicKey) *InitializeAccount { + return NewInitializeAccountInstructionBuilder(). + SetAccount(account). + SetMintAccount(mint). + SetOwnerAccount(owner). + SetSysVarRentPubkeyAccount(SysVarRentPubkey) +} diff --git a/programs/token/InitializeAccount2.go b/programs/token/InitializeAccount2.go new file mode 100644 index 00000000..0b7ecbc7 --- /dev/null +++ b/programs/token/InitializeAccount2.go @@ -0,0 +1,179 @@ +package token + +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" +) + +// Like InitializeAccount, but the owner pubkey is passed via instruction data +// rather than the accounts list. This variant may be preferable when using +// Cross Program Invocation from an instruction that does not need the owner's +// `AccountInfo` otherwise. +type InitializeAccount2 struct { + // The new account's owner/multisignature. + Owner *ag_solanago.PublicKey + + // [0] = [WRITE] account + // ··········· The account to initialize. + // + // [1] = [] mint + // ··········· The mint this account will be associated with. + // + // [2] = [] $(SysVarRentPubkey) + // ··········· Rent sysvar. + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewInitializeAccount2InstructionBuilder creates a new `InitializeAccount2` instruction builder. +func NewInitializeAccount2InstructionBuilder() *InitializeAccount2 { + nd := &InitializeAccount2{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), + } + nd.AccountMetaSlice[2] = ag_solanago.Meta(ag_solanago.SysVarRentPubkey) + return nd +} + +// SetOwner sets the "owner" parameter. +// The new account's owner/multisignature. +func (inst *InitializeAccount2) SetOwner(owner ag_solanago.PublicKey) *InitializeAccount2 { + inst.Owner = &owner + return inst +} + +// SetAccount sets the "account" account. +// The account to initialize. +func (inst *InitializeAccount2) SetAccount(account ag_solanago.PublicKey) *InitializeAccount2 { + inst.AccountMetaSlice[0] = ag_solanago.Meta(account).WRITE() + return inst +} + +// GetAccount gets the "account" account. +// The account to initialize. +func (inst *InitializeAccount2) GetAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// SetMintAccount sets the "mint" account. +// The mint this account will be associated with. +func (inst *InitializeAccount2) SetMintAccount(mint ag_solanago.PublicKey) *InitializeAccount2 { + inst.AccountMetaSlice[1] = ag_solanago.Meta(mint) + return inst +} + +// GetMintAccount gets the "mint" account. +// The mint this account will be associated with. +func (inst *InitializeAccount2) GetMintAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +// SetSysVarRentPubkeyAccount sets the "$(SysVarRentPubkey)" account. +// Rent sysvar. +func (inst *InitializeAccount2) SetSysVarRentPubkeyAccount(SysVarRentPubkey ag_solanago.PublicKey) *InitializeAccount2 { + inst.AccountMetaSlice[2] = ag_solanago.Meta(SysVarRentPubkey) + return inst +} + +// GetSysVarRentPubkeyAccount gets the "$(SysVarRentPubkey)" account. +// Rent sysvar. +func (inst *InitializeAccount2) GetSysVarRentPubkeyAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + +func (inst InitializeAccount2) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_InitializeAccount2), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst InitializeAccount2) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InitializeAccount2) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Owner == nil { + return errors.New("Owner parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Account is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.SysVarRentPubkey is not set") + } + } + return nil +} + +func (inst *InitializeAccount2) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InitializeAccount2")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Owner", *inst.Owner)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" account", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("SysVarRent", inst.AccountMetaSlice[2])) + }) + }) + }) +} + +func (obj InitializeAccount2) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `Owner` param: + err = encoder.Encode(obj.Owner) + if err != nil { + return err + } + return nil +} +func (obj *InitializeAccount2) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `Owner`: + err = decoder.Decode(&obj.Owner) + if err != nil { + return err + } + return nil +} + +// NewInitializeAccount2Instruction declares a new InitializeAccount2 instruction with the provided parameters and accounts. +func NewInitializeAccount2Instruction( + // Parameters: + owner ag_solanago.PublicKey, + // Accounts: + account ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + SysVarRentPubkey ag_solanago.PublicKey) *InitializeAccount2 { + return NewInitializeAccount2InstructionBuilder(). + SetOwner(owner). + SetAccount(account). + SetMintAccount(mint). + SetSysVarRentPubkeyAccount(SysVarRentPubkey) +} diff --git a/programs/token/InitializeAccount2_test.go b/programs/token/InitializeAccount2_test.go new file mode 100644 index 00000000..4b132e9d --- /dev/null +++ b/programs/token/InitializeAccount2_test.go @@ -0,0 +1,31 @@ +package token + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_InitializeAccount2(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("InitializeAccount2"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(InitializeAccount2) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(InitializeAccount2) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/InitializeAccount3.go b/programs/token/InitializeAccount3.go new file mode 100644 index 00000000..b7e8d1d6 --- /dev/null +++ b/programs/token/InitializeAccount3.go @@ -0,0 +1,153 @@ +package token + +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" +) + +// Like InitializeAccount2, but does not require the Rent sysvar to be provided. +type InitializeAccount3 struct { + // The new account's owner/multisignature. + Owner *ag_solanago.PublicKey + + // [0] = [WRITE] account + // ··········· The account to initialize. + // + // [1] = [] mint + // ··········· The mint this account will be associated with. + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewInitializeAccount3InstructionBuilder creates a new `InitializeAccount3` instruction builder. +func NewInitializeAccount3InstructionBuilder() *InitializeAccount3 { + nd := &InitializeAccount3{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2), + } + return nd +} + +// SetOwner sets the "owner" parameter. +// The new account's owner/multisignature. +func (inst *InitializeAccount3) SetOwner(owner ag_solanago.PublicKey) *InitializeAccount3 { + inst.Owner = &owner + return inst +} + +// SetAccount sets the "account" account. +// The account to initialize. +func (inst *InitializeAccount3) SetAccount(account ag_solanago.PublicKey) *InitializeAccount3 { + inst.AccountMetaSlice[0] = ag_solanago.Meta(account).WRITE() + return inst +} + +// GetAccount gets the "account" account. +// The account to initialize. +func (inst *InitializeAccount3) GetAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// SetMintAccount sets the "mint" account. +// The mint this account will be associated with. +func (inst *InitializeAccount3) SetMintAccount(mint ag_solanago.PublicKey) *InitializeAccount3 { + inst.AccountMetaSlice[1] = ag_solanago.Meta(mint) + return inst +} + +// GetMintAccount gets the "mint" account. +// The mint this account will be associated with. +func (inst *InitializeAccount3) GetMintAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +func (inst InitializeAccount3) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_InitializeAccount3), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst InitializeAccount3) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InitializeAccount3) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Owner == nil { + return errors.New("Owner parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Account is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Mint is not set") + } + } + return nil +} + +func (inst *InitializeAccount3) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InitializeAccount3")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("Owner", *inst.Owner)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("account", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice[1])) + }) + }) + }) +} + +func (obj InitializeAccount3) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `Owner` param: + err = encoder.Encode(obj.Owner) + if err != nil { + return err + } + return nil +} +func (obj *InitializeAccount3) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `Owner`: + err = decoder.Decode(&obj.Owner) + if err != nil { + return err + } + return nil +} + +// NewInitializeAccount3Instruction declares a new InitializeAccount3 instruction with the provided parameters and accounts. +func NewInitializeAccount3Instruction( + // Parameters: + owner ag_solanago.PublicKey, + // Accounts: + account ag_solanago.PublicKey, + mint ag_solanago.PublicKey) *InitializeAccount3 { + return NewInitializeAccount3InstructionBuilder(). + SetOwner(owner). + SetAccount(account). + SetMintAccount(mint) +} diff --git a/programs/token/InitializeAccount3_test.go b/programs/token/InitializeAccount3_test.go new file mode 100644 index 00000000..443fc879 --- /dev/null +++ b/programs/token/InitializeAccount3_test.go @@ -0,0 +1,31 @@ +package token + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_InitializeAccount3(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("InitializeAccount3"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(InitializeAccount3) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(InitializeAccount3) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/InitializeAccount_test.go b/programs/token/InitializeAccount_test.go new file mode 100644 index 00000000..01b41201 --- /dev/null +++ b/programs/token/InitializeAccount_test.go @@ -0,0 +1,31 @@ +package token + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_InitializeAccount(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("InitializeAccount"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(InitializeAccount) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(InitializeAccount) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/InitializeMint.go b/programs/token/InitializeMint.go new file mode 100644 index 00000000..f9c08148 --- /dev/null +++ b/programs/token/InitializeMint.go @@ -0,0 +1,231 @@ +package token + +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" +) + +// Initializes a new mint and optionally deposits all the newly minted +// tokens in an account. +// +// The `InitializeMint` instruction requires no signers and MUST be +// included within the same Transaction as the system program's +// `CreateAccount` instruction that creates the account being initialized. +// Otherwise another party can acquire ownership of the uninitialized +// account. +type InitializeMint struct { + // Number of base 10 digits to the right of the decimal place. + Decimals *uint8 + + // The authority/multisignature to mint tokens. + MintAuthority *ag_solanago.PublicKey + + // The freeze authority/multisignature of the mint. + FreezeAuthority *ag_solanago.PublicKey `bin:"optional"` + + // [0] = [WRITE] mint + // ··········· The mint to initialize. + // + // [1] = [] $(SysVarRentPubkey) + // ··········· Rent sysvar. + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewInitializeMintInstructionBuilder creates a new `InitializeMint` instruction builder. +func NewInitializeMintInstructionBuilder() *InitializeMint { + nd := &InitializeMint{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 2), + } + nd.AccountMetaSlice[1] = ag_solanago.Meta(ag_solanago.SysVarRentPubkey) + return nd +} + +// SetDecimals sets the "decimals" parameter. +// Number of base 10 digits to the right of the decimal place. +func (inst *InitializeMint) SetDecimals(decimals uint8) *InitializeMint { + inst.Decimals = &decimals + return inst +} + +// SetMintAuthority sets the "mint_authority" parameter. +// The authority/multisignature to mint tokens. +func (inst *InitializeMint) SetMintAuthority(mint_authority ag_solanago.PublicKey) *InitializeMint { + inst.MintAuthority = &mint_authority + return inst +} + +// SetFreezeAuthority sets the "freeze_authority" parameter. +// The freeze authority/multisignature of the mint. +func (inst *InitializeMint) SetFreezeAuthority(freeze_authority ag_solanago.PublicKey) *InitializeMint { + inst.FreezeAuthority = &freeze_authority + return inst +} + +// SetMintAccount sets the "mint" account. +// The mint to initialize. +func (inst *InitializeMint) SetMintAccount(mint ag_solanago.PublicKey) *InitializeMint { + inst.AccountMetaSlice[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// GetMintAccount gets the "mint" account. +// The mint to initialize. +func (inst *InitializeMint) GetMintAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// SetSysVarRentPubkeyAccount sets the "$(SysVarRentPubkey)" account. +// Rent sysvar. +func (inst *InitializeMint) SetSysVarRentPubkeyAccount(SysVarRentPubkey ag_solanago.PublicKey) *InitializeMint { + inst.AccountMetaSlice[1] = ag_solanago.Meta(SysVarRentPubkey) + return inst +} + +// GetSysVarRentPubkeyAccount gets the "$(SysVarRentPubkey)" account. +// Rent sysvar. +func (inst *InitializeMint) GetSysVarRentPubkeyAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +func (inst InitializeMint) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_InitializeMint), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst InitializeMint) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InitializeMint) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Decimals == nil { + return errors.New("Decimals parameter is not set") + } + if inst.MintAuthority == nil { + return errors.New("MintAuthority parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Mint is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.SysVarRentPubkey is not set") + } + } + return nil +} + +func (inst *InitializeMint) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InitializeMint")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param(" Decimals", *inst.Decimals)) + paramsBranch.Child(ag_format.Param(" MintAuthority", *inst.MintAuthority)) + paramsBranch.Child(ag_format.Param("FreezeAuthority (OPT)", inst.FreezeAuthority)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta("SysVarRent", inst.AccountMetaSlice[1])) + }) + }) + }) +} + +func (obj InitializeMint) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `Decimals` param: + err = encoder.Encode(obj.Decimals) + if err != nil { + return err + } + // Serialize `MintAuthority` param: + err = encoder.Encode(obj.MintAuthority) + if err != nil { + return err + } + // Serialize `FreezeAuthority` param (optional): + { + if obj.FreezeAuthority == nil { + err = encoder.WriteBool(false) + if err != nil { + return err + } + } else { + err = encoder.WriteBool(true) + if err != nil { + return err + } + err = encoder.Encode(obj.FreezeAuthority) + if err != nil { + return err + } + } + } + return nil +} +func (obj *InitializeMint) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `Decimals`: + err = decoder.Decode(&obj.Decimals) + if err != nil { + return err + } + // Deserialize `MintAuthority`: + err = decoder.Decode(&obj.MintAuthority) + if err != nil { + return err + } + // Deserialize `FreezeAuthority` (optional): + { + ok, err := decoder.ReadBool() + if err != nil { + return err + } + if ok { + err = decoder.Decode(&obj.FreezeAuthority) + if err != nil { + return err + } + } + } + return nil +} + +// NewInitializeMintInstruction declares a new InitializeMint instruction with the provided parameters and accounts. +func NewInitializeMintInstruction( + // Parameters: + decimals uint8, + mint_authority ag_solanago.PublicKey, + freeze_authority ag_solanago.PublicKey, + // Accounts: + mint ag_solanago.PublicKey, + SysVarRentPubkey ag_solanago.PublicKey) *InitializeMint { + return NewInitializeMintInstructionBuilder(). + SetDecimals(decimals). + SetMintAuthority(mint_authority). + SetFreezeAuthority(freeze_authority). + SetMintAccount(mint). + SetSysVarRentPubkeyAccount(SysVarRentPubkey) +} diff --git a/programs/token/InitializeMint2.go b/programs/token/InitializeMint2.go new file mode 100644 index 00000000..c85772bd --- /dev/null +++ b/programs/token/InitializeMint2.go @@ -0,0 +1,201 @@ +package token + +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" +) + +// Like InitializeMint, but does not require the Rent sysvar to be provided. +type InitializeMint2 struct { + // Number of base 10 digits to the right of the decimal place. + Decimals *uint8 + + // The authority/multisignature to mint tokens. + MintAuthority *ag_solanago.PublicKey + + // The freeze authority/multisignature of the mint. + FreezeAuthority *ag_solanago.PublicKey `bin:"optional"` + + // [0] = [WRITE] mint + // ··········· The mint to initialize. + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewInitializeMint2InstructionBuilder creates a new `InitializeMint2` instruction builder. +func NewInitializeMint2InstructionBuilder() *InitializeMint2 { + nd := &InitializeMint2{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1), + } + return nd +} + +// SetDecimals sets the "decimals" parameter. +// Number of base 10 digits to the right of the decimal place. +func (inst *InitializeMint2) SetDecimals(decimals uint8) *InitializeMint2 { + inst.Decimals = &decimals + return inst +} + +// SetMintAuthority sets the "mint_authority" parameter. +// The authority/multisignature to mint tokens. +func (inst *InitializeMint2) SetMintAuthority(mint_authority ag_solanago.PublicKey) *InitializeMint2 { + inst.MintAuthority = &mint_authority + return inst +} + +// SetFreezeAuthority sets the "freeze_authority" parameter. +// The freeze authority/multisignature of the mint. +func (inst *InitializeMint2) SetFreezeAuthority(freeze_authority ag_solanago.PublicKey) *InitializeMint2 { + inst.FreezeAuthority = &freeze_authority + return inst +} + +// SetMintAccount sets the "mint" account. +// The mint to initialize. +func (inst *InitializeMint2) SetMintAccount(mint ag_solanago.PublicKey) *InitializeMint2 { + inst.AccountMetaSlice[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// GetMintAccount gets the "mint" account. +// The mint to initialize. +func (inst *InitializeMint2) GetMintAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +func (inst InitializeMint2) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_InitializeMint2), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst InitializeMint2) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InitializeMint2) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Decimals == nil { + return errors.New("Decimals parameter is not set") + } + if inst.MintAuthority == nil { + return errors.New("MintAuthority parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Mint is not set") + } + } + return nil +} + +func (inst *InitializeMint2) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InitializeMint2")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param(" Decimals", *inst.Decimals)) + paramsBranch.Child(ag_format.Param(" MintAuthority", *inst.MintAuthority)) + paramsBranch.Child(ag_format.Param("FreezeAuthority (OPT)", inst.FreezeAuthority)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("mint", inst.AccountMetaSlice[0])) + }) + }) + }) +} + +func (obj InitializeMint2) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `Decimals` param: + err = encoder.Encode(obj.Decimals) + if err != nil { + return err + } + // Serialize `MintAuthority` param: + err = encoder.Encode(obj.MintAuthority) + if err != nil { + return err + } + // Serialize `FreezeAuthority` param (optional): + { + if obj.FreezeAuthority == nil { + err = encoder.WriteBool(false) + if err != nil { + return err + } + } else { + err = encoder.WriteBool(true) + if err != nil { + return err + } + err = encoder.Encode(obj.FreezeAuthority) + if err != nil { + return err + } + } + } + return nil +} +func (obj *InitializeMint2) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `Decimals`: + err = decoder.Decode(&obj.Decimals) + if err != nil { + return err + } + // Deserialize `MintAuthority`: + err = decoder.Decode(&obj.MintAuthority) + if err != nil { + return err + } + // Deserialize `FreezeAuthority` (optional): + { + ok, err := decoder.ReadBool() + if err != nil { + return err + } + if ok { + err = decoder.Decode(&obj.FreezeAuthority) + if err != nil { + return err + } + } + } + return nil +} + +// NewInitializeMint2Instruction declares a new InitializeMint2 instruction with the provided parameters and accounts. +func NewInitializeMint2Instruction( + // Parameters: + decimals uint8, + mint_authority ag_solanago.PublicKey, + freeze_authority ag_solanago.PublicKey, + // Accounts: + mint ag_solanago.PublicKey) *InitializeMint2 { + return NewInitializeMint2InstructionBuilder(). + SetDecimals(decimals). + SetMintAuthority(mint_authority). + SetFreezeAuthority(freeze_authority). + SetMintAccount(mint) +} diff --git a/programs/token/InitializeMint2_test.go b/programs/token/InitializeMint2_test.go new file mode 100644 index 00000000..eb617f26 --- /dev/null +++ b/programs/token/InitializeMint2_test.go @@ -0,0 +1,31 @@ +package token + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_InitializeMint2(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("InitializeMint2"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(InitializeMint2) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(InitializeMint2) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/InitializeMint_test.go b/programs/token/InitializeMint_test.go new file mode 100644 index 00000000..4a5b096d --- /dev/null +++ b/programs/token/InitializeMint_test.go @@ -0,0 +1,31 @@ +package token + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_InitializeMint(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("InitializeMint"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(InitializeMint) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(InitializeMint) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/InitializeMultisig.go b/programs/token/InitializeMultisig.go new file mode 100644 index 00000000..9938d81f --- /dev/null +++ b/programs/token/InitializeMultisig.go @@ -0,0 +1,211 @@ +package token + +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" +) + +// Initializes a multisignature account with N provided signers. +// +// Multisignature accounts can used in place of any single owner/delegate +// accounts in any token instruction that require an owner/delegate to be +// present. The variant field represents the number of signers (M) +// required to validate this multisignature account. +// +// The `InitializeMultisig` instruction requires no signers and MUST be +// included within the same Transaction as the system program's +// `CreateAccount` instruction that creates the account being initialized. +// Otherwise another party can acquire ownership of the uninitialized +// account. +type InitializeMultisig struct { + // The number of signers (M) required to validate this multisignature + // account. + M *uint8 + + // [0] = [WRITE] account + // ··········· The multisignature account to initialize. + // + // [1] = [] $(SysVarRentPubkey) + // ··········· Rent sysvar. + // + // [2...] = [SIGNER] signers + // ··········· ..2+N The signer accounts, must equal to N where 1 <= N <=11 + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *InitializeMultisig) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + return nil +} + +func (slice InitializeMultisig) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewInitializeMultisigInstructionBuilder creates a new `InitializeMultisig` instruction builder. +func NewInitializeMultisigInstructionBuilder() *InitializeMultisig { + nd := &InitializeMultisig{ + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + nd.Accounts[1] = ag_solanago.Meta(ag_solanago.SysVarRentPubkey) + return nd +} + +// SetM sets the "m" parameter. +// The number of signers (M) required to validate this multisignature +// account. +func (inst *InitializeMultisig) SetM(m uint8) *InitializeMultisig { + inst.M = &m + return inst +} + +// SetAccount sets the "account" account. +// The multisignature account to initialize. +func (inst *InitializeMultisig) SetAccount(account ag_solanago.PublicKey) *InitializeMultisig { + inst.Accounts[0] = ag_solanago.Meta(account).WRITE() + return inst +} + +// GetAccount gets the "account" account. +// The multisignature account to initialize. +func (inst *InitializeMultisig) GetAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetSysVarRentPubkeyAccount sets the "$(SysVarRentPubkey)" account. +// Rent sysvar. +func (inst *InitializeMultisig) SetSysVarRentPubkeyAccount(SysVarRentPubkey ag_solanago.PublicKey) *InitializeMultisig { + inst.Accounts[1] = ag_solanago.Meta(SysVarRentPubkey) + return inst +} + +// GetSysVarRentPubkeyAccount gets the "$(SysVarRentPubkey)" account. +// Rent sysvar. +func (inst *InitializeMultisig) GetSysVarRentPubkeyAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// AddSigners adds the "signers" accounts. +// ..2+N The signer accounts, must equal to N where 1 <= N <=11 +func (inst *InitializeMultisig) AddSigners(signers ...ag_solanago.PublicKey) *InitializeMultisig { + for _, signer := range signers { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} + +func (inst InitializeMultisig) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_InitializeMultisig), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst InitializeMultisig) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InitializeMultisig) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.M == nil { + return errors.New("M parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.Accounts[0] == nil { + return fmt.Errorf("accounts.Account is not set") + } + if inst.Accounts[1] == nil { + return fmt.Errorf("accounts.SysVarRentPubkey is not set") + } + if 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 *InitializeMultisig) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InitializeMultisig")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("M", *inst.M)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" account", inst.Accounts[0])) + accountsBranch.Child(ag_format.Meta("SysVarRent", 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 InitializeMultisig) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `M` param: + err = encoder.Encode(obj.M) + if err != nil { + return err + } + return nil +} +func (obj *InitializeMultisig) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `M`: + err = decoder.Decode(&obj.M) + if err != nil { + return err + } + return nil +} + +// NewInitializeMultisigInstruction declares a new InitializeMultisig instruction with the provided parameters and accounts. +func NewInitializeMultisigInstruction( + // Parameters: + m uint8, + // Accounts: + account ag_solanago.PublicKey, + SysVarRentPubkey ag_solanago.PublicKey, + signers []ag_solanago.PublicKey, +) *InitializeMultisig { + return NewInitializeMultisigInstructionBuilder(). + SetM(m). + SetAccount(account). + SetSysVarRentPubkeyAccount(SysVarRentPubkey). + AddSigners(signers...) +} diff --git a/programs/token/InitializeMultisig2.go b/programs/token/InitializeMultisig2.go new file mode 100644 index 00000000..670e1bc3 --- /dev/null +++ b/programs/token/InitializeMultisig2.go @@ -0,0 +1,175 @@ +package token + +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" +) + +// Like InitializeMultisig, but does not require the Rent sysvar to be provided. +type InitializeMultisig2 struct { + // The number of signers (M) required to validate this multisignature account. + M *uint8 + + // [0] = [WRITE] account + // ··········· The multisignature account to initialize. + // + // [1] = [SIGNER] signers + // ··········· The signer accounts, must equal to N where 1 <= N <= 11. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *InitializeMultisig2) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(1) + return nil +} + +func (slice InitializeMultisig2) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewInitializeMultisig2InstructionBuilder creates a new `InitializeMultisig2` instruction builder. +func NewInitializeMultisig2InstructionBuilder() *InitializeMultisig2 { + nd := &InitializeMultisig2{ + Accounts: make(ag_solanago.AccountMetaSlice, 1), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetM sets the "m" parameter. +// The number of signers (M) required to validate this multisignature account. +func (inst *InitializeMultisig2) SetM(m uint8) *InitializeMultisig2 { + inst.M = &m + return inst +} + +// SetAccount sets the "account" account. +// The multisignature account to initialize. +func (inst *InitializeMultisig2) SetAccount(account ag_solanago.PublicKey) *InitializeMultisig2 { + inst.Accounts[0] = ag_solanago.Meta(account).WRITE() + return inst +} + +// GetAccount gets the "account" account. +// The multisignature account to initialize. +func (inst *InitializeMultisig2) GetAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// AddSigners adds the "signers" accounts. +// The signer accounts, must equal to N where 1 <= N <= 11. +func (inst *InitializeMultisig2) AddSigners(signers ...ag_solanago.PublicKey) *InitializeMultisig2 { + for _, signer := range signers { + inst.Signers = append(inst.Signers, ag_solanago.Meta(signer).SIGNER()) + } + return inst +} + +func (inst InitializeMultisig2) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_InitializeMultisig2), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst InitializeMultisig2) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *InitializeMultisig2) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.M == nil { + return errors.New("M parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.Accounts[0] == nil { + return errors.New("accounts.Account is not set") + } + if 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 *InitializeMultisig2) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("InitializeMultisig2")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("M", *inst.M)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("account", inst.Accounts[0])) + + 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 InitializeMultisig2) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `M` param: + err = encoder.Encode(obj.M) + if err != nil { + return err + } + return nil +} +func (obj *InitializeMultisig2) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `M`: + err = decoder.Decode(&obj.M) + if err != nil { + return err + } + return nil +} + +// NewInitializeMultisig2Instruction declares a new InitializeMultisig2 instruction with the provided parameters and accounts. +func NewInitializeMultisig2Instruction( + // Parameters: + m uint8, + // Accounts: + account ag_solanago.PublicKey, + signers []ag_solanago.PublicKey, +) *InitializeMultisig2 { + return NewInitializeMultisig2InstructionBuilder(). + SetM(m). + SetAccount(account). + AddSigners(signers...) +} diff --git a/programs/token/InitializeMultisig2_test.go b/programs/token/InitializeMultisig2_test.go new file mode 100644 index 00000000..78896de8 --- /dev/null +++ b/programs/token/InitializeMultisig2_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_InitializeMultisig2(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("InitializeMultisig2"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(InitializeMultisig2) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(InitializeMultisig2) + err = decodeT(got, buf.Bytes()) + params.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/InitializeMultisig_test.go b/programs/token/InitializeMultisig_test.go new file mode 100644 index 00000000..93dd5512 --- /dev/null +++ b/programs/token/InitializeMultisig_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_InitializeMultisig(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("InitializeMultisig"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(InitializeMultisig) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(InitializeMultisig) + err = decodeT(got, buf.Bytes()) + got.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/MintTo.go b/programs/token/MintTo.go new file mode 100644 index 00000000..8ec023b0 --- /dev/null +++ b/programs/token/MintTo.go @@ -0,0 +1,216 @@ +package token + +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" +) + +// Mints new tokens to an account. The native mint does not support +// minting. +type MintTo struct { + // The amount of new tokens to mint. + Amount *uint64 + + // [0] = [WRITE] mint + // ··········· The mint. + // + // [1] = [WRITE] destination + // ··········· The account to mint tokens to. + // + // [2] = [] authority + // ··········· The mint's minting authority. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *MintTo) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice MintTo) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewMintToInstructionBuilder creates a new `MintTo` instruction builder. +func NewMintToInstructionBuilder() *MintTo { + nd := &MintTo{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetAmount sets the "amount" parameter. +// The amount of new tokens to mint. +func (inst *MintTo) SetAmount(amount uint64) *MintTo { + inst.Amount = &amount + return inst +} + +// SetMintAccount sets the "mint" account. +// The mint. +func (inst *MintTo) SetMintAccount(mint ag_solanago.PublicKey) *MintTo { + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// GetMintAccount gets the "mint" account. +// The mint. +func (inst *MintTo) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetDestinationAccount sets the "destination" account. +// The account to mint tokens to. +func (inst *MintTo) SetDestinationAccount(destination ag_solanago.PublicKey) *MintTo { + inst.Accounts[1] = ag_solanago.Meta(destination).WRITE() + return inst +} + +// GetDestinationAccount gets the "destination" account. +// The account to mint tokens to. +func (inst *MintTo) GetDestinationAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetAuthorityAccount sets the "authority" account. +// The mint's minting authority. +func (inst *MintTo) SetAuthorityAccount(authority ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *MintTo { + 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 minting authority. +func (inst *MintTo) GetAuthorityAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst MintTo) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_MintTo), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst MintTo) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *MintTo) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Amount == nil { + return errors.New("Amount 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)) + } + } + return nil +} + +func (inst *MintTo) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("MintTo")). + // + 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)) + }) + + // 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 MintTo) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `Amount` param: + err = encoder.Encode(obj.Amount) + if err != nil { + return err + } + return nil +} +func (obj *MintTo) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `Amount`: + err = decoder.Decode(&obj.Amount) + if err != nil { + return err + } + return nil +} + +// NewMintToInstruction declares a new MintTo instruction with the provided parameters and accounts. +func NewMintToInstruction( + // Parameters: + amount uint64, + // Accounts: + mint ag_solanago.PublicKey, + destination ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *MintTo { + return NewMintToInstructionBuilder(). + SetAmount(amount). + SetMintAccount(mint). + SetDestinationAccount(destination). + SetAuthorityAccount(authority, multisigSigners...) +} diff --git a/programs/token/MintToChecked.go b/programs/token/MintToChecked.go new file mode 100644 index 00000000..090cc890 --- /dev/null +++ b/programs/token/MintToChecked.go @@ -0,0 +1,245 @@ +package token + +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" +) + +// Mints new tokens to an account. The native mint does not support minting. +// +// This instruction differs from MintTo in that the decimals value is +// checked by the caller. This may be useful when creating transactions +// offline or within a hardware wallet. +type MintToChecked struct { + // The amount of new tokens to mint. + Amount *uint64 + + // Expected number of base 10 digits to the right of the decimal place. + Decimals *uint8 + + // [0] = [WRITE] mint + // ··········· The mint. + // + // [1] = [WRITE] destination + // ··········· The account to mint tokens to. + // + // [2] = [] authority + // ··········· The mint's minting authority. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *MintToChecked) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice MintToChecked) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewMintToCheckedInstructionBuilder creates a new `MintToChecked` instruction builder. +func NewMintToCheckedInstructionBuilder() *MintToChecked { + nd := &MintToChecked{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetAmount sets the "amount" parameter. +// The amount of new tokens to mint. +func (inst *MintToChecked) SetAmount(amount uint64) *MintToChecked { + inst.Amount = &amount + return inst +} + +// SetDecimals sets the "decimals" parameter. +// Expected number of base 10 digits to the right of the decimal place. +func (inst *MintToChecked) SetDecimals(decimals uint8) *MintToChecked { + inst.Decimals = &decimals + return inst +} + +// SetMintAccount sets the "mint" account. +// The mint. +func (inst *MintToChecked) SetMintAccount(mint ag_solanago.PublicKey) *MintToChecked { + inst.Accounts[0] = ag_solanago.Meta(mint).WRITE() + return inst +} + +// GetMintAccount gets the "mint" account. +// The mint. +func (inst *MintToChecked) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetDestinationAccount sets the "destination" account. +// The account to mint tokens to. +func (inst *MintToChecked) SetDestinationAccount(destination ag_solanago.PublicKey) *MintToChecked { + inst.Accounts[1] = ag_solanago.Meta(destination).WRITE() + return inst +} + +// GetDestinationAccount gets the "destination" account. +// The account to mint tokens to. +func (inst *MintToChecked) GetDestinationAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetAuthorityAccount sets the "authority" account. +// The mint's minting authority. +func (inst *MintToChecked) SetAuthorityAccount(authority ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *MintToChecked { + 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 minting authority. +func (inst *MintToChecked) GetAuthorityAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst MintToChecked) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_MintToChecked), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst MintToChecked) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *MintToChecked) 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.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 *MintToChecked) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("MintToChecked")). + // + 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)) + }) + + // 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 MintToChecked) 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 + } + return nil +} +func (obj *MintToChecked) 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 + } + return nil +} + +// NewMintToCheckedInstruction declares a new MintToChecked instruction with the provided parameters and accounts. +func NewMintToCheckedInstruction( + // Parameters: + amount uint64, + decimals uint8, + // Accounts: + mint ag_solanago.PublicKey, + destination ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *MintToChecked { + return NewMintToCheckedInstructionBuilder(). + SetAmount(amount). + SetDecimals(decimals). + SetMintAccount(mint). + SetDestinationAccount(destination). + SetAuthorityAccount(authority, multisigSigners...) +} diff --git a/programs/token/MintToChecked_test.go b/programs/token/MintToChecked_test.go new file mode 100644 index 00000000..95b83da6 --- /dev/null +++ b/programs/token/MintToChecked_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_MintToChecked(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("MintToChecked"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(MintToChecked) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(MintToChecked) + err = decodeT(got, buf.Bytes()) + params.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/MintTo_test.go b/programs/token/MintTo_test.go new file mode 100644 index 00000000..1afe5711 --- /dev/null +++ b/programs/token/MintTo_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_MintTo(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("MintTo"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(MintTo) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(MintTo) + err = decodeT(got, buf.Bytes()) + params.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/Revoke.go b/programs/token/Revoke.go new file mode 100644 index 00000000..5067fa9b --- /dev/null +++ b/programs/token/Revoke.go @@ -0,0 +1,162 @@ +package token + +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" +) + +// Revokes the delegate's authority. +type Revoke struct { + + // [0] = [WRITE] source + // ··········· The source account. + // + // [1] = [] owner + // ··········· The source account's owner. + // + // [2...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *Revoke) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + return nil +} + +func (slice Revoke) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewRevokeInstructionBuilder creates a new `Revoke` instruction builder. +func NewRevokeInstructionBuilder() *Revoke { + nd := &Revoke{ + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetSourceAccount sets the "source" account. +// The source account. +func (inst *Revoke) SetSourceAccount(source ag_solanago.PublicKey) *Revoke { + inst.Accounts[0] = ag_solanago.Meta(source).WRITE() + return inst +} + +// GetSourceAccount gets the "source" account. +// The source account. +func (inst *Revoke) GetSourceAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetOwnerAccount sets the "owner" account. +// The source account's owner. +func (inst *Revoke) SetOwnerAccount(owner ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *Revoke { + 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 +} + +// GetOwnerAccount gets the "owner" account. +// The source account's owner. +func (inst *Revoke) GetOwnerAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +func (inst Revoke) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_Revoke), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst Revoke) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *Revoke) Validate() error { + // 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.Owner 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 *Revoke) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("Revoke")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // 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(" owner", 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 Revoke) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} +func (obj *Revoke) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +// NewRevokeInstruction declares a new Revoke instruction with the provided parameters and accounts. +func NewRevokeInstruction( + // Accounts: + source ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *Revoke { + return NewRevokeInstructionBuilder(). + SetSourceAccount(source). + SetOwnerAccount(owner, multisigSigners...) +} diff --git a/programs/token/Revoke_test.go b/programs/token/Revoke_test.go new file mode 100644 index 00000000..680138c7 --- /dev/null +++ b/programs/token/Revoke_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_Revoke(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("Revoke"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(Revoke) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(Revoke) + err = decodeT(got, buf.Bytes()) + params.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/SetAuthority.go b/programs/token/SetAuthority.go new file mode 100644 index 00000000..5fc7eed1 --- /dev/null +++ b/programs/token/SetAuthority.go @@ -0,0 +1,237 @@ +package token + +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" +) + +// Sets a new authority of a mint or account. +type SetAuthority struct { + // The type of authority to update. + AuthorityType *AuthorityType + + // The new authority. + NewAuthority *ag_solanago.PublicKey `bin:"optional"` + + // [0] = [WRITE] subject + // ··········· The mint or account to change the authority of. + // + // [1] = [] authority + // ··········· The current authority of the mint or account. + // + // [2...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *SetAuthority) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(2) + return nil +} + +func (slice SetAuthority) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewSetAuthorityInstructionBuilder creates a new `SetAuthority` instruction builder. +func NewSetAuthorityInstructionBuilder() *SetAuthority { + nd := &SetAuthority{ + Accounts: make(ag_solanago.AccountMetaSlice, 2), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetAuthorityType sets the "authority_type" parameter. +// The type of authority to update. +func (inst *SetAuthority) SetAuthorityType(authority_type AuthorityType) *SetAuthority { + inst.AuthorityType = &authority_type + return inst +} + +// SetNewAuthority sets the "new_authority" parameter. +// The new authority. +func (inst *SetAuthority) SetNewAuthority(new_authority ag_solanago.PublicKey) *SetAuthority { + inst.NewAuthority = &new_authority + return inst +} + +// SetSubjectAccount sets the "subject" account. +// The mint or account to change the authority of. +func (inst *SetAuthority) SetSubjectAccount(subject ag_solanago.PublicKey) *SetAuthority { + inst.Accounts[0] = ag_solanago.Meta(subject).WRITE() + return inst +} + +// GetSubjectAccount gets the "subject" account. +// The mint or account to change the authority of. +func (inst *SetAuthority) GetSubjectAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetAuthorityAccount sets the "authority" account. +// The current authority of the mint or account. +func (inst *SetAuthority) SetAuthorityAccount(authority ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *SetAuthority { + 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 current authority of the mint or account. +func (inst *SetAuthority) GetAuthorityAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +func (inst SetAuthority) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_SetAuthority), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst SetAuthority) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *SetAuthority) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.AuthorityType == nil { + return errors.New("AuthorityType parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.Accounts[0] == nil { + return errors.New("accounts.Subject 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 *SetAuthority) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("SetAuthority")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param(" AuthorityType", *inst.AuthorityType)) + paramsBranch.Child(ag_format.Param("NewAuthority (OPT)", inst.NewAuthority)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" subject", 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 SetAuthority) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `AuthorityType` param: + err = encoder.Encode(obj.AuthorityType) + if err != nil { + return err + } + // Serialize `NewAuthority` param (optional): + { + if obj.NewAuthority == nil { + err = encoder.WriteBool(false) + if err != nil { + return err + } + } else { + err = encoder.WriteBool(true) + if err != nil { + return err + } + err = encoder.Encode(obj.NewAuthority) + if err != nil { + return err + } + } + } + return nil +} +func (obj *SetAuthority) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `AuthorityType`: + err = decoder.Decode(&obj.AuthorityType) + if err != nil { + return err + } + // Deserialize `NewAuthority` (optional): + { + ok, err := decoder.ReadBool() + if err != nil { + return err + } + if ok { + err = decoder.Decode(&obj.NewAuthority) + if err != nil { + return err + } + } + } + return nil +} + +// NewSetAuthorityInstruction declares a new SetAuthority instruction with the provided parameters and accounts. +func NewSetAuthorityInstruction( + // Parameters: + authority_type AuthorityType, + new_authority ag_solanago.PublicKey, + // Accounts: + subject ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *SetAuthority { + return NewSetAuthorityInstructionBuilder(). + SetAuthorityType(authority_type). + SetNewAuthority(new_authority). + SetSubjectAccount(subject). + SetAuthorityAccount(authority, multisigSigners...) +} diff --git a/programs/token/SetAuthority_test.go b/programs/token/SetAuthority_test.go new file mode 100644 index 00000000..b4ec4006 --- /dev/null +++ b/programs/token/SetAuthority_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_SetAuthority(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("SetAuthority"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(SetAuthority) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(SetAuthority) + err = decodeT(got, buf.Bytes()) + params.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/SyncNative.go b/programs/token/SyncNative.go new file mode 100644 index 00000000..b753e740 --- /dev/null +++ b/programs/token/SyncNative.go @@ -0,0 +1,104 @@ +package token + +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" +) + +// Given a wrapped / native token account (a token account containing SOL) +// updates its amount field based on the account's underlying `lamports`. +// This is useful if a non-wrapped SOL account uses `system_instruction::transfer` +// to move lamports to a wrapped token account, and needs to have its token +// `amount` field updated. +type SyncNative struct { + + // [0] = [WRITE] tokenAccount + // ··········· The native token account to sync with its underlying lamports. + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewSyncNativeInstructionBuilder creates a new `SyncNative` instruction builder. +func NewSyncNativeInstructionBuilder() *SyncNative { + nd := &SyncNative{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 1), + } + return nd +} + +// SetTokenAccount sets the "tokenAccount" account. +// The native token account to sync with its underlying lamports. +func (inst *SyncNative) SetTokenAccount(tokenAccount ag_solanago.PublicKey) *SyncNative { + inst.AccountMetaSlice[0] = ag_solanago.Meta(tokenAccount).WRITE() + return inst +} + +// GetTokenAccount gets the "tokenAccount" account. +// The native token account to sync with its underlying lamports. +func (inst *SyncNative) GetTokenAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +func (inst SyncNative) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_SyncNative), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst SyncNative) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *SyncNative) Validate() error { + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.TokenAccount is not set") + } + } + return nil +} + +func (inst *SyncNative) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("SyncNative")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta("tokenAccount", inst.AccountMetaSlice[0])) + }) + }) + }) +} + +func (obj SyncNative) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} +func (obj *SyncNative) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +// NewSyncNativeInstruction declares a new SyncNative instruction with the provided parameters and accounts. +func NewSyncNativeInstruction( + // Accounts: + tokenAccount ag_solanago.PublicKey) *SyncNative { + return NewSyncNativeInstructionBuilder(). + SetTokenAccount(tokenAccount) +} diff --git a/programs/token/SyncNative_test.go b/programs/token/SyncNative_test.go new file mode 100644 index 00000000..b3238aca --- /dev/null +++ b/programs/token/SyncNative_test.go @@ -0,0 +1,31 @@ +package token + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_SyncNative(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("SyncNative"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(SyncNative) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(SyncNative) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/ThawAccount.go b/programs/token/ThawAccount.go new file mode 100644 index 00000000..90aaf081 --- /dev/null +++ b/programs/token/ThawAccount.go @@ -0,0 +1,184 @@ +package token + +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" +) + +// Thaw a Frozen account using the Mint's freeze_authority (if set). +type ThawAccount struct { + + // [0] = [WRITE] account + // ··········· The account to thaw. + // + // [1] = [] mint + // ··········· The token mint. + // + // [2] = [] authority + // ··········· The mint freeze authority. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *ThawAccount) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice ThawAccount) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewThawAccountInstructionBuilder creates a new `ThawAccount` instruction builder. +func NewThawAccountInstructionBuilder() *ThawAccount { + nd := &ThawAccount{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetAccount sets the "account" account. +// The account to thaw. +func (inst *ThawAccount) SetAccount(account ag_solanago.PublicKey) *ThawAccount { + inst.Accounts[0] = ag_solanago.Meta(account).WRITE() + return inst +} + +// GetAccount gets the "account" account. +// The account to thaw. +func (inst *ThawAccount) GetAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetMintAccount sets the "mint" account. +// The token mint. +func (inst *ThawAccount) SetMintAccount(mint ag_solanago.PublicKey) *ThawAccount { + inst.Accounts[1] = ag_solanago.Meta(mint) + return inst +} + +// GetMintAccount gets the "mint" account. +// The token mint. +func (inst *ThawAccount) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetAuthorityAccount sets the "authority" account. +// The mint freeze authority. +func (inst *ThawAccount) SetAuthorityAccount(authority ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *ThawAccount { + 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 freeze authority. +func (inst *ThawAccount) GetAuthorityAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst ThawAccount) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_ThawAccount), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst ThawAccount) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *ThawAccount) Validate() error { + // Check whether all (required) accounts are set: + { + if inst.Accounts[0] == nil { + return errors.New("accounts.Account 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.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 *ThawAccount) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("ThawAccount")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) + + // Accounts of the instruction: + instructionBranch.Child("Accounts").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" account", inst.Accounts[0])) + accountsBranch.Child(ag_format.Meta(" mint", 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 ThawAccount) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + return nil +} +func (obj *ThawAccount) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + return nil +} + +// NewThawAccountInstruction declares a new ThawAccount instruction with the provided parameters and accounts. +func NewThawAccountInstruction( + // Accounts: + account ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + authority ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *ThawAccount { + return NewThawAccountInstructionBuilder(). + SetAccount(account). + SetMintAccount(mint). + SetAuthorityAccount(authority, multisigSigners...) +} diff --git a/programs/token/ThawAccount_test.go b/programs/token/ThawAccount_test.go new file mode 100644 index 00000000..338074f8 --- /dev/null +++ b/programs/token/ThawAccount_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_ThawAccount(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("ThawAccount"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(ThawAccount) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(ThawAccount) + err = decodeT(got, buf.Bytes()) + params.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/Transfer.go b/programs/token/Transfer.go new file mode 100644 index 00000000..6104c0eb --- /dev/null +++ b/programs/token/Transfer.go @@ -0,0 +1,217 @@ +package token + +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. +type Transfer struct { + // The amount of tokens to transfer. + Amount *uint64 + + // [0] = [WRITE] source + // ··········· The source account. + // + // [1] = [WRITE] destination + // ··········· The destination account. + // + // [2] = [] owner + // ··········· The source account owner/delegate. + // + // [3...] = [SIGNER] signers + // ··········· M signer accounts. + Accounts ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` + Signers ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +func (obj *Transfer) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(3) + return nil +} + +func (slice Transfer) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewTransferInstructionBuilder creates a new `Transfer` instruction builder. +func NewTransferInstructionBuilder() *Transfer { + nd := &Transfer{ + Accounts: make(ag_solanago.AccountMetaSlice, 3), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetAmount sets the "amount" parameter. +// The amount of tokens to transfer. +func (inst *Transfer) SetAmount(amount uint64) *Transfer { + inst.Amount = &amount + return inst +} + +// SetSourceAccount sets the "source" account. +// The source account. +func (inst *Transfer) SetSourceAccount(source ag_solanago.PublicKey) *Transfer { + inst.Accounts[0] = ag_solanago.Meta(source).WRITE() + return inst +} + +// GetSourceAccount gets the "source" account. +// The source account. +func (inst *Transfer) GetSourceAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetDestinationAccount sets the "destination" account. +// The destination account. +func (inst *Transfer) SetDestinationAccount(destination ag_solanago.PublicKey) *Transfer { + inst.Accounts[1] = ag_solanago.Meta(destination).WRITE() + return inst +} + +// GetDestinationAccount gets the "destination" account. +// The destination account. +func (inst *Transfer) GetDestinationAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetOwnerAccount sets the "owner" account. +// The source account owner/delegate. +func (inst *Transfer) SetOwnerAccount(owner ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *Transfer { + 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 +} + +// GetOwnerAccount gets the "owner" account. +// The source account owner/delegate. +func (inst *Transfer) GetOwnerAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +func (inst Transfer) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_Transfer), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst Transfer) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *Transfer) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.Amount == nil { + return errors.New("Amount parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.Accounts[0] == nil { + return fmt.Errorf("accounts.Source is not set") + } + if inst.Accounts[1] == nil { + return fmt.Errorf("accounts.Destination is not set") + } + if inst.Accounts[2] == nil { + return fmt.Errorf("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 *Transfer) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("Transfer")). + // + 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)) + }) + + // 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("destination", inst.Accounts[1])) + accountsBranch.Child(ag_format.Meta(" owner", 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 Transfer) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `Amount` param: + err = encoder.Encode(obj.Amount) + if err != nil { + return err + } + return nil +} +func (obj *Transfer) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `Amount`: + err = decoder.Decode(&obj.Amount) + if err != nil { + return err + } + return nil +} + +// NewTransferInstruction declares a new Transfer instruction with the provided parameters and accounts. +func NewTransferInstruction( + // Parameters: + amount uint64, + // Accounts: + source ag_solanago.PublicKey, + destination ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey) *Transfer { + return NewTransferInstructionBuilder(). + SetAmount(amount). + SetSourceAccount(source). + SetDestinationAccount(destination). + SetOwnerAccount(owner, multisigSigners...) +} diff --git a/programs/token/TransferChecked.go b/programs/token/TransferChecked.go new file mode 100644 index 00000000..7cbd533a --- /dev/null +++ b/programs/token/TransferChecked.go @@ -0,0 +1,270 @@ +package token + +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 TransferChecked struct { + // The amount of tokens to transfer. + Amount *uint64 + + // Expected number of base 10 digits to the right of the decimal place. + Decimals *uint8 + + // [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 *TransferChecked) SetAccounts(accounts []*ag_solanago.AccountMeta) error { + obj.Accounts, obj.Signers = ag_solanago.AccountMetaSlice(accounts).SplitFrom(4) + return nil +} + +func (slice TransferChecked) GetAccounts() (accounts []*ag_solanago.AccountMeta) { + accounts = append(accounts, slice.Accounts...) + accounts = append(accounts, slice.Signers...) + return +} + +// NewTransferCheckedInstructionBuilder creates a new `TransferChecked` instruction builder. +func NewTransferCheckedInstructionBuilder() *TransferChecked { + nd := &TransferChecked{ + Accounts: make(ag_solanago.AccountMetaSlice, 4), + Signers: make(ag_solanago.AccountMetaSlice, 0), + } + return nd +} + +// SetAmount sets the "amount" parameter. +// The amount of tokens to transfer. +func (inst *TransferChecked) SetAmount(amount uint64) *TransferChecked { + inst.Amount = &amount + return inst +} + +// SetDecimals sets the "decimals" parameter. +// Expected number of base 10 digits to the right of the decimal place. +func (inst *TransferChecked) SetDecimals(decimals uint8) *TransferChecked { + inst.Decimals = &decimals + return inst +} + +// SetSourceAccount sets the "source" account. +// The source account. +func (inst *TransferChecked) SetSourceAccount(source ag_solanago.PublicKey) *TransferChecked { + inst.Accounts[0] = ag_solanago.Meta(source).WRITE() + return inst +} + +// GetSourceAccount gets the "source" account. +// The source account. +func (inst *TransferChecked) GetSourceAccount() *ag_solanago.AccountMeta { + return inst.Accounts[0] +} + +// SetMintAccount sets the "mint" account. +// The token mint. +func (inst *TransferChecked) SetMintAccount(mint ag_solanago.PublicKey) *TransferChecked { + inst.Accounts[1] = ag_solanago.Meta(mint) + return inst +} + +// GetMintAccount gets the "mint" account. +// The token mint. +func (inst *TransferChecked) GetMintAccount() *ag_solanago.AccountMeta { + return inst.Accounts[1] +} + +// SetDestinationAccount sets the "destination" account. +// The destination account. +func (inst *TransferChecked) SetDestinationAccount(destination ag_solanago.PublicKey) *TransferChecked { + inst.Accounts[2] = ag_solanago.Meta(destination).WRITE() + return inst +} + +// GetDestinationAccount gets the "destination" account. +// The destination account. +func (inst *TransferChecked) GetDestinationAccount() *ag_solanago.AccountMeta { + return inst.Accounts[2] +} + +// SetOwnerAccount sets the "owner" account. +// The source account's owner/delegate. +func (inst *TransferChecked) SetOwnerAccount(owner ag_solanago.PublicKey, multisigSigners ...ag_solanago.PublicKey) *TransferChecked { + 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 +} + +// GetOwnerAccount gets the "owner" account. +// The source account's owner/delegate. +func (inst *TransferChecked) GetOwnerAccount() *ag_solanago.AccountMeta { + return inst.Accounts[3] +} + +func (inst TransferChecked) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: ag_binary.TypeIDFromUint8(Instruction_TransferChecked), + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst TransferChecked) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *TransferChecked) 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 *TransferChecked) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("TransferChecked")). + // + 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)) + }) + + // 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 TransferChecked) 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 + } + return nil +} +func (obj *TransferChecked) 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 + } + return nil +} + +// NewTransferCheckedInstruction declares a new TransferChecked instruction with the provided parameters and accounts. +func NewTransferCheckedInstruction( + // Parameters: + amount uint64, + decimals uint8, + // Accounts: + source ag_solanago.PublicKey, + mint ag_solanago.PublicKey, + destination ag_solanago.PublicKey, + owner ag_solanago.PublicKey, + multisigSigners []ag_solanago.PublicKey, +) *TransferChecked { + return NewTransferCheckedInstructionBuilder(). + SetAmount(amount). + SetDecimals(decimals). + SetSourceAccount(source). + SetMintAccount(mint). + SetDestinationAccount(destination). + SetOwnerAccount(owner, multisigSigners...) +} diff --git a/programs/token/TransferChecked_test.go b/programs/token/TransferChecked_test.go new file mode 100644 index 00000000..393464b7 --- /dev/null +++ b/programs/token/TransferChecked_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_TransferChecked(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("TransferChecked"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(TransferChecked) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(TransferChecked) + err = decodeT(got, buf.Bytes()) + params.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/Transfer_test.go b/programs/token/Transfer_test.go new file mode 100644 index 00000000..2ab64bdd --- /dev/null +++ b/programs/token/Transfer_test.go @@ -0,0 +1,34 @@ +package token + +import ( + "bytes" + "strconv" + "testing" + + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" +) + +func TestEncodeDecode_Transfer(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("Transfer"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(Transfer) + fu.Fuzz(params) + params.Accounts = nil + params.Signers = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + // + got := new(Transfer) + err = decodeT(got, buf.Bytes()) + got.Accounts = nil + params.Signers = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/programs/token/accounts.go b/programs/token/accounts.go new file mode 100644 index 00000000..7265b780 --- /dev/null +++ b/programs/token/accounts.go @@ -0,0 +1,51 @@ +package token + +import ag_solanago "github.com/gagliardetto/solana-go" + +type Mint struct { + // Optional authority used to mint new tokens. The mint authority may only be provided during + // mint creation. If no mint authority is present then the mint has a fixed supply and no + // further tokens may be minted. + MintAuthority *ag_solanago.PublicKey `bin:"optional"` + + // Total supply of tokens. + Supply uint64 + + // Number of base 10 digits to the right of the decimal place. + Decimals uint8 + + // Is `true` if this structure has been initialized + IsInitialized bool + + // Optional authority to freeze token accounts. + FreezeAuthority *ag_solanago.PublicKey `bin:"optional"` +} + +type Account struct { + // The mint associated with this account + Mint ag_solanago.PublicKey + + // The owner of this account. + Owner ag_solanago.PublicKey + + // The amount of tokens this account holds. + Amount uint64 + + // If `delegate` is `Some` then `delegated_amount` represents + // the amount authorized by the delegate + Delegate *ag_solanago.PublicKey `bin:"optional"` + + // The account's state + State AccountState + + // If is_some, this is a native token, and the value logs the rent-exempt reserve. An Account + // is required to be rent-exempt, so the value is used by the Processor to ensure that wrapped + // SOL accounts do not drop below this threshold. + IsNative *uint64 `bin:"optional"` + + // The amount delegated + DelegatedAmount uint64 + + // Optional authority to close the account. + CloseAuthority *ag_solanago.PublicKey `bin:"optional"` +} diff --git a/programs/token/instructions.go b/programs/token/instructions.go index 68bbd17d..a7b13372 100644 --- a/programs/token/instructions.go +++ b/programs/token/instructions.go @@ -1,205 +1,346 @@ -// Copyright 2020 dfuse Platform Inc. -// -// 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. +// A Token program on the Solana blockchain. +// This program defines a common implementation for Fungible and Non Fungible tokens. package token import ( + "bytes" "fmt" - "github.com/gagliardetto/solana-go/text" - - bin "github.com/gagliardetto/binary" - "github.com/gagliardetto/solana-go" + 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" ) -var TOKEN_PROGRAM_ID = solana.MustPublicKeyFromBase58("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") +const MAX_SIGNERS = 11 -func init() { - solana.RegisterInstructionDecoder(TOKEN_PROGRAM_ID, registryDecodeInstruction) -} +var ProgramID ag_solanago.PublicKey = ag_solanago.TokenProgramID -func registryDecodeInstruction(accounts []*solana.AccountMeta, data []byte) (interface{}, error) { - inst, err := DecodeInstruction(accounts, data) - if err != nil { - return nil, err - } - return inst, nil +func SetProgramID(pubkey ag_solanago.PublicKey) { + ProgramID = pubkey + ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) } -func DecodeInstruction(accounts []*solana.AccountMeta, data []byte) (*Instruction, error) { - var inst Instruction - if err := bin.NewBinDecoder(data).Decode(&inst); err != nil { - return nil, fmt.Errorf("unable to decode instruction for serum program: %w", err) - } +const ProgramName = "Token" - if v, ok := inst.Impl.(solana.AccountsSettable); ok { - err := v.SetAccounts(accounts) - if err != nil { - return nil, fmt.Errorf("unable to set accounts for instruction: %w", err) - } +func init() { + if !ProgramID.IsZero() { + ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) } - - return &inst, nil -} - -var InstructionDefVariant = bin.NewVariantDefinition(bin.Uint8TypeIDEncoding, []bin.VariantType{ - {"initialize_mint", (*InitializeMint)(nil)}, - {"initialize_account", (*InitializeAccount)(nil)}, - {"InitializeMultisig", (*InitializeMultisig)(nil)}, - {"Transfer", (*Transfer)(nil)}, - {"Approve", (*Approve)(nil)}, - {"Revoke", (*Revoke)(nil)}, - {"SetAuthority", (*SetAuthority)(nil)}, - {"MintTo", (*MintTo)(nil)}, - {"Burn", (*Burn)(nil)}, - {"CloseAccount", (*CloseAccount)(nil)}, - {"FreezeAccount", (*FreezeAccount)(nil)}, - {"ThawAccount", (*ThawAccount)(nil)}, - {"TransferChecked", (*TransferChecked)(nil)}, - {"ApproveChecked", (*ApproveChecked)(nil)}, - {"MintToChecked", (*MintToChecked)(nil)}, - {"BurnChecked", (*BurnChecked)(nil)}, -}) - -type Instruction struct { - bin.BaseVariant } -var _ bin.EncoderDecoder = &Instruction{} - -func (i *Instruction) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) { - return i.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionDefVariant) -} +const ( + // Initializes a new mint and optionally deposits all the newly minted + // tokens in an account. + // + // The `InitializeMint` instruction requires no signers and MUST be + // included within the same Transaction as the system program's + // `CreateAccount` instruction that creates the account being initialized. + // Otherwise another party can acquire ownership of the uninitialized + // account. + Instruction_InitializeMint uint8 = iota + + // Initializes a new account to hold tokens. If this account is associated + // with the native mint then the token balance of the initialized account + // will be equal to the amount of SOL in the account. If this account is + // associated with another mint, that mint must be initialized before this + // command can succeed. + // + // The `InitializeAccount` instruction requires no signers and MUST be + // included within the same Transaction as the system program's + // `CreateAccount` instruction that creates the account being initialized. + // Otherwise another party can acquire ownership of the uninitialized + // account. + Instruction_InitializeAccount + + // Initializes a multisignature account with N provided signers. + // + // Multisignature accounts can used in place of any single owner/delegate + // accounts in any token instruction that require an owner/delegate to be + // present. The variant field represents the number of signers (M) + // required to validate this multisignature account. + // + // The `InitializeMultisig` instruction requires no signers and MUST be + // included within the same Transaction as the system program's + // `CreateAccount` instruction that creates the account being initialized. + // Otherwise another party can acquire ownership of the uninitialized + // account. + Instruction_InitializeMultisig + + // 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. + Instruction_Transfer + + // Approves a delegate. A delegate is given the authority over tokens on + // behalf of the source account's owner. + Instruction_Approve + + // Revokes the delegate's authority. + Instruction_Revoke + + // Sets a new authority of a mint or account. + Instruction_SetAuthority + + // Mints new tokens to an account. The native mint does not support + // minting. + Instruction_MintTo + + // Burns tokens by removing them from an account. `Burn` does not support + // accounts associated with the native mint, use `CloseAccount` instead. + Instruction_Burn + + // Close an account by transferring all its SOL to the destination account. + // Non-native accounts may only be closed if its token amount is zero. + Instruction_CloseAccount + + // Freeze an Initialized account using the Mint's freeze_authority (if set). + Instruction_FreezeAccount + + // Thaw a Frozen account using the Mint's freeze_authority (if set). + Instruction_ThawAccount + + // 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. + Instruction_TransferChecked + + // Approves a delegate. A delegate is given the authority over tokens on + // behalf of the source account's owner. + // + // This instruction differs from Approve 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. + Instruction_ApproveChecked + + // Mints new tokens to an account. The native mint does not support minting. + // + // This instruction differs from MintTo in that the decimals value is + // checked by the caller. This may be useful when creating transactions + // offline or within a hardware wallet. + Instruction_MintToChecked + + // Burns tokens by removing them from an account. `BurnChecked` does not + // support accounts associated with the native mint, use `CloseAccount` + // instead. + // + // This instruction differs from Burn in that the decimals value is checked + // by the caller. This may be useful when creating transactions offline or + // within a hardware wallet. + Instruction_BurnChecked + + // Like InitializeAccount, but the owner pubkey is passed via instruction data + // rather than the accounts list. This variant may be preferable when using + // Cross Program Invocation from an instruction that does not need the owner's + // `AccountInfo` otherwise. + Instruction_InitializeAccount2 + + // Given a wrapped / native token account (a token account containing SOL) + // updates its amount field based on the account's underlying `lamports`. + // This is useful if a non-wrapped SOL account uses `system_instruction::transfer` + // to move lamports to a wrapped token account, and needs to have its token + // `amount` field updated. + Instruction_SyncNative + + // Like InitializeAccount2, but does not require the Rent sysvar to be provided. + Instruction_InitializeAccount3 + + // Like InitializeMultisig, but does not require the Rent sysvar to be provided. + Instruction_InitializeMultisig2 + + // Like InitializeMint, but does not require the Rent sysvar to be provided. + Instruction_InitializeMint2 +) -func (i *Instruction) MarshalWithEncoder(encoder *bin.Encoder) error { - err := encoder.WriteUint8(i.TypeID.Uint8()) - if err != nil { - return fmt.Errorf("unable to write variant type: %w", err) +// InstructionIDToName returns the name of the instruction given its ID. +func InstructionIDToName(id uint8) string { + switch id { + case Instruction_InitializeMint: + return "InitializeMint" + case Instruction_InitializeAccount: + return "InitializeAccount" + case Instruction_InitializeMultisig: + return "InitializeMultisig" + case Instruction_Transfer: + return "Transfer" + case Instruction_Approve: + return "Approve" + case Instruction_Revoke: + return "Revoke" + case Instruction_SetAuthority: + return "SetAuthority" + case Instruction_MintTo: + return "MintTo" + case Instruction_Burn: + return "Burn" + case Instruction_CloseAccount: + return "CloseAccount" + case Instruction_FreezeAccount: + return "FreezeAccount" + case Instruction_ThawAccount: + return "ThawAccount" + case Instruction_TransferChecked: + return "TransferChecked" + case Instruction_ApproveChecked: + return "ApproveChecked" + case Instruction_MintToChecked: + return "MintToChecked" + case Instruction_BurnChecked: + return "BurnChecked" + case Instruction_InitializeAccount2: + return "InitializeAccount2" + case Instruction_SyncNative: + return "SyncNative" + case Instruction_InitializeAccount3: + return "InitializeAccount3" + case Instruction_InitializeMultisig2: + return "InitializeMultisig2" + case Instruction_InitializeMint2: + return "InitializeMint2" + default: + return "" } - return encoder.Encode(i.Impl) -} -func (i *Instruction) TextEncode(encoder *text.Encoder, option *text.Option) error { - return encoder.Encode(i.Impl, option) -} - -type InitializeMultisigAccounts struct { -} -type InitializeMultisig struct { - Accounts *InitializeMultisigAccounts -} - -type InitializeMintAccounts struct { -} -type InitializeMint struct { - Accounts *InitializeMintAccounts -} - -type TransferAccounts struct { -} -type Transfer struct { - Accounts *TransferAccounts -} - -type ApproveAccounts struct { -} -type Approve struct { - Accounts *ApproveAccounts } -type RevokeAccounts struct { -} -type Revoke struct { - Accounts *RevokeAccounts -} - -type SetAuthorityAccounts struct { -} -type SetAuthority struct { - Accounts *SetAuthorityAccounts -} - -type MintToAccounts struct { -} -type MintTo struct { - Accounts *MintToAccounts -} - -type BurnAccounts struct { -} -type Burn struct { - Accounts *BurnAccounts +type Instruction struct { + ag_binary.BaseVariant } -type CloseAccountAccounts struct { -} -type CloseAccount struct { - Accounts *CloseAccountAccounts +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)) + } } -type FreezeAccountAccounts struct { -} -type FreezeAccount struct { - Accounts *FreezeAccountAccounts -} +var InstructionImplDef = ag_binary.NewVariantDefinition( + ag_binary.Uint8TypeIDEncoding, + []ag_binary.VariantType{ + { + "InitializeMint", (*InitializeMint)(nil), + }, + { + "InitializeAccount", (*InitializeAccount)(nil), + }, + { + "InitializeMultisig", (*InitializeMultisig)(nil), + }, + { + "Transfer", (*Transfer)(nil), + }, + { + "Approve", (*Approve)(nil), + }, + { + "Revoke", (*Revoke)(nil), + }, + { + "SetAuthority", (*SetAuthority)(nil), + }, + { + "MintTo", (*MintTo)(nil), + }, + { + "Burn", (*Burn)(nil), + }, + { + "CloseAccount", (*CloseAccount)(nil), + }, + { + "FreezeAccount", (*FreezeAccount)(nil), + }, + { + "ThawAccount", (*ThawAccount)(nil), + }, + { + "TransferChecked", (*TransferChecked)(nil), + }, + { + "ApproveChecked", (*ApproveChecked)(nil), + }, + { + "MintToChecked", (*MintToChecked)(nil), + }, + { + "BurnChecked", (*BurnChecked)(nil), + }, + { + "InitializeAccount2", (*InitializeAccount2)(nil), + }, + { + "SyncNative", (*SyncNative)(nil), + }, + { + "InitializeAccount3", (*InitializeAccount3)(nil), + }, + { + "InitializeMultisig2", (*InitializeMultisig2)(nil), + }, + { + "InitializeMint2", (*InitializeMint2)(nil), + }, + }, +) -type ThawAccountAccounts struct { -} -type ThawAccount struct { - Accounts *ThawAccountAccounts +func (inst *Instruction) ProgramID() ag_solanago.PublicKey { + return ProgramID } -type TransferCheckedAccounts struct { -} -type TransferChecked struct { - Accounts *TransferCheckedAccounts +func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) { + return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts() } -type ApproveCheckedAccounts struct { -} -type ApproveChecked struct { - Accounts *ApproveCheckedAccounts +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 } -type MintToCheckedAccounts struct { -} -type MintToChecked struct { - Accounts *MintToCheckedAccounts +func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error { + return encoder.Encode(inst.Impl, option) } -type BurnCheckedAccounts struct { -} -type BurnChecked struct { - Accounts *BurnCheckedAccounts +func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { + return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef) } -type InitializeAccountAccounts struct { - Account *solana.AccountMeta `text:"linear,notype"` - Mint *solana.AccountMeta `text:"linear,notype"` - Owner *solana.AccountMeta `text:"linear,notype"` - RentSysvar *solana.AccountMeta `text:"linear,notype"` +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) } -type InitializeAccount struct { - Accounts *InitializeAccountAccounts `bin:"-"` +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 (i *InitializeAccount) SetAccounts(accounts []*solana.AccountMeta) error { - i.Accounts = &InitializeAccountAccounts{ - Account: accounts[0], - Mint: accounts[1], - Owner: accounts[2], - RentSysvar: accounts[3], +func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) { + inst := new(Instruction) + if err := ag_binary.NewBinDecoder(data).Decode(inst); err != nil { + return nil, fmt.Errorf("unable to decode instruction: %w", err) } - return nil + 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 } diff --git a/programs/token/mints-data/mainnet-tokens.json b/programs/token/mints-data/mainnet-tokens.json deleted file mode 100644 index 479a040d..00000000 --- a/programs/token/mints-data/mainnet-tokens.json +++ /dev/null @@ -1,148 +0,0 @@ -[ - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x476c5E26a75bd202a9683ffD34359C0CC15be0fF/logo.png", - "mintAddress": "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", - "tokenName": "Serum", - "tokenSymbol": "SRM" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x476c5E26a75bd202a9683ffD34359C0CC15be0fF/logo.png", - "mintAddress": "MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L", - "tokenName": "MegaSerum", - "tokenSymbol": "MSRM" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/bitcoin/info/logo.png", - "mintAddress": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", - "tokenName": "Wrapped Bitcoin", - "tokenSymbol": "BTC" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", - "mintAddress": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk", - "tokenName": "Wrapped Ethereum", - "tokenSymbol": "ETH" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0x50D1c9771902476076eCFc8B2A83Ad6b9355a4c9/logo.png", - "mintAddress": "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3", - "tokenName": "Wrapped FTT", - "tokenSymbol": "FTT" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e/logo.png", - "mintAddress": "3JSf5tPeuscJGtaCp5giEiDhv51gQ4v3zWg8DGgyLfAB", - "tokenName": "Wrapped YFI", - "tokenSymbol": "YFI" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x514910771AF9Ca656af840dff83E8264EcF986CA/logo.png", - "mintAddress": "CWE8jPTUYhdCTZYWPTe1o5DFqfdjzWKc9WKz6rSjQUdG", - "tokenName": "Wrapped Chainlink", - "tokenSymbol": "LINK" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ripple/info/logo.png", - "mintAddress": "Ga2AXHpfAF6mv2ekZwcsJFqu7wB4NV331qNH7fW9Nst8", - "tokenName": "Wrapped XRP", - "tokenSymbol": "XRP" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png", - "mintAddress": "BQcdHdAQW1hczDbBi9hiegXAR7A98Q9jx3X3iBBBDiq4", - "tokenName": "Wrapped USDT", - "tokenSymbol": "USDT" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", - "mintAddress": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", - "tokenName": "USD Coin", - "tokenSymbol": "USDC" - }, - { - "deprecated": true, - "icon": "https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", - "mintAddress": "BXXkv6z8ykpG1yuvUDPgh732wzVHB69RnB9YgSYh3itW", - "tokenName": "Wrapped USDC", - "tokenSymbol": "WUSDC" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6B3595068778DD592e39A122f4f5a5cF09C90fE2/logo.png", - "mintAddress": "AR1Mtgh7zAtxuxGd2XPovXPVjcSdY3i4rQYisNadjfKy", - "tokenName": "Wrapped SUSHI", - "tokenSymbol": "SUSHI" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/6996a371cd02f516506a8f092eeb29888501447c/blockchains/nuls/assets/NULSd6HgyZkiqLnBzTaeSQfx1TNg2cqbzq51h/logo.png", - "mintAddress": "CsZ5LZkDS7h9TDKjrbL7VAwQZ9nsRu8vJLhRYfmGaN8K", - "tokenName": "Wrapped ALEPH", - "tokenSymbol": "ALEPH" - }, - { - "icon": "https://github.com/trustwallet/assets/raw/b0ab88654fe64848da80d982945e4db06e197d4f/blockchains/ethereum/assets/0x8CE9137d39326AD0cD6491fb5CC0CbA0e089b6A9/logo.png", - "mintAddress": "SF3oTvfWzEP3DTwGSvUXRrGTvr75pdZNnBLAH9bzMuX", - "tokenName": "Wrapped SXP", - "tokenSymbol": "SXP" - }, - { - "mintAddress": "BtZQfWqDGbk9Wf2rXEiWyQBdBY1etnUUn6zEphvVS7yN", - "tokenName": "Wrapped HGET", - "tokenSymbol": "HGET" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/4c82c2a409f18a4dd96a504f967a55a8fe47026d/blockchains/smartchain/assets/0xd4CB328A82bDf5f03eB737f37Fa6B370aef3e888/logo.png", - "mintAddress": "5Fu5UUgbjpUvdBveb3a1JTNirL8rXtiYeSMWvKjtUNQv", - "tokenName": "Wrapped CREAM", - "tokenSymbol": "CREAM" - }, - { - "mintAddress": "873KLxCbz7s9Kc4ZzgYRtNmhfkQrhfyWGZJBmyCbC3ei", - "tokenName": "Wrapped UBXT", - "tokenSymbol": "UBXT" - }, - { - "mintAddress": "HqB7uswoVg4suaQiDP3wjxob1G5WdZ144zhdStwMCq7e", - "tokenName": "Wrapped HNT", - "tokenSymbol": "HNT" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/6e375e4e5fb0ffe09ed001bae1ef8ca1d6c86034/blockchains/ethereum/assets/0xf8C3527CC04340b208C854E985240c02F7B7793f/logo.png", - "mintAddress": "9S4t2NEAiJVMvPdRYKVrfJpBafPBLtvbvyS3DecojQHw", - "tokenName": "Wrapped FRONT", - "tokenSymbol": "FRONT" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/878dcab0fab90e6593bcb9b7d941be4915f287dc/blockchains/ethereum/assets/0xb2734a4Cec32C81FDE26B0024Ad3ceB8C9b34037/logo.png", - "mintAddress": "6WNVCuxCGJzNjmMZoKyhZJwvJ5tYpsLyAtagzYASqBoF", - "tokenName": "Wrapped AKRO", - "tokenSymbol": "AKRO" - }, - { - "mintAddress": "DJafV9qemGp7mLMEn5wrfqaFwxsbLgUsGVS16zKRk9kc", - "tokenName": "Wrapped HXRO", - "tokenSymbol": "HXRO" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/08d734b5e6ec95227dc50efef3a9cdfea4c398a1/blockchains/ethereum/assets/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/logo.png", - "mintAddress": "DEhAasscXF4kEGxFgJ3bq4PpVGp5wyUxMRvn6TzGVHaw", - "tokenName": "Wrapped UNI", - "tokenSymbol": "UNI" - }, - { - "mintAddress": "GeDS162t9yGJuLEHPWXXGrb1zwkzinCgRwnT8vHYjKza", - "tokenName": "Wrapped MATH", - "tokenSymbol": "MATH" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/08d734b5e6ec95227dc50efef3a9cdfea4c398a1/blockchains/tomochain/info/logo.png", - "mintAddress": "GXMvfY2jpQctDqZ9RoU3oWPhufKiCcFEfchvYumtX7jd", - "tokenName": "Wrapped TOMO", - "tokenSymbol": "TOMO" - }, - { - "icon": "https://raw.githubusercontent.com/trustwallet/assets/2d2491130e6beda208ba4fc6df028a82a0106ab6/blockchains/ethereum/assets/0xB1f66997A5760428D3a87D68b90BfE0aE64121cC/logo.png", - "mintAddress": "EqWCKXfs3x47uVosDpTRgFniThL9Y8iCztJaapxbEaVX", - "tokenName": "Wrapped LUA", - "tokenSymbol": "LUA" - } -] diff --git a/programs/token/rice-box.go b/programs/token/rice-box.go deleted file mode 100644 index 9370608d..00000000 --- a/programs/token/rice-box.go +++ /dev/null @@ -1,44 +0,0 @@ -// Code generated by rice embed-go; DO NOT EDIT. -package token - -import ( - "time" - - "github.com/GeertJohan/go.rice/embedded" -) - -func init() { - - // define files - file2 := &embedded.EmbeddedFile{ - Filename: "mainnet-tokens.json", - FileModTime: time.Unix(1606332268, 0), - - Content: string("[\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x476c5E26a75bd202a9683ffD34359C0CC15be0fF/logo.png\",\n \"mintAddress\": \"SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt\",\n \"tokenName\": \"Serum\",\n \"tokenSymbol\": \"SRM\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x476c5E26a75bd202a9683ffD34359C0CC15be0fF/logo.png\",\n \"mintAddress\": \"MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L\",\n \"tokenName\": \"MegaSerum\",\n \"tokenSymbol\": \"MSRM\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/bitcoin/info/logo.png\",\n \"mintAddress\": \"9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E\",\n \"tokenName\": \"Wrapped Bitcoin\",\n \"tokenSymbol\": \"BTC\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png\",\n \"mintAddress\": \"2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk\",\n \"tokenName\": \"Wrapped Ethereum\",\n \"tokenSymbol\": \"ETH\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0x50D1c9771902476076eCFc8B2A83Ad6b9355a4c9/logo.png\",\n \"mintAddress\": \"AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3\",\n \"tokenName\": \"Wrapped FTT\",\n \"tokenSymbol\": \"FTT\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e/logo.png\",\n \"mintAddress\": \"3JSf5tPeuscJGtaCp5giEiDhv51gQ4v3zWg8DGgyLfAB\",\n \"tokenName\": \"Wrapped YFI\",\n \"tokenSymbol\": \"YFI\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x514910771AF9Ca656af840dff83E8264EcF986CA/logo.png\",\n \"mintAddress\": \"CWE8jPTUYhdCTZYWPTe1o5DFqfdjzWKc9WKz6rSjQUdG\",\n \"tokenName\": \"Wrapped Chainlink\",\n \"tokenSymbol\": \"LINK\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ripple/info/logo.png\",\n \"mintAddress\": \"Ga2AXHpfAF6mv2ekZwcsJFqu7wB4NV331qNH7fW9Nst8\",\n \"tokenName\": \"Wrapped XRP\",\n \"tokenSymbol\": \"XRP\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png\",\n \"mintAddress\": \"BQcdHdAQW1hczDbBi9hiegXAR7A98Q9jx3X3iBBBDiq4\",\n \"tokenName\": \"Wrapped USDT\",\n \"tokenSymbol\": \"USDT\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png\",\n \"mintAddress\": \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\",\n \"tokenName\": \"USD Coin\",\n \"tokenSymbol\": \"USDC\"\n },\n {\n \"deprecated\": true,\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png\",\n \"mintAddress\": \"BXXkv6z8ykpG1yuvUDPgh732wzVHB69RnB9YgSYh3itW\",\n \"tokenName\": \"Wrapped USDC\",\n \"tokenSymbol\": \"WUSDC\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6B3595068778DD592e39A122f4f5a5cF09C90fE2/logo.png\",\n \"mintAddress\": \"AR1Mtgh7zAtxuxGd2XPovXPVjcSdY3i4rQYisNadjfKy\",\n \"tokenName\": \"Wrapped SUSHI\",\n \"tokenSymbol\": \"SUSHI\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/6996a371cd02f516506a8f092eeb29888501447c/blockchains/nuls/assets/NULSd6HgyZkiqLnBzTaeSQfx1TNg2cqbzq51h/logo.png\",\n \"mintAddress\": \"CsZ5LZkDS7h9TDKjrbL7VAwQZ9nsRu8vJLhRYfmGaN8K\",\n \"tokenName\": \"Wrapped ALEPH\",\n \"tokenSymbol\": \"ALEPH\"\n },\n {\n \"icon\": \"https://github.com/trustwallet/assets/raw/b0ab88654fe64848da80d982945e4db06e197d4f/blockchains/ethereum/assets/0x8CE9137d39326AD0cD6491fb5CC0CbA0e089b6A9/logo.png\",\n \"mintAddress\": \"SF3oTvfWzEP3DTwGSvUXRrGTvr75pdZNnBLAH9bzMuX\",\n \"tokenName\": \"Wrapped SXP\",\n \"tokenSymbol\": \"SXP\"\n },\n {\n \"mintAddress\": \"BtZQfWqDGbk9Wf2rXEiWyQBdBY1etnUUn6zEphvVS7yN\",\n \"tokenName\": \"Wrapped HGET\",\n \"tokenSymbol\": \"HGET\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/4c82c2a409f18a4dd96a504f967a55a8fe47026d/blockchains/smartchain/assets/0xd4CB328A82bDf5f03eB737f37Fa6B370aef3e888/logo.png\",\n \"mintAddress\": \"5Fu5UUgbjpUvdBveb3a1JTNirL8rXtiYeSMWvKjtUNQv\",\n \"tokenName\": \"Wrapped CREAM\",\n \"tokenSymbol\": \"CREAM\"\n },\n {\n \"mintAddress\": \"873KLxCbz7s9Kc4ZzgYRtNmhfkQrhfyWGZJBmyCbC3ei\",\n \"tokenName\": \"Wrapped UBXT\",\n \"tokenSymbol\": \"UBXT\"\n },\n {\n \"mintAddress\": \"HqB7uswoVg4suaQiDP3wjxob1G5WdZ144zhdStwMCq7e\",\n \"tokenName\": \"Wrapped HNT\",\n \"tokenSymbol\": \"HNT\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/6e375e4e5fb0ffe09ed001bae1ef8ca1d6c86034/blockchains/ethereum/assets/0xf8C3527CC04340b208C854E985240c02F7B7793f/logo.png\",\n \"mintAddress\": \"9S4t2NEAiJVMvPdRYKVrfJpBafPBLtvbvyS3DecojQHw\",\n \"tokenName\": \"Wrapped FRONT\",\n \"tokenSymbol\": \"FRONT\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/878dcab0fab90e6593bcb9b7d941be4915f287dc/blockchains/ethereum/assets/0xb2734a4Cec32C81FDE26B0024Ad3ceB8C9b34037/logo.png\",\n \"mintAddress\": \"6WNVCuxCGJzNjmMZoKyhZJwvJ5tYpsLyAtagzYASqBoF\",\n \"tokenName\": \"Wrapped AKRO\",\n \"tokenSymbol\": \"AKRO\"\n },\n {\n \"mintAddress\": \"DJafV9qemGp7mLMEn5wrfqaFwxsbLgUsGVS16zKRk9kc\",\n \"tokenName\": \"Wrapped HXRO\",\n \"tokenSymbol\": \"HXRO\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/08d734b5e6ec95227dc50efef3a9cdfea4c398a1/blockchains/ethereum/assets/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/logo.png\",\n \"mintAddress\": \"DEhAasscXF4kEGxFgJ3bq4PpVGp5wyUxMRvn6TzGVHaw\",\n \"tokenName\": \"Wrapped UNI\",\n \"tokenSymbol\": \"UNI\"\n },\n {\n \"mintAddress\": \"GeDS162t9yGJuLEHPWXXGrb1zwkzinCgRwnT8vHYjKza\",\n \"tokenName\": \"Wrapped MATH\",\n \"tokenSymbol\": \"MATH\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/08d734b5e6ec95227dc50efef3a9cdfea4c398a1/blockchains/tomochain/info/logo.png\",\n \"mintAddress\": \"GXMvfY2jpQctDqZ9RoU3oWPhufKiCcFEfchvYumtX7jd\",\n \"tokenName\": \"Wrapped TOMO\",\n \"tokenSymbol\": \"TOMO\"\n },\n {\n \"icon\": \"https://raw.githubusercontent.com/trustwallet/assets/2d2491130e6beda208ba4fc6df028a82a0106ab6/blockchains/ethereum/assets/0xB1f66997A5760428D3a87D68b90BfE0aE64121cC/logo.png\",\n \"mintAddress\": \"EqWCKXfs3x47uVosDpTRgFniThL9Y8iCztJaapxbEaVX\",\n \"tokenName\": \"Wrapped LUA\",\n \"tokenSymbol\": \"LUA\"\n }\n]\n"), - } - - // define dirs - dir1 := &embedded.EmbeddedDir{ - Filename: "", - DirModTime: time.Unix(1606332268, 0), - ChildFiles: []*embedded.EmbeddedFile{ - file2, // "mainnet-tokens.json" - - }, - } - - // link ChildDirs - dir1.ChildDirs = []*embedded.EmbeddedDir{} - - // register embeddedBox - embedded.RegisterEmbeddedBox(`mints-data`, &embedded.EmbeddedBox{ - Name: `mints-data`, - Time: time.Unix(1606332268, 0), - Dirs: map[string]*embedded.EmbeddedDir{ - "": dir1, - }, - Files: map[string]*embedded.EmbeddedFile{ - "mainnet-tokens.json": file2, - }, - }) -} diff --git a/programs/token/rpc.go b/programs/token/rpc.go index 83accef2..33ccb3f9 100644 --- a/programs/token/rpc.go +++ b/programs/token/rpc.go @@ -18,15 +18,25 @@ import ( "context" "fmt" + bin "github.com/gagliardetto/binary" "github.com/gagliardetto/solana-go/rpc" ) -//go:generate rice embed-go +const MINT_SIZE = 82 + +func (mint *Mint) Decode(data []byte) error { + mint = new(Mint) + dec := bin.NewBinDecoder(data) + if err := dec.Decode(&mint); err != nil { + return fmt.Errorf("unable to decode mint: %w", err) + } + return nil +} func FetchMints(ctx context.Context, rpcCli *rpc.Client) (out []*Mint, err error) { resp, err := rpcCli.GetProgramAccountsWithOpts( ctx, - TOKEN_PROGRAM_ID, + ProgramID, &rpc.GetProgramAccountsOpts{ Filters: []rpc.RPCFilter{ { @@ -45,7 +55,7 @@ func FetchMints(ctx context.Context, rpcCli *rpc.Client) (out []*Mint, err error for _, keyedAcct := range resp { acct := keyedAcct.Account - m := &Mint{} + m := new(Mint) if err := m.Decode(acct.Data.GetBinary()); err != nil { return nil, fmt.Errorf("unable to decode mint %q: %w", acct.Owner.String(), err) } diff --git a/programs/token/testing_utils.go b/programs/token/testing_utils.go new file mode 100644 index 00000000..20ddafd1 --- /dev/null +++ b/programs/token/testing_utils.go @@ -0,0 +1,18 @@ +package token + +import ( + "bytes" + "fmt" + ag_binary "github.com/gagliardetto/binary" +) + +func encodeT(data interface{}, buf *bytes.Buffer) error { + if err := ag_binary.NewBinEncoder(buf).Encode(data); err != nil { + return fmt.Errorf("unable to encode instruction: %w", err) + } + return nil +} + +func decodeT(dst interface{}, data []byte) error { + return ag_binary.NewBinDecoder(data).Decode(dst) +} diff --git a/programs/token/types.go b/programs/token/types.go index dc013a66..def5ca0f 100644 --- a/programs/token/types.go +++ b/programs/token/types.go @@ -1,66 +1,51 @@ package token import ( - "fmt" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" +) + +type AuthorityType ag_binary.BorshEnum + +const ( + // Authority to mint new tokens + AuthorityMintTokens AuthorityType = iota + + // Authority to freeze any account associated with the Mint + AuthorityFreezeAccount - bin "github.com/gagliardetto/binary" - "github.com/gagliardetto/solana-go" + // Owner of a given token account + AuthorityAccountOwner + + // Authority to close a token account + AuthorityCloseAccount ) -// Token contract interface +type AccountState ag_binary.BorshEnum -type Token struct { - ProgramID string - Mint string -} +const ( + // Account is not yet initialized + Uninitialized AccountState = iota -func New(programID string, mint string) *Token { - return &Token{ProgramID: programID, Mint: mint} -} + // Account is initialized; the account owner and/or delegate may perform permitted operations + // on this account + Initialized -type Account struct { - Mint solana.PublicKey - Owner solana.PublicKey - Amount bin.Uint64 - IsDelegateSet uint32 - Delegate solana.PublicKey - IsInitialized bool - IsNative bool - Padding [2]byte `json:"-"` - DelegatedAmount bin.Uint64 -} + // Account has been frozen by the mint freeze authority. Neither the account owner nor + // the delegate are able to perform operations on this account. + Frozen +) type Multisig struct { - M byte - N byte - IsInitialized bool - Signers [11]solana.PublicKey -} + // Number of signers required + M uint8 -const MINT_SIZE = 82 + // Number of valid signers + N uint8 -type Mint struct { - MintAuthorityOption uint32 - MintAuthority solana.PublicKey - Supply bin.Uint64 - Decimals uint8 - IsInitialized bool - FreezeAuthorityOption uint32 - FreezeAuthority solana.PublicKey -} - -func (m *Mint) Decode(in []byte) error { - decoder := bin.NewBinDecoder(in) - err := decoder.Decode(&m) - if err != nil { - return fmt.Errorf("unpack: %w", err) - } - return nil -} + // Is `true` if this structure has been initialized + IsInitialized bool -type MintMeta struct { - TokenSymbol string - MintAddress solana.PublicKey - TokenName string - IconURL string `json:"icon"` + // Signer public keys + Signers [11]ag_solanago.PublicKey } diff --git a/programs/token/types_test.go b/programs/token/types_test.go deleted file mode 100644 index 042f39c7..00000000 --- a/programs/token/types_test.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2020 dfuse Platform Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package token - -import ( - "bytes" - "context" - "os" - "testing" - - bin "github.com/gagliardetto/binary" - "github.com/gagliardetto/solana-go" - "github.com/gagliardetto/solana-go/rpc" - "github.com/mr-tron/base58" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAccount(t *testing.T) { - b58data := "SqtzmJArwV2556pK7AdHbHNPVP2L2WaR6zfcFeot94TzGRUyUMEWew558UxnYEGrmm9b9VZY7MS6TCHT5wqtzaA5Vy8ghoFyGmbRNC58CttRf5GzH9wfjCkncyrmKjfevyjrJ2W9XKLgYGth46ctFWzJJXCeHsYwDx1d" - data, _ := base58.Decode(b58data) - - //fmt.Println("HEX:", hex.EncodeToString(data)) - // ba71eb12868584549b86f75620e7bb3ac5ef49df3fef0d48ad08e48dfa0fc786 // mint - // d7a1d0a56e355f17cedd5733e36a0cc9e2caf7a435e3256e4c9bff755f682b5a // owner - // 5ece000000000000 // amount - // 00000000 // is delegate set - // 0000000000000000000000000000000000000000000000000000000000000000 // delegate - // 01000000 // is initialized, is native + padding - // 0000000000000000 // delegate amount - var out Account - err := bin.NewBinDecoder(data).Decode(&out) - require.NoError(t, err) - - expect := Account{ - Mint: solana.MustPublicKeyFromBase58("DYoajiN32pjK8zMAa67ScNn2E7EmXrZ6doABRqfSZ63F"), - Owner: solana.MustPublicKeyFromBase58("FWjmNcjufwC3QFdcHrAK1yAQkCwJSUAxvVFFgvQ1nAJM"), - Amount: bin.Uint64(52830), - IsInitialized: true, - } - expectJSON, err := json.MarshalIndent(expect, "", " ") - require.NoError(t, err) - - outJSON, err := json.MarshalIndent(out, "", " ") - require.NoError(t, err) - - assert.JSONEq(t, string(expectJSON), string(outJSON)) - - buf := &bytes.Buffer{} - assert.NoError(t, bin.NewBinEncoder(buf).Encode(out)) - - assert.Equal(t, b58data, base58.Encode(buf.Bytes())) -} - -func TestMint(t *testing.T) { - rpcURL := os.Getenv("RPC_URL") - if rpcURL == "" { - t.Skip("Setup 'RPC_URL' to run test i.e. 'wss://api.mainnet-beta.solana.com'") - return - } - addr := solana.MustPublicKeyFromBase58("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") - cli := rpc.New(rpcURL) - - var m Mint - err := cli.GetAccountDataIn(context.Background(), addr, &m) - // handle `err` - require.NoError(t, err) - - json.NewEncoder(os.Stdout).Encode(m) - // {"OwnerOption":1, - // "Owner":"2wmVCSfPxGPjrnMMn7rchp4uaeoTqN39mXFC2zhPdri9", - // "Decimals":128, - // "IsInitialized":true} -} - -func TestRawMint(t *testing.T) { - rpcURL := os.Getenv("RPC_URL") - if rpcURL == "" { - t.Skip("Setup 'RPC_URL' to run test i.e. 'wss://api.mainnet-beta.solana.com'") - return - } - addr := solana.MustPublicKeyFromBase58("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") - cli := rpc.New(rpcURL) - - resp, err := cli.GetAccountInfo(context.Background(), addr) - // handle `err` - require.NoError(t, err) - - json.NewEncoder(os.Stdout).Encode(resp) - // {"OwnerOption":1, - // "Owner":"2wmVCSfPxGPjrnMMn7rchp4uaeoTqN39mXFC2zhPdri9", - // "Decimals":128, - // "IsInitialized":true} -} diff --git a/registry.go b/registry.go index 61477f93..a3a0ae8b 100644 --- a/registry.go +++ b/registry.go @@ -12,20 +12,20 @@ type InstructionDecoder func(instructionAccounts []*AccountMeta, data []byte) (i var InstructionDecoderRegistry = map[string]InstructionDecoder{} func RegisterInstructionDecoder(programID PublicKey, decoder InstructionDecoder) { - p := programID.String() - if _, found := InstructionDecoderRegistry[p]; found { - panic(fmt.Sprintf("unable to re-register instruction decoder for program %q", p)) + pid := programID.String() + if _, found := InstructionDecoderRegistry[pid]; found { + panic(fmt.Sprintf("unable to re-register instruction decoder for program %q", pid)) } - InstructionDecoderRegistry[p] = decoder + InstructionDecoderRegistry[pid] = decoder } func DecodeInstruction(programID PublicKey, accounts []*AccountMeta, data []byte) (interface{}, error) { - p := programID.String() + pid := programID.String() - decoder, found := InstructionDecoderRegistry[p] + decoder, found := InstructionDecoderRegistry[pid] if !found { - return nil, fmt.Errorf("unknown programID, cannot find any instruction decoder %q", p) + return nil, fmt.Errorf("instruction decoder not found for %s", pid) } return decoder(accounts, data) diff --git a/rpc/endpoints.go b/rpc/endpoints.go index d60116ea..2a387e05 100644 --- a/rpc/endpoints.go +++ b/rpc/endpoints.go @@ -27,4 +27,5 @@ const ( TestNet_WS = protocolWSS + hostTestNet MainNetBeta_WS = protocolWSS + hostMainNetBeta MainNetBetaSerum_WS = protocolWSS + hostMainNetBetaSerum + LocalNet_WS = "ws://127.0.0.1:8900" ) diff --git a/rpc/sendAndConfirmTransaction/sendAndConfirmTransaction.go b/rpc/sendAndConfirmTransaction/sendAndConfirmTransaction.go new file mode 100644 index 00000000..06fd547b --- /dev/null +++ b/rpc/sendAndConfirmTransaction/sendAndConfirmTransaction.go @@ -0,0 +1,69 @@ +package sendandconfirmtransaction + +import ( + "context" + "fmt" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + "github.com/gagliardetto/solana-go/rpc/ws" +) + +// Send and wait for confirmation of a transaction. +func SendAndConfirmTransaction( + ctx context.Context, + rpcClient *rpc.Client, + wsClient *ws.Client, + transaction *solana.Transaction, +) (signature solana.Signature, err error) { + return SendAndConfirmTransactionWithOpts( + ctx, + rpcClient, + wsClient, + transaction, + false, + rpc.CommitmentFinalized, + ) +} + +// Send and wait for confirmation of a transaction. +func SendAndConfirmTransactionWithOpts( + ctx context.Context, + rpcClient *rpc.Client, + wsClient *ws.Client, + transaction *solana.Transaction, + skipPreflight bool, // if true, skip the preflight transaction checks (default: false) + preflightCommitment rpc.CommitmentType, // optional; Commitment level to use for preflight (default: "finalized"). +) (signature solana.Signature, err error) { + + sig, err := rpcClient.SendTransactionWithOpts( + ctx, + transaction, + skipPreflight, + preflightCommitment, + ) + if err != nil { + return sig, err + } + + sub, err := wsClient.SignatureSubscribe( + sig, + rpc.CommitmentFinalized, + ) + if err != nil { + return sig, err + } + defer sub.Unsubscribe() + + for { + got, err := sub.Recv() + if err != nil { + return sig, err + } + if got.Value.Err != nil { + return sig, fmt.Errorf("transaction confirmation failed: %v", got.Value.Err) + } else { + return sig, nil + } + } +} diff --git a/rpc/ws/client.go b/rpc/ws/client.go index 2fda3530..2d316907 100644 --- a/rpc/ws/client.go +++ b/rpc/ws/client.go @@ -40,6 +40,15 @@ type Client struct { reconnectOnErr bool } +const ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 +) + // Connect creates a new websocket client connecting to the provided endpoint. func Connect(ctx context.Context, rpcEndpoint string) (c *Client, err error) { c = &Client{ @@ -60,12 +69,17 @@ func Connect(ctx context.Context, rpcEndpoint string) (c *Client, err error) { } go func() { + c.conn.SetReadDeadline(time.Now().Add(pongWait)) + c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + ticker := time.NewTicker(pingPeriod) for { - c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) - if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { - return + select { + case <-ticker.C: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if err := c.conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil { + return + } } - time.Sleep(20 * time.Second) } }() go c.receiveMessages() diff --git a/text/format/format.go b/text/format/format.go index d92ace1e..fb0f53ad 100644 --- a/text/format/format.go +++ b/text/format/format.go @@ -18,7 +18,15 @@ func Instruction(name string) string { } func Param(name string, value interface{}) string { - return Sf(CC(Shakespeare(name), ": %s"), Lime(strings.TrimSpace(spew.Sdump(value)))) + return Sf( + Shakespeare(name)+": %s", + strings.TrimSpace( + prefixEachLineExceptFirst( + strings.Repeat(" ", len(name)+2), + strings.TrimSpace(spew.Sdump(value)), + ), + ), + ) } func Account(name string, pubKey solana.PublicKey) string { @@ -31,7 +39,7 @@ func Meta(name string, meta *solana.AccountMeta) string { if meta.IsWritable { out += "WRITE" } - if meta.IsWritable { + if meta.IsSigner { if meta.IsWritable { out += ", " } @@ -40,3 +48,22 @@ func Meta(name string, meta *solana.AccountMeta) string { out += "] " return out } + +func prefixEachLineExceptFirst(prefix string, s string) string { + return foreachLine(s, + func(i int, line string) string { + if i == 0 { + return Lime(line) + "\n" + } + return prefix + Lime(line) + "\n" + }) +} + +type sf func(int, string) string + +func foreachLine(str string, transform sf) (out string) { + for idx, line := range strings.Split(str, "\n") { + out += transform(idx, line) + } + return +} diff --git a/text/tools.go b/text/tools.go index 65ddb33a..4d841044 100644 --- a/text/tools.go +++ b/text/tools.go @@ -173,14 +173,10 @@ func Bold(str string) string { type sf func(int, string) string -// Apply given transformation func for each line in string func foreachLine(str string, transform sf) (out string) { - out = "" - for idx, line := range strings.Split(str, "\n") { out += transform(idx, line) } - return } diff --git a/text/tree.go b/text/tree.go index 55393e57..a28239a5 100644 --- a/text/tree.go +++ b/text/tree.go @@ -15,10 +15,10 @@ type EncodableToTree interface { EncodeToTree(parent treeout.Branches) } -func NewTreeEncoder(w io.Writer, docs ...string) *TreeEncoder { +func NewTreeEncoder(w io.Writer, doc string) *TreeEncoder { return &TreeEncoder{ output: w, - Tree: treeout.New(docs...), + Tree: treeout.New(doc), } } diff --git a/transaction.go b/transaction.go index 5c5b57b9..c05dff7d 100644 --- a/transaction.go +++ b/transaction.go @@ -327,8 +327,8 @@ func (tx *Transaction) Sign(getter privateKeyGetter) (out []Signature, err error } func (tx *Transaction) EncodeTree(encoder *text.TreeEncoder) (int, error) { - if len(encoder.Docs) == 0 { - encoder.Docs = []string{"Transaction"} + if len(encoder.Doc) == 0 { + encoder.Doc = "Transaction" } tx.EncodeToTree(encoder) return encoder.WriteString(encoder.Tree.String()) @@ -337,7 +337,7 @@ func (tx *Transaction) EncodeTree(encoder *text.TreeEncoder) (int, error) { func (tx *Transaction) EncodeToTree(parent treeout.Branches) { parent.ParentFunc(func(txTree treeout.Branches) { - txTree.Child("Signatures[]").ParentFunc(func(signaturesBranch treeout.Branches) { + txTree.Child(fmt.Sprintf("Signatures[len=%v]", len(tx.Signatures))).ParentFunc(func(signaturesBranch treeout.Branches) { for _, sig := range tx.Signatures { signaturesBranch.Child(sig.String()) } @@ -348,22 +348,24 @@ func (tx *Transaction) EncodeToTree(parent treeout.Branches) { }) }) - parent.Child("Instructions[]").ParentFunc(func(message treeout.Branches) { + parent.Child(fmt.Sprintf("Instructions[len=%v]", len(tx.Message.Instructions))).ParentFunc(func(message treeout.Branches) { for _, inst := range tx.Message.Instructions { progKey, err := tx.ResolveProgramIDIndex(inst.ProgramIDIndex) - if err != nil { - panic(err) - } - - decodedInstruction, err := DecodeInstruction(progKey, inst.ResolveInstructionAccounts(&tx.Message), inst.Data) - if err != nil { - panic(err) - } - if enToTree, ok := decodedInstruction.(text.EncodableToTree); ok { - enToTree.EncodeToTree(message) + if err == nil { + decodedInstruction, err := DecodeInstruction(progKey, inst.ResolveInstructionAccounts(&tx.Message), inst.Data) + if err == nil { + if enToTree, ok := decodedInstruction.(text.EncodableToTree); ok { + enToTree.EncodeToTree(message) + } else { + message.Child(spew.Sdump(decodedInstruction)) + } + } else { + // TODO: log error? + message.Child(fmt.Sprintf(text.RedBG("cannot decode instruction for %s program: %s"), progKey, err)) + } } else { - message.Child(spew.Sdump(decodedInstruction)) + message.Child(fmt.Sprintf(text.RedBG("cannot ResolveProgramIDIndex: %s"), err)) } } }) diff --git a/types.go b/types.go index 7ab1cffa..f01167ff 100644 --- a/types.go +++ b/types.go @@ -66,7 +66,7 @@ var _ bin.EncoderDecoder = &Message{} func (mx *Message) EncodeToTree(txTree treeout.Branches) { txTree.Child(text.Sf("RecentBlockhash: %s", mx.RecentBlockhash)) - txTree.Child("AccountKeys[]").ParentFunc(func(accountKeysBranch treeout.Branches) { + txTree.Child(fmt.Sprintf("AccountKeys[len=%v]", len(mx.AccountKeys))).ParentFunc(func(accountKeysBranch treeout.Branches) { for _, key := range mx.AccountKeys { accountKeysBranch.Child(text.ColorizeBG(key.String())) }