Skip to content

Commit

Permalink
Raydium v2 Swap implemented
Browse files Browse the repository at this point in the history
MVP client for swapping on Raydium v4 amm pools
  • Loading branch information
BifrostTitan committed Oct 21, 2024
1 parent c9cc40d commit 47c4875
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 442 deletions.
75 changes: 55 additions & 20 deletions Client/RaydiumClient.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
using System;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using Solnet.Programs.Abstract;
using Solnet.Programs.Utilities;
using Solnet.Programs;
using Solnet.Programs.Models;
using Solnet.Raydium.Types;
using Solnet.Rpc;
using Solnet.Rpc.Builders;
using Solnet.Programs.Models;
using Solnet.Rpc.Core.Http;
using Solnet.Rpc.Core.Sockets;
using Solnet.Rpc.Types;
using Solnet.Rpc.Models;
using Solnet.Rpc.Types;
using Solnet.Wallet;
using Solnet.Raydium.Library;
using System.Diagnostics;
using System.Security.Cryptography;

namespace Solnet.Raydium.Client
{
public class RaydiumAmmClient
{
public PublicKey programId = new PublicKey("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");

public PublicKey ammAuthority = new PublicKey("5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1");
public IRpcClient RpcClient { get; set; }
public RaydiumAmmClient(IRpcClient rpcClient)
{
Expand Down Expand Up @@ -101,51 +98,89 @@ public async Task<AccountResultWrapper<Fees>> GetFeesAsync(string accountAddress
public async Task<AccountResultWrapper<AmmInfo>> GetAmmInfoAsync(string poolAddress, Commitment commitment = Commitment.Finalized)
{
var res = await RpcClient.GetAccountInfoAsync(poolAddress, commitment);
if (!res.WasSuccessful)
return new AccountResultWrapper<AmmInfo>(res);
var resultingAccount = AmmInfo.Deserialize(Convert.FromBase64String(res.Result.Value.Data[0]));
return new AccountResultWrapper<AmmInfo>(res, resultingAccount);
}

public async Task<RequestResult<string>> SendInitializeAsync(InitializeAccounts accounts, byte nonce, ulong openTime, PublicKey feePayer, Account trader, PublicKey programId)
public async Task<RequestResult<string>> SendInitializeAsync(InitializeAccounts accounts, byte nonce, ulong openTime, PublicKey feePayer, Account trader)
{
TransactionInstruction instr = RaydiumAmmProgram.Initialize(accounts, nonce, openTime, programId);
return await BuildSignSend(trader, feePayer, instr);
}

public async Task<RequestResult<string>> SendInitialize2Async(Initialize2Accounts accounts, byte nonce, ulong openTime, ulong initPcAmount, ulong initCoinAmount, PublicKey feePayer, Account trader, PublicKey programId)
public async Task<RequestResult<string>> SendInitialize2Async(Initialize2Accounts accounts, byte nonce, ulong openTime, ulong initPcAmount, ulong initCoinAmount, PublicKey feePayer, Account trader)
{
TransactionInstruction instr = RaydiumAmmProgram.Initialize2(accounts, nonce, openTime, initPcAmount, initCoinAmount, programId);
return await BuildSignSend(trader, feePayer, instr);
}

public async Task<RequestResult<string>> SendDepositAsync(DepositAccounts accounts, ulong maxCoinAmount, ulong maxPcAmount, ulong baseSide, PublicKey feePayer, Account trader, PublicKey programId)
public async Task<RequestResult<string>> SendDepositAsync(DepositAccounts accounts, ulong maxCoinAmount, ulong maxPcAmount, ulong baseSide, PublicKey feePayer, Account trader)
{
TransactionInstruction instr = RaydiumAmmProgram.Deposit(accounts, maxCoinAmount, maxPcAmount, baseSide, programId);
return await BuildSignSend(trader, feePayer, instr);
}

public async Task<RequestResult<string>> SendWithdrawAsync(WithdrawAccounts accounts, ulong amount, PublicKey feePayer, Account trader, PublicKey programId)
public async Task<RequestResult<string>> SendWithdrawAsync(WithdrawAccounts accounts, ulong amount, PublicKey feePayer, Account trader)
{
TransactionInstruction instr = RaydiumAmmProgram.Withdraw(accounts, amount, programId);
return await BuildSignSend(trader, feePayer, instr);
}

public async Task<RequestResult<string>> SendWithdrawPnlAsync(WithdrawPnlAccounts accounts, PublicKey feePayer, Account trader, PublicKey programId)
public async Task<RequestResult<string>> SendWithdrawPnlAsync(WithdrawPnlAccounts accounts, PublicKey feePayer, Account trader)
{
TransactionInstruction instr = RaydiumAmmProgram.WithdrawPnl(accounts, programId);
return await BuildSignSend(trader, feePayer, instr);
}

public async Task<RequestResult<string>> SendWithdrawSrmAsync(WithdrawSrmAccounts accounts, ulong amount, PublicKey feePayer, Account trader, PublicKey programId)
public async Task<RequestResult<string>> SendWithdrawSrmAsync(WithdrawSrmAccounts accounts, ulong amount, PublicKey feePayer, Account trader)
{
TransactionInstruction instr = RaydiumAmmProgram.WithdrawSrm(accounts, amount, programId);
return await BuildSignSend(trader, feePayer, instr);
}

public async Task<RequestResult<string>> SendSwapAsync(SwapBaseInAccounts accounts, ulong amountIn, ulong minimumAmountOut, PublicKey feePayer, Account trader, PublicKey programId)
public async Task<RequestResult<string>> SendSwapAsync(string _poolAddress, ulong amountIn, ulong minimumAmountOut, OrderSide side, PublicKey feePayer, Account trader)
{
TransactionInstruction instr = RaydiumAmmProgram.PerformSwap(accounts, amountIn, minimumAmountOut, programId);
PublicKey poolAddress = new PublicKey(_poolAddress);
var ammInfo = await GetAmmInfoAsync(poolAddress);
PublicKey baseTokenAccount = AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(trader, ammInfo.ParsedResult.BaseMint);
PublicKey quoteTokenAccount = AssociatedTokenAccountProgram.DeriveAssociatedTokenAccount(trader, ammInfo.ParsedResult.QuoteMint);

SwapBaseInAccounts swapBaseInAccounts = new SwapBaseInAccounts()
{
Amm = poolAddress,
AmmAuthority = ammAuthority,
AmmOpenOrders = ammInfo.ParsedResult.OpenOrders,
AmmTargetOrders = ammInfo.ParsedResult.TargetOrders,

SerumCoinVaultAccount = poolAddress,
SerumAsks = poolAddress,
SerumBids = poolAddress,
SerumMarket = poolAddress,
SerumEventQueue = poolAddress,
SerumPcVaultAccount = poolAddress,
SerumProgram = poolAddress,
SerumVaultSigner = poolAddress,
UserSourceOwner = trader,
BaseVaultAccount = ammInfo.ParsedResult.BaseVault,
QuoteVaultAccount = ammInfo.ParsedResult.QuoteVault,

TokenProgram = TokenProgram.ProgramIdKey

};

if (side == OrderSide.Buy)
{
//quote tokens -> base tokens
swapBaseInAccounts.UerSourceTokenAccount = quoteTokenAccount;
swapBaseInAccounts.UerDestinationTokenAccount = baseTokenAccount;
}
if (side == OrderSide.Sell)
{
//base tokens -> quote tokens
swapBaseInAccounts.UerSourceTokenAccount = baseTokenAccount;
swapBaseInAccounts.UerDestinationTokenAccount = quoteTokenAccount;
}
TransactionInstruction instr = RaydiumAmmProgram.PerformSwap(swapBaseInAccounts, amountIn, minimumAmountOut, programId);
return await BuildSignSend(trader, feePayer, instr);
}

Expand Down
2 changes: 1 addition & 1 deletion Client/RaydiumProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public static TransactionInstruction WithdrawSrm(WithdrawSrmAccounts accounts, u
public static TransactionInstruction PerformSwap(SwapBaseInAccounts accounts, ulong amountIn, ulong minimumAmountOut, PublicKey programId)
{
List<AccountMeta> keys = new()
{AccountMeta.ReadOnly(accounts.TokenProgram, false), AccountMeta.Writable(accounts.Amm, false), AccountMeta.ReadOnly(accounts.AmmAuthority, false), AccountMeta.Writable(accounts.AmmOpenOrders, false), AccountMeta.Writable(accounts.AmmTargetOrders, false), AccountMeta.Writable(accounts.PoolCoinTokenAccount, false), AccountMeta.Writable(accounts.PoolPcTokenAccount, false), AccountMeta.ReadOnly(accounts.SerumProgram, false), AccountMeta.Writable(accounts.SerumMarket, false), AccountMeta.Writable(accounts.SerumBids, false), AccountMeta.Writable(accounts.SerumAsks, false), AccountMeta.Writable(accounts.SerumEventQueue, false), AccountMeta.Writable(accounts.SerumCoinVaultAccount, false), AccountMeta.Writable(accounts.SerumPcVaultAccount, false), AccountMeta.ReadOnly(accounts.SerumVaultSigner, false), AccountMeta.Writable(accounts.UerSourceTokenAccount, false), AccountMeta.Writable(accounts.UerDestinationTokenAccount, false), AccountMeta.ReadOnly(accounts.UserSourceOwner, true)};
{AccountMeta.ReadOnly(accounts.TokenProgram, false), AccountMeta.Writable(accounts.Amm, false), AccountMeta.ReadOnly(accounts.AmmAuthority, false), AccountMeta.Writable(accounts.AmmOpenOrders, false), AccountMeta.Writable(accounts.AmmTargetOrders, false), AccountMeta.Writable(accounts.BaseVaultAccount, false), AccountMeta.Writable(accounts.QuoteVaultAccount, false), AccountMeta.ReadOnly(accounts.SerumProgram, false), AccountMeta.Writable(accounts.SerumMarket, false), AccountMeta.Writable(accounts.SerumBids, false), AccountMeta.Writable(accounts.SerumAsks, false), AccountMeta.Writable(accounts.SerumEventQueue, false), AccountMeta.Writable(accounts.SerumCoinVaultAccount, false), AccountMeta.Writable(accounts.SerumPcVaultAccount, false), AccountMeta.ReadOnly(accounts.SerumVaultSigner, false), AccountMeta.Writable(accounts.UerSourceTokenAccount, false), AccountMeta.Writable(accounts.UerDestinationTokenAccount, false), AccountMeta.ReadOnly(accounts.UserSourceOwner, true)};
byte[] _data = new byte[1200];
int offset = 0;
_data.WriteU8(9, offset);
Expand Down
84 changes: 33 additions & 51 deletions Library/Accounts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ public partial class TargetOrders
public static ulong ACCOUNT_DISCRIMINATOR => 16712735355329896817UL;
public static ReadOnlySpan<byte> ACCOUNT_DISCRIMINATOR_BYTES => new byte[] { 113, 225, 140, 255, 65, 144, 239, 231 };
public static string ACCOUNT_DISCRIMINATOR_B58 => "L3nkuN7DJxn";
public ulong[] Owner { get; set; }
public ulong[]? Owner { get; set; }

public TargetOrder[] BuyOrders { get; set; }
public TargetOrder[]? BuyOrders { get; set; }

public ulong[] Padding1 { get; set; }
public ulong[]? Padding1 { get; set; }

public BigInteger TargetX { get; set; }

Expand All @@ -46,13 +46,13 @@ public partial class TargetOrders

public BigInteger CalcPnlY { get; set; }

public TargetOrder[] SellOrders { get; set; }
public TargetOrder[]? SellOrders { get; set; }

public ulong[] Padding2 { get; set; }
public ulong[]? Padding2 { get; set; }

public ulong[] ReplaceBuyClientId { get; set; }
public ulong[]? ReplaceBuyClientId { get; set; }

public ulong[] ReplaceSellClientId { get; set; }
public ulong[]? ReplaceSellClientId { get; set; }

public ulong LastOrderNumerator { get; set; }

Expand All @@ -66,7 +66,7 @@ public partial class TargetOrders

public ulong ValidSellOrderNum { get; set; }

public ulong[] Padding3 { get; set; }
public ulong[]? Padding3 { get; set; }

public BigInteger FreeSlotBits { get; set; }

Expand Down Expand Up @@ -195,17 +195,13 @@ public partial class Fees
public ulong SwapFeeNumerator { get; set; }

public ulong SwapFeeDenominator { get; set; }
public static Fees Deserialize(ReadOnlySpan<byte> _data)
public static Fees? Deserialize(ReadOnlySpan<byte> _data)
{
int offset = 0;
ulong accountHashValue = _data.GetU64(offset);
offset += 8;
ulong accountHashValue = _data.GetU64(offset);
var result = new Fees();
if (accountHashValue != ACCOUNT_DISCRIMINATOR)
{
result = null;
return result;
}


result.MinSeparateNumerator = _data.GetU64(offset);
offset += 8;
Expand All @@ -228,15 +224,7 @@ public static Fees Deserialize(ReadOnlySpan<byte> _data)
public static int Deserialize(ReadOnlySpan<byte> _data, out Fees result,int initialOffset = 0)
{
int offset = initialOffset;
ulong accountHashValue = _data.GetU64(offset);
offset += 8;
result = new Fees();
if (accountHashValue != ACCOUNT_DISCRIMINATOR)
{
result = null;
return initialOffset;
}

result.MinSeparateNumerator = _data.GetU64(offset);
offset += 8;
result.MinSeparateDenominator = _data.GetU64(offset);
Expand All @@ -253,7 +241,7 @@ public static int Deserialize(ReadOnlySpan<byte> _data, out Fees result,int init
offset += 8;
result.SwapFeeDenominator = _data.GetU64(offset);
offset += 8;
return initialOffset - offset;
return offset - initialOffset;
}
}

Expand Down Expand Up @@ -294,50 +282,43 @@ public partial class AmmInfo

public ulong SysDecimalValue { get; set; }

public Fees Fees { get; set; }
public Fees? Fees { get; set; }

public OutPutData OutPut { get; set; }
public OutPutData? OutPut { get; set; }

public PublicKey TokenCoin { get; set; }
public PublicKey? BaseVault { get; set; }

public PublicKey TokenPc { get; set; }
public PublicKey? QuoteVault { get; set; }

public PublicKey CoinMint { get; set; }
public PublicKey? BaseMint { get; set; }

public PublicKey PcMint { get; set; }
public PublicKey? QuoteMint { get; set; }

public PublicKey LpMint { get; set; }
public PublicKey? LpMint { get; set; }

public PublicKey OpenOrders { get; set; }
public PublicKey? OpenOrders { get; set; }

public PublicKey Market { get; set; }
public PublicKey? Market { get; set; }

public PublicKey SerumDex { get; set; }
public PublicKey? SerumDex { get; set; }

public PublicKey TargetOrders { get; set; }
public PublicKey? TargetOrders { get; set; }

public PublicKey WithdrawQueue { get; set; }
public PublicKey? WithdrawQueue { get; set; }

public PublicKey TokenTempLp { get; set; }
public PublicKey? TokenTempLp { get; set; }

public PublicKey AmmOwner { get; set; }
public PublicKey? AmmOwner { get; set; }

public ulong LpAmount { get; set; }

public ulong ClientOrderId { get; set; }

public ulong[] Padding { get; set; }
public ulong[]? Padding { get; set; }

public static AmmInfo Deserialize(ReadOnlySpan<byte> _data)
{
int offset = 0;
ulong accountHashValue = _data.GetU64(offset);
offset += 8;
if (accountHashValue != ACCOUNT_DISCRIMINATOR)
{
return null;
}

AmmInfo result = new AmmInfo();
result.Status = _data.GetU64(offset);
offset += 8;
Expand Down Expand Up @@ -375,13 +356,13 @@ public static AmmInfo Deserialize(ReadOnlySpan<byte> _data)
result.Fees = resultFees;
offset += OutPutData.Deserialize(_data, offset, out var resultOutPut);
result.OutPut = resultOutPut;
result.TokenCoin = _data.GetPubKey(offset);
result.BaseVault = _data.GetPubKey(offset);
offset += 32;
result.TokenPc = _data.GetPubKey(offset);
result.QuoteVault = _data.GetPubKey(offset);
offset += 32;
result.CoinMint = _data.GetPubKey(offset);
result.BaseMint = _data.GetPubKey(offset);
offset += 32;
result.PcMint = _data.GetPubKey(offset);
result.QuoteMint = _data.GetPubKey(offset);
offset += 32;
result.LpMint = _data.GetPubKey(offset);
offset += 32;
Expand Down Expand Up @@ -409,7 +390,8 @@ public static AmmInfo Deserialize(ReadOnlySpan<byte> _data)
result.Padding[resultPaddingIdx] = _data.GetU64(offset);
offset += 8;
}

Console.WriteLine(offset);
Console.WriteLine(_data.Length.ToString());
return result;
}
}
Expand Down
Loading

0 comments on commit 47c4875

Please sign in to comment.