Skip to content

Commit

Permalink
Phoenix: Implement Swap instruction
Browse files Browse the repository at this point in the history
  • Loading branch information
skynetcap committed Nov 28, 2023
1 parent 3510bc9 commit e460c43
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.mmorrell.phoenix.model;

import lombok.Builder;
import lombok.Data;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

@Data
@Builder
public class ImmediateOrCancelOrderPacketRecord {

private byte side;
private long priceInTicks;
private long numBaseLots;
private long numQuoteLots;
private long minBaseLotsToFill;
private long minQuoteLotsToFill;
private byte selfTradeBehavior;
private long matchLimit;
private byte[] clientOrderId;
private boolean useOnlyDepositedFunds;

public byte[] toBytes() {
ByteBuffer buffer = ByteBuffer.allocate(63);
buffer.order(ByteOrder.LITTLE_ENDIAN);

buffer.put(0, side);
buffer.put(1, (byte) 1); // coption for present = 1
buffer.putLong(2, side == 0 ? 4503599627370496L : 0x00);
buffer.putLong(10, 0x00); //numBaseLots
buffer.putLong(18, numQuoteLots);
buffer.putLong(26, 0x00); // min base lots to fill
buffer.putLong(34, 0x00); // quote lots to Fill
buffer.put(42, selfTradeBehavior);
buffer.put(43, (byte) 0); // coption for not present = 0
// buffer.putLong(44, matchLimit); // skip matchLimit since it's coption is 0
buffer.put(44, clientOrderId); // go straight to clientOrderId from the coption 0 byte
buffer.put(60, useOnlyDepositedFunds ? (byte) 1 : (byte) 0);
buffer.put(61, (byte) 0); // coption for lastValidSlot (0 = ignored)
buffer.put(62, (byte) 0); // coption for lastValidUnixTimestampInSeconds

return buffer.array();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -351,4 +351,9 @@ public long convertSizeToNumBaseLots(double size) {

return (long) result;
}

public long convertSizeToNumQuoteLots(double size) {
return (long) (size * Math.pow(10, phoenixMarketHeader.getQuoteDecimals())) /
phoenixMarketHeader.getQuoteLotSize();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mmorrell.phoenix.program;

import com.mmorrell.phoenix.model.ImmediateOrCancelOrderPacketRecord;
import com.mmorrell.phoenix.model.LimitOrderPacketRecord;
import lombok.extern.slf4j.Slf4j;
import org.p2p.solanaj.core.AccountMeta;
Expand All @@ -20,9 +21,6 @@ public class PhoenixProgram extends Program {
PublicKey.valueOf("PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY");
private static final PublicKey TOKEN_PROGRAM_ID =
PublicKey.valueOf("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
private static final PublicKey SYSVAR_RENT_PUBKEY =
PublicKey.valueOf("SysvarRent111111111111111111111111111111111");


public static TransactionInstruction placeLimitOrder(PublicKey market, PublicKey trader,
PublicKey baseAccount,
Expand Down Expand Up @@ -130,4 +128,39 @@ private static byte[] encodeCancelAllOrdersWithFreeFundsInstruction() {
result.put(0, (byte) 7);
return result.array();
}

public static TransactionInstruction swap(PublicKey market, PublicKey trader, PublicKey baseAccount,
PublicKey quoteAccount, PublicKey baseVault, PublicKey quoteVault,
ImmediateOrCancelOrderPacketRecord limitOrderPacketRecord) {
List<AccountMeta> accountMetas = new ArrayList<>();

accountMetas.add(new AccountMeta(PHOENIX_PROGRAM_ID, false, false));
accountMetas.add(new AccountMeta(PhoenixSeatManagerProgram.PHOENIX_LOG_AUTHORITY_ID, false, false));
accountMetas.add(new AccountMeta(market, false, true));
accountMetas.add(new AccountMeta(trader, true, false));
accountMetas.add(new AccountMeta(baseAccount, false, true));
accountMetas.add(new AccountMeta(quoteAccount, false, true));
accountMetas.add(new AccountMeta(baseVault, false, true));
accountMetas.add(new AccountMeta(quoteVault, false, true));
accountMetas.add(new AccountMeta(TOKEN_PROGRAM_ID, false, false));

byte[] transactionData = encodeSwapInstruction(limitOrderPacketRecord);

return createTransactionInstruction(
PHOENIX_PROGRAM_ID,
accountMetas,
transactionData
);
}

private static byte[] encodeSwapInstruction(ImmediateOrCancelOrderPacketRecord immediateOrCancelOrderPacketRecord) {
ByteBuffer result = ByteBuffer.allocate(65);
result.order(ByteOrder.LITTLE_ENDIAN);

result.put(0, (byte) 0); // discriminator 0 = swap
result.put(1, (byte) 2); // 1 = limit, 2 = IoC
result.put(2, immediateOrCancelOrderPacketRecord.toBytes());

return result.array();
}
}
67 changes: 67 additions & 0 deletions phoenix/src/test/java/PhoenixTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import com.google.common.io.Resources;
import com.mmorrell.metaplex.manager.MetaplexManager;
import com.mmorrell.phoenix.manager.PhoenixManager;
import com.mmorrell.phoenix.model.ImmediateOrCancelOrderPacketRecord;
import com.mmorrell.phoenix.model.LimitOrderPacketRecord;
import com.mmorrell.phoenix.model.PhoenixMarket;
import com.mmorrell.phoenix.model.PhoenixMarketHeader;
Expand Down Expand Up @@ -74,6 +75,72 @@ public void phoenixGetMarketsTest() throws RpcException {
});
}

@Test
public void swapTest() throws IOException, RpcException {
PhoenixManager phoenixManager = new PhoenixManager(client);
Account tradingAccount = Account.fromJson(
Resources.toString(
Resources.getResource("mikefsWLEcNYHgsiwSRr6PVd7yVcoKeaURQqeDE1tXN.json"),
Charset.defaultCharset()
)
);

Optional<PhoenixMarket> marketOptional = phoenixManager.getMarket(
new PublicKey("4DoNfFBfF7UokCC2FQzriy7yHK6DY6NVdYpuekQ5pRgg")
);

if (marketOptional.isEmpty()) {
log.error("Unable to get market for test.");
return;
}

PhoenixMarket market = marketOptional.get();
log.info("Market: {}", market);

double amountUsd = 0.10;

ImmediateOrCancelOrderPacketRecord iocOrder = ImmediateOrCancelOrderPacketRecord.builder()
.side((byte) 0)
.numQuoteLots(market.convertSizeToNumQuoteLots(amountUsd))
.selfTradeBehavior((byte) 1)
.clientOrderId(new byte[]{})
.useOnlyDepositedFunds(false)
.build();

Transaction orderTx = new Transaction();
orderTx.addInstruction(
ComputeBudgetProgram.setComputeUnitPrice(
123
)
);

orderTx.addInstruction(
ComputeBudgetProgram.setComputeUnitLimit(
130_000
)
);

orderTx.addInstruction(
PhoenixProgram.swap(
SOL_USDC_MARKET,
tradingAccount.getPublicKey(),
new PublicKey("Avs5RSYyecvLnt9iFYNQX5EMUun3egh3UNPw8P6ULbNS"),
new PublicKey("A6Jcj1XV6QqDpdimmL7jm1gQtSP62j8BWbyqkdhe4eLe"),
market.getPhoenixMarketHeader().getBaseVaultKey(),
market.getPhoenixMarketHeader().getQuoteVaultKey(),
iocOrder
)
);

String placeLimitOrderTx = client.getApi().sendTransaction(
orderTx,
List.of(tradingAccount),
client.getApi().getRecentBlockhash(Commitment.PROCESSED)
);
log.info("Swap order in transaction: {}, {}", iocOrder, placeLimitOrderTx);

}

// Given a marketId, and double values, convert to lots/atoms
@Test
public void orderLotsConversionTest() {
Expand Down

0 comments on commit e460c43

Please sign in to comment.