{"content":{"title":"golang调用raydium dex交易代币的telegram机器人","body":"最近solana生态很火，土狗横行，那些开发telegram bot的团队赚手续费都麻了，思来项目，我个人也可以吗，毕竟我之前写过很多telegram、wechat、qq、twitter相互转发工具，也有一些客户群体，说干就干。接下来我分享一下逻辑和代码。\r\n我先贴代码：\r\n\r\n```golang\r\npackage raydium\r\n\r\nimport (\r\n\t\"context\"\r\n\t\"fmt\"\r\n\t\"github.com/gagliardetto/solana-go\"\r\n\tassociatedtokenaccount \"github.com/gagliardetto/solana-go/programs/associated-token-account\"\r\n\t\"github.com/gagliardetto/solana-go/rpc\"\r\n\t\"telegram_bot/internal/quant_server/swap/raydium/config\"\r\n\t\"telegram_bot/internal/quant_server/swap/raydium/raydium\"\r\n)\r\n\r\nvar clientRPC *rpc.Client\r\n\r\n//创建一个rpc客户端\r\nfunc NewClinet() *rpc.Client {\r\n\tif clientRPC != nil {\r\n\t\treturn clientRPC\r\n\t}\r\n\trpcEndpoint := \"https://api.mainnet-beta.solana.com\"\r\n\r\n\tclientRPC = rpc.New(rpcEndpoint)\r\n\r\n\t// 确保客户端创建成功\r\n\tif clientRPC == nil {\r\n\t\t// 处理错误情况\r\n\t\tpanic(\"Solana Failed to create RPC client\")\r\n\t}\r\n\treturn clientRPC\r\n}\r\n\r\nfunc Swap(opType int, privateKeyHex string, amount uint64, fromTokenAddr, toTokenAddr string, huaDian int, gasYouXian uint64, shouFeiAddress string, shouFeiAmount uint64) (string, error) {\r\n\tvar baseMint, quoteAddr string\r\n\tif opType == 1 {\r\n\t\t//买\r\n\t\tbaseMint = toTokenAddr\r\n\t\tquoteAddr = config.WrappedSOL\r\n\t} else {\r\n\t\tbaseMint = fromTokenAddr\r\n\t\tquoteAddr = config.WrappedSOL\r\n\t}\r\n\r\n\trealGasYouXian := gasYouXian\r\n\tprivateKey, err := solana.PrivateKeyFromBase58(privateKeyHex)\r\n\tif err != nil {\r\n\t\t// 处理错误\r\n\t\tfmt.Println(\"私钥错误：\", err, privateKeyHex)\r\n\t\treturn \"\", err\r\n\t}\r\n\r\n\tswap := raydium.NewRaydiumSwap(NewClinet(), privateKey)\r\n\tctx := context.Background()\r\n\r\n\t//授权\r\n\tarr, instrs, err := checkAccount(privateKey, fromTokenAddr, toTokenAddr)\r\n\r\n\tsig, err := swap.EasySwap(\r\n\t\tctx,\r\n\t\tbaseMint,\r\n\t\tquoteAddr,\r\n\t\tamount,             // amount\r\n\t\tfromTokenAddr,      // fromToken\r\n\t\tarr[fromTokenAddr], // fromAccount\r\n\t\ttoTokenAddr,        // toToken\r\n\t\tarr[toTokenAddr],   // toAccount\r\n\t\tinstrs,\r\n\t\thuaDian,\r\n\t\trealGasYouXian,\r\n\t\tshouFeiAddress,\r\n\t\tshouFeiAmount,\r\n\t\tprivateKey,\r\n\t)\r\n\treturn sig.String(), err\r\n}\r\n\r\nfunc checkAccount(privateKey solana.PrivateKey, formTokenAddr, toTokenAddr string) (map[string]solana.PublicKey, []solana.Instruction, error) {\r\n\tmints := []solana.PublicKey{\r\n\t\tsolana.MustPublicKeyFromBase58(formTokenAddr),\r\n\t\tsolana.MustPublicKeyFromBase58(toTokenAddr),\r\n\t}\r\n\r\n\texistingAccounts, missingAccounts, err := raydium.GetTokenAccountsFromMints(context.Background(), *NewClinet(), privateKey.PublicKey(), mints...)\r\n\tif err != nil {\r\n\t\tfmt.Println(\"GetTokenAccountsFromMints：\", err)\r\n\t\treturn nil, nil, err\r\n\t}\r\n\r\n\tinstrs := []solana.Instruction{}\r\n\tif len(missingAccounts) != 0 {\r\n\t\tfor mint := range missingAccounts {\r\n\t\t\tif mint == config.NativeSOL {\r\n\t\t\t\tcontinue\r\n\t\t\t}\r\n\t\t\tinst, err := associatedtokenaccount.NewCreateInstruction(\r\n\t\t\t\tprivateKey.PublicKey(),\r\n\t\t\t\tprivateKey.PublicKey(),\r\n\t\t\t\tsolana.MustPublicKeyFromBase58(mint),\r\n\t\t\t).ValidateAndBuild()\r\n\t\t\tif err != nil {\r\n\t\t\t\tfmt.Println(\"NewCreateInstruction：\", err)\r\n\t\t\t\treturn nil, nil, err\r\n\t\t\t}\r\n\t\t\tinstrs = append(instrs, inst)\r\n\t\t}\r\n\t\tfor k, v := range missingAccounts {\r\n\t\t\texistingAccounts[k] = v\r\n\t\t}\r\n\t}\r\n\treturn existingAccounts, instrs, nil\r\n}\r\n```\r\n\r\n\r\n```golang\r\npackage raydium\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"context\"\r\n\t\"encoding/binary\"\r\n\t\"errors\"\r\n\t\"fmt\"\r\n\tcomputebudget \"github.com/gagliardetto/solana-go/programs/compute-budget\"\r\n\r\n\t\"telegram_bot/internal/quant_server/swap/raydium/config\"\r\n\r\n\tbin \"github.com/gagliardetto/binary\"\r\n\t\"github.com/gagliardetto/solana-go\"\r\n\t\"github.com/gagliardetto/solana-go/programs/system\"\r\n\t\"github.com/gagliardetto/solana-go/programs/token\"\r\n\t\"github.com/gagliardetto/solana-go/rpc\"\r\n)\r\n\r\ntype RaydiumSwap struct {\r\n\tclientRPC *rpc.Client\r\n\taccount   solana.PrivateKey\r\n}\r\n\r\nfunc NewRaydiumSwap(clientRPC *rpc.Client, account solana.PrivateKey) *RaydiumSwap {\r\n\treturn &RaydiumSwap{\r\n\t\tclientRPC: clientRPC,\r\n\t\taccount:   account,\r\n\t}\r\n}\r\n\r\nfunc (s *RaydiumSwap) EasySwap(\r\n\tctx context.Context,\r\n\tbaseMint string,\r\n\tquoteMint string,\r\n\tamount uint64,\r\n\tfromToken string,\r\n\tfromAccount solana.PublicKey,\r\n\ttoToken string,\r\n\ttoAccount solana.PublicKey,\r\n\tinsts []solana.Instruction,\r\n\thuaDian int,\r\n\tgasYouXian uint64,\r\n\tshouFeiAddress string,\r\n\tshouFeiAmount uint64,\r\n\tprivateKey solana.PrivateKey,\r\n) (*solana.Signature, error) {\r\n\tparsedPool, err := FindPoolInfoByID(baseMint, quoteMint)\r\n\tif err != nil {\r\n\t\tfmt.Println(\"FindPoolInfoByID：\", err)\r\n\t\treturn nil, err\r\n\t}\r\n\treturn s.Swap(ctx, &config.RaydiumPoolConfig{\r\n\t\tAmmId:                 parsedPool.ID,\r\n\t\tAmmAuthority:          parsedPool.Authority,\r\n\t\tAmmOpenOrders:         parsedPool.OpenOrders,\r\n\t\tAmmTargetOrders:       parsedPool.TargetOrders,\r\n\t\tAmmQuantities:         config.NativeSOL,\r\n\t\tPoolCoinTokenAccount:  parsedPool.BaseVault,\r\n\t\tPoolPcTokenAccount:    parsedPool.QuoteVault,\r\n\t\tSerumProgramId:        parsedPool.MarketProgramId,\r\n\t\tSerumMarket:           parsedPool.MarketId,\r\n\t\tSerumBids:             parsedPool.MarketBids,\r\n\t\tSerumAsks:             parsedPool.MarketAsks,\r\n\t\tSerumEventQueue:       parsedPool.MarketEventQueue,\r\n\t\tSerumCoinVaultAccount: parsedPool.MarketBaseVault,\r\n\t\tSerumPcVaultAccount:   parsedPool.MarketQuoteVault,\r\n\t\tSerumVaultSigner:      parsedPool.MarketAuthority,\r\n\t}, amount, fromToken, fromAccount, toToken, toAccount, insts, huaDian, gasYouXian, shouFeiAddress, shouFeiAmount, privateKey)\r\n}\r\n\r\nfunc (s *RaydiumSwap) Swap(\r\n\tctx context.Context,\r\n\tpool *config.RaydiumPoolConfig,\r\n\tamount uint64,\r\n\tfromToken string,\r\n\tfromAccount solana.PublicKey,\r\n\ttoToken string,\r\n\ttoAccount solana.PublicKey,\r\n\tinsts []solana.Instruction,\r\n\thuaDian int,\r\n\tgasYouXian uint64,\r\n\tshouFeiAddress string,\r\n\tshouFeiAmount uint64,\r\n\tprivateKey solana.PrivateKey,\r\n) (*solana.Signature, error) {\r\n\tres, err := s.clientRPC.GetMultipleAccounts(\r\n\t\tctx,\r\n\t\tsolana.MustPublicKeyFromBase58(pool.PoolCoinTokenAccount),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.PoolPcTokenAccount),\r\n\t)\r\n\tif err != nil {\r\n\t\tfmt.Println(\"GetMultipleAccounts：\", err)\r\n\t\treturn nil, err\r\n\t}\r\n\r\n\tvar poolCoinBalance token.Account\r\n\terr = bin.NewBinDecoder(res.Value[0].Data.GetBinary()).Decode(&poolCoinBalance)\r\n\tif err != nil {\r\n\t\tfmt.Println(\"NewBinDecoder：\", err)\r\n\t\treturn nil, err\r\n\t}\r\n\r\n\tvar poolPcBalance token.Account\r\n\terr = bin.NewBinDecoder(res.Value[1].Data.GetBinary()).Decode(&poolPcBalance)\r\n\tif err != nil {\r\n\t\treturn nil, err\r\n\t}\r\n\r\n\tdenominator := poolCoinBalance.Amount + amount\r\n\tminimumOutAmount := poolPcBalance.Amount * amount / denominator\r\n\t// slippage 2%\r\n\tminimumOutAmount = minimumOutAmount * uint64(huaDian) / 100\r\n\r\n\tif minimumOutAmount <= 0 {\r\n\t\treturn nil, errors.New(\"min swap output amount must be grater then zero, try to swap a bigger amount\")\r\n\t}\r\n\r\n\tinstrs := []solana.Instruction{}\r\n\r\n\t//i, _ := computebudget.NewSetComputeUnitLimitInstruction(uint32(801432)).ValidateAndBuild()\r\n\t//instrs = append(instrs, i)\r\n\r\n\t//gas优先费\r\n\ti, _ := computebudget.NewSetComputeUnitPriceInstruction(gasYouXian).ValidateAndBuild()\r\n\tinstrs = append(instrs, i)\r\n\r\n\t//添加转账，收手续费\r\n\tif shouFeiAddress != \"\" {\r\n\t\taccountTo := solana.MustPublicKeyFromBase58(shouFeiAddress)\r\n\t\tinstrs = append(instrs, system.NewTransferInstruction(\r\n\t\t\tshouFeiAmount,\r\n\t\t\tprivateKey.PublicKey(),\r\n\t\t\taccountTo,\r\n\t\t).Build())\r\n\t}\r\n\r\n\tinstrs = append(instrs, insts...)\r\n\tsigners := []solana.PrivateKey{s.account}\r\n\ttempAccount := solana.NewWallet()\r\n\tneedWrapSOL := fromToken == config.NativeSOL || toToken == config.NativeSOL\r\n\tif needWrapSOL {\r\n\t\trentCost, err := s.clientRPC.GetMinimumBalanceForRentExemption(\r\n\t\t\tctx,\r\n\t\t\tconfig.TokenAccountSize,\r\n\t\t\trpc.CommitmentConfirmed,\r\n\t\t)\r\n\t\tif err != nil {\r\n\t\t\treturn nil, err\r\n\t\t}\r\n\t\taccountLamports := rentCost\r\n\t\tif fromToken == config.NativeSOL {\r\n\t\t\t// If is from a SOL account, transfer the amount\r\n\t\t\taccountLamports += amount\r\n\t\t}\r\n\t\tcreateInst, err := system.NewCreateAccountInstruction(\r\n\t\t\taccountLamports,\r\n\t\t\tconfig.TokenAccountSize,\r\n\t\t\tsolana.TokenProgramID,\r\n\t\t\ts.account.PublicKey(),\r\n\t\t\ttempAccount.PublicKey(),\r\n\t\t).ValidateAndBuild()\r\n\t\tif err != nil {\r\n\t\t\treturn nil, err\r\n\t\t}\r\n\t\tinstrs = append(instrs, createInst)\r\n\t\tinitInst, err := token.NewInitializeAccountInstruction(\r\n\t\t\ttempAccount.PublicKey(),\r\n\t\t\tsolana.MustPublicKeyFromBase58(config.WrappedSOL),\r\n\t\t\ts.account.PublicKey(),\r\n\t\t\tsolana.SysVarRentPubkey,\r\n\t\t).ValidateAndBuild()\r\n\t\tif err != nil {\r\n\t\t\treturn nil, err\r\n\t\t}\r\n\t\tinstrs = append(instrs, initInst)\r\n\t\tsigners = append(signers, tempAccount.PrivateKey)\r\n\t\t// Use this new temp account as from or to\r\n\t\tif fromToken == config.NativeSOL {\r\n\t\t\tfromAccount = tempAccount.PublicKey()\r\n\t\t}\r\n\t\tif toToken == config.NativeSOL {\r\n\t\t\ttoAccount = tempAccount.PublicKey()\r\n\t\t}\r\n\t}\r\n\r\n\tinstrs = append(instrs, NewRaydiumSwapInstruction(\r\n\t\tamount,\r\n\t\tminimumOutAmount,\r\n\t\tsolana.TokenProgramID,\r\n\t\tsolana.MustPublicKeyFromBase58(pool.AmmId),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.AmmAuthority),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.AmmOpenOrders),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.AmmTargetOrders),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.PoolCoinTokenAccount),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.PoolPcTokenAccount),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.SerumProgramId),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.SerumMarket),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.SerumBids),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.SerumAsks),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.SerumEventQueue),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.SerumCoinVaultAccount),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.SerumPcVaultAccount),\r\n\t\tsolana.MustPublicKeyFromBase58(pool.SerumVaultSigner),\r\n\t\tfromAccount,\r\n\t\ttoAccount,\r\n\t\ts.account.PublicKey(),\r\n\t))\r\n\r\n\tif needWrapSOL {\r\n\t\tcloseInst, err := token.NewCloseAccountInstruction(\r\n\t\t\ttempAccount.PublicKey(),\r\n\t\t\ts.account.PublicKey(),\r\n\t\t\ts.account.PublicKey(),\r\n\t\t\t[]solana.PublicKey{},\r\n\t\t).ValidateAndBuild()\r\n\t\tif err != nil {\r\n\t\t\treturn nil, err\r\n\t\t}\r\n\t\tinstrs = append(instrs, closeInst)\r\n\t}\r\n\r\n\tsig, err := ExecuteInstructions(ctx, s.clientRPC, signers, instrs...)\r\n\tif err != nil {\r\n\t\tfmt.Println(\"ExecuteInstructions：12121212\", err.Error())\r\n\t\treturn nil, err\r\n\t}\r\n\r\n\treturn sig, nil\r\n}\r\n\r\n/** Instructions  **/\r\n\r\ntype RaySwapInstruction struct {\r\n\tbin.BaseVariant\r\n\tInAmount                uint64\r\n\tMinimumOutAmount        uint64\r\n\tsolana.AccountMetaSlice `bin:\"-\" borsh_skip:\"true\"`\r\n}\r\n\r\nfunc (inst *RaySwapInstruction) ProgramID() solana.PublicKey {\r\n\treturn solana.MustPublicKeyFromBase58(config.RaydiumLiquidityPoolProgramIDV4)\r\n}\r\n\r\nfunc (inst *RaySwapInstruction) Accounts() (out []*solana.AccountMeta) {\r\n\treturn inst.Impl.(solana.AccountsGettable).GetAccounts()\r\n}\r\n\r\nfunc (inst *RaySwapInstruction) Data() ([]byte, error) {\r\n\tbuf := new(bytes.Buffer)\r\n\tif err := bin.NewBorshEncoder(buf).Encode(inst); err != nil {\r\n\t\treturn nil, fmt.Errorf(\"unable to encode instruction: %w\", err)\r\n\t}\r\n\treturn buf.Bytes(), nil\r\n}\r\n\r\nfunc (inst *RaySwapInstruction) MarshalWithEncoder(encoder *bin.Encoder) (err error) {\r\n\t// Swap instruction is number 9\r\n\terr = encoder.WriteUint8(9)\r\n\tif err != nil {\r\n\t\treturn err\r\n\t}\r\n\terr = encoder.WriteUint64(inst.InAmount, binary.LittleEndian)\r\n\tif err != nil {\r\n\t\treturn err\r\n\t}\r\n\terr = encoder.WriteUint64(inst.MinimumOutAmount, binary.LittleEndian)\r\n\tif err != nil {\r\n\t\treturn err\r\n\t}\r\n\treturn nil\r\n}\r\n\r\nfunc NewRaydiumSwapInstruction(\r\n\t// Parameters:\r\n\tinAmount uint64,\r\n\tminimumOutAmount uint64,\r\n\t// Accounts:\r\n\ttokenProgram solana.PublicKey,\r\n\tammId solana.PublicKey,\r\n\tammAuthority solana.PublicKey,\r\n\tammOpenOrders solana.PublicKey,\r\n\tammTargetOrders solana.PublicKey,\r\n\tpoolCoinTokenAccount solana.PublicKey,\r\n\tpoolPcTokenAccount solana.PublicKey,\r\n\tserumProgramId solana.PublicKey,\r\n\tserumMarket solana.PublicKey,\r\n\tserumBids solana.PublicKey,\r\n\tserumAsks solana.PublicKey,\r\n\tserumEventQueue solana.PublicKey,\r\n\tserumCoinVaultAccount solana.PublicKey,\r\n\tserumPcVaultAccount solana.PublicKey,\r\n\tserumVaultSigner solana.PublicKey,\r\n\tuserSourceTokenAccount solana.PublicKey,\r\n\tuserDestTokenAccount solana.PublicKey,\r\n\tuserOwner solana.PublicKey,\r\n) *RaySwapInstruction {\r\n\r\n\tinst := RaySwapInstruction{\r\n\t\tInAmount:         inAmount,\r\n\t\tMinimumOutAmount: minimumOutAmount,\r\n\t\tAccountMetaSlice: make(solana.AccountMetaSlice, 18),\r\n\t}\r\n\tinst.BaseVariant = bin.BaseVariant{\r\n\t\tImpl: inst,\r\n\t}\r\n\r\n\tinst.AccountMetaSlice[0] = solana.Meta(tokenProgram)\r\n\tinst.AccountMetaSlice[1] = solana.Meta(ammId).WRITE()\r\n\tinst.AccountMetaSlice[2] = solana.Meta(ammAuthority)\r\n\tinst.AccountMetaSlice[3] = solana.Meta(ammOpenOrders).WRITE()\r\n\tinst.AccountMetaSlice[4] = solana.Meta(ammTargetOrders).WRITE()\r\n\tinst.AccountMetaSlice[5] = solana.Meta(poolCoinTokenAccount).WRITE()\r\n\tinst.AccountMetaSlice[6] = solana.Meta(poolPcTokenAccount).WRITE()\r\n\tinst.AccountMetaSlice[7] = solana.Meta(serumProgramId)\r\n\tinst.AccountMetaSlice[8] = solana.Meta(serumMarket).WRITE()\r\n\tinst.AccountMetaSlice[9] = solana.Meta(serumBids).WRITE()\r\n\tinst.AccountMetaSlice[10] = solana.Meta(serumAsks).WRITE()\r\n\tinst.AccountMetaSlice[11] = solana.Meta(serumEventQueue).WRITE()\r\n\tinst.AccountMetaSlice[12] = solana.Meta(serumCoinVaultAccount).WRITE()\r\n\tinst.AccountMetaSlice[13] = solana.Meta(serumPcVaultAccount).WRITE()\r\n\tinst.AccountMetaSlice[14] = solana.Meta(serumVaultSigner)\r\n\tinst.AccountMetaSlice[15] = solana.Meta(userSourceTokenAccount).WRITE()\r\n\tinst.AccountMetaSlice[16] = solana.Meta(userDestTokenAccount).WRITE()\r\n\tinst.AccountMetaSlice[17] = solana.Meta(userOwner).SIGNER()\r\n\r\n\treturn &inst\r\n}\r\n```\r\n\r\n项目结构：\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/04/3hY86XBB661921239e053.png)\r\n\r\n先写这么多，等我完善说明，感兴趣的可以一起讨论，目前就差获取池子的狙击功能了，我计划用nodejs来写，配合golang，毕竟sdk都是js写的，如果用golang的话，后续sdk修改之后，就需要同步修改golang了，麻烦，如果未来我没有能力修改sdk，我这项目就废了。"},"author":{"user":"https://learnblockchain.cn/people/846","address":null},"history":null,"timestamp":1712923491,"version":1}