Skip to content

Commit

Permalink
Update Jupiter adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
emersonliuuu committed Feb 1, 2023
1 parent cd1bb48 commit 7f92b3c
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 146 deletions.
2 changes: 2 additions & 0 deletions src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export class GatewayBuilder {
this._metadata.routes = Boolean(this._metadata.routes)
? [...this._metadata.routes, await protocol.getRoute()]
: [await protocol.getRoute()];
this._metadata.addressLookupTables = protocol.getAddressLookupTables().map((a) => a.key);
break;
default:
throw new Error("Unsupported Protocol");
Expand Down Expand Up @@ -1739,6 +1740,7 @@ export class GatewayBuilder {
async v0Transactions(addressLookupTable: anchor.web3.PublicKey[] = []): Promise<anchor.web3.VersionedTransaction[]> {
return compressV0(this._transactions, this._provider.wallet.publicKey, this._provider.connection, [
...ADDRESS_LOOKUP_TABLES,
...this._metadata.addressLookupTables,
...addressLookupTable,
]);
}
Expand Down
196 changes: 75 additions & 121 deletions src/protocols/jupiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ import * as anchor from "@project-serum/anchor";
import { Jupiter, RouteInfo } from "@jup-ag/core";
import JSBI from "jsbi";
import { GatewayParams, IProtocolSwap, PAYLOAD_SIZE, SwapParams } from "../types";
import { GATEWAY_PROGRAM_ID, JUPITER_ADAPTER_PROGRAM_ID, JUPITER_PROGRAM_ID, WSOL } from "../ids";
import { getAssociatedTokenAddress } from "@solana/spl-token-v2";
import { GATEWAY_PROGRAM_ID, JUPITER_ADAPTER_PROGRAM_ID, JUPITER_PROGRAM_ID } from "../ids";
import { Gateway } from "@dappio-wonderland/gateway-idls";
import { getActivityIndex, getGatewayAuthority, sigHash } from "../utils";
import { struct, u64 } from "@project-serum/borsh";
import { BN } from "bn.js";

// Parameter for storing `SwapLeg` from Jupiter
const MAX_SWAP_CONFIG_SIZE = 30;
Expand All @@ -16,8 +13,7 @@ const MAX_SWAP_CONFIG_SIZE = 30;
// https://github.com/solana-labs/solana/issues/26641
const MAX_ROUTE_HOP = 2;
// Constraint to avoid exceed tx size limit after adding gateway keys in swap tx Jupiter generated
const MAX_STATIC_KEYS = 27;
const MAX_RAW_SWAP_TX_SIZE = 1072;
const MAX_RAW_SWAP_TX_SIZE = anchor.web3.PACKET_DATA_SIZE - anchor.web3.PUBLIC_KEY_LENGTH * 5 - 8; //1072;

interface ProtocolJupiterParams extends SwapParams {
userKey: anchor.web3.PublicKey;
Expand Down Expand Up @@ -55,6 +51,7 @@ export class ProtocolJupiter implements IProtocolSwap {

this._bestRoute = routes.routesInfos[0];
for (let [i, route] of routes.routesInfos.entries()) {
console.log(i, ":");
const { isLegible, swapTransaction, addressLookupTableAccounts } = await this._legibleRoute(route);
if (isLegible) {
this._bestRoute = route;
Expand All @@ -66,14 +63,15 @@ export class ProtocolJupiter implements IProtocolSwap {
throw "Error: Failed to find a route that can pass through gateway.";
}
}
console.log("========");
}

async swap(): Promise<{ txs: (anchor.web3.Transaction | anchor.web3.VersionedTransaction)[]; input: Buffer }> {
async swap(): Promise<{ txs: anchor.web3.Transaction[]; input: Buffer }> {
// Handle payload input here
let payload = Buffer.alloc(PAYLOAD_SIZE);
let swapRouteConfig = Buffer.alloc(MAX_SWAP_CONFIG_SIZE);

let txs: (anchor.web3.Transaction | anchor.web3.VersionedTransaction)[] = [];
let txs: anchor.web3.Transaction[] = [];
const preInstructions: anchor.web3.TransactionInstruction[] = [];
const postInstructions: anchor.web3.TransactionInstruction[] = [];
let remainingAccounts: anchor.web3.AccountMeta[];
Expand All @@ -99,88 +97,26 @@ export class ProtocolJupiter implements IProtocolSwap {

if (isTxV2) {
const swapTx = swapTransaction as anchor.web3.VersionedTransaction;

const originKeysLen = swapTx.message.staticAccountKeys.length;
const writeIndexStart =
swapTx.message.staticAccountKeys.length - swapTx.message.header.numReadonlyUnsignedAccounts;
const keysToAppend = 5;
const gatewayProgramIndex = originKeysLen + 1;
const gatewayStateIndex = writeIndexStart;
const jupiterAdapterProgramIndex = originKeysLen + 2;
const activityIndex = originKeysLen + 3;
const authorityIndex = originKeysLen + 4;

let readOnlyKeys: anchor.web3.PublicKey[] = [];
for (let i = 0; i < swapTx.message.header.numReadonlyUnsignedAccounts; i++) {
readOnlyKeys = [swapTx.message.staticAccountKeys.pop(), ...readOnlyKeys];
}
swapTx.message.staticAccountKeys.push(this._gatewayStateKey);

swapTx.message.staticAccountKeys.push(
...readOnlyKeys,
GATEWAY_PROGRAM_ID,
JUPITER_ADAPTER_PROGRAM_ID,
await getActivityIndex(this._params.userKey),
getGatewayAuthority()
);
let jupiterProgramIndex = swapTx.message.staticAccountKeys.length - 1;
for (let [index, pubkey] of swapTx.message.staticAccountKeys.entries()) {
if (pubkey.equals(JUPITER_PROGRAM_ID)) {
jupiterProgramIndex = index;
}
}
swapTx.message.header.numReadonlyUnsignedAccounts = swapTx.message.header.numReadonlyUnsignedAccounts + 4;

let swapIndex = 0;
let swapIx: anchor.web3.MessageCompiledInstruction;
for (let [index, ix] of swapTx.message.compiledInstructions.entries()) {
ix.accountKeyIndexes = ix.accountKeyIndexes.map((i) => {
return i < originKeysLen ? (i < writeIndexStart ? i : i + 1) : i + keysToAppend;
});
ix.programIdIndex = ix.programIdIndex < writeIndexStart ? ix.programIdIndex : ix.programIdIndex + 1;
swapTx.message.compiledInstructions[index] = ix;
if (ix.programIdIndex == jupiterProgramIndex) {
swapIndex = index;
swapIx = ix;
const decompiledMessage = anchor.web3.TransactionMessage.decompile(swapTx.message, {
addressLookupTableAccounts,
});
const tx = new anchor.web3.Transaction();
for (let ix of decompiledMessage.instructions) {
if (ix.programId.equals(JUPITER_PROGRAM_ID)) {
const { _swapAmountConfig, _swapRouteConfig } = this._getSwapConfig(ix.data);
_swapAmountConfig.copy(payload);
_swapRouteConfig.copy(swapRouteConfig);
tx.add(await this._wrap(ix));
} else {
tx.add(ix);
}
}
const swapDiscriminator = Buffer.from(sigHash("global", "swap"), "hex");

const rawData = Uint8Array.from(swapIx.data);
const swapConfig = {
protocolConfig: Buffer.from(rawData.slice(8, rawData.byteLength - 19)), // (regular)7 - 12 bytes, but sometimes(split swap) might be upto 17 bytes
inputAmount: Buffer.from(rawData.slice(rawData.byteLength - 19, rawData.byteLength - 11)), // u64
outputAmount: Buffer.from(rawData.slice(rawData.byteLength - 11, rawData.byteLength - 3)), // u64
slippageBps: Buffer.from(rawData.slice(rawData.byteLength - 3, rawData.byteLength - 1)), // u16
platformFeeBps: Buffer.from(rawData.slice(rawData.byteLength - 1, rawData.byteLength)), // u8
};

payload.set(swapConfig.inputAmount);
payload.set(swapConfig.outputAmount, 8);
payload.set(swapConfig.slippageBps, 16);
if (swapConfig.protocolConfig.length < MAX_SWAP_CONFIG_SIZE) {
swapRouteConfig.set([swapConfig.protocolConfig.length], 0);
swapRouteConfig.set(swapConfig.protocolConfig, 1);
} else if (swapConfig.protocolConfig.length == MAX_SWAP_CONFIG_SIZE) {
payload.set(swapConfig.protocolConfig, 0);
} else {
throw `Error: Currently Gateway only support swap config under ${MAX_SWAP_CONFIG_SIZE} bytes,\nplease change to another route to fix it or wait for the updates.`;
}
(this._gatewayParams.swapAmountConfig as Uint8Array[]).push(payload.subarray(0, 18));
(this._gatewayParams.swapRouteConfig as Uint8Array[]).push(swapRouteConfig);

swapIx.data = swapDiscriminator;
swapIx.programIdIndex = gatewayProgramIndex;
swapIx.accountKeyIndexes = [
gatewayStateIndex,
jupiterAdapterProgramIndex,
jupiterProgramIndex,
activityIndex,
authorityIndex,
...swapIx.accountKeyIndexes,
];
swapTx.message.compiledInstructions[swapIndex] = swapIx;
txs = [swapTx];
txs.push(tx);

return { txs, input: payload };
} else {
let isPreIx = true;
let swapIx: anchor.web3.TransactionInstruction;
Expand All @@ -197,26 +133,9 @@ export class ProtocolJupiter implements IProtocolSwap {
}

// Extract config
const rawData = Uint8Array.from(swapIx.data);
const swapConfig = {
protocolConfig: Buffer.from(rawData.slice(8, rawData.byteLength - 19)), // (regular)7 - 9 bytes, but sometimes(split swap) might be upto 17 bytes
inputAmount: Buffer.from(rawData.slice(rawData.byteLength - 19, rawData.byteLength - 11)), // u64
outputAmount: Buffer.from(rawData.slice(rawData.byteLength - 11, rawData.byteLength - 3)), // u64
slippageBps: Buffer.from(rawData.slice(rawData.byteLength - 3, rawData.byteLength - 1)), // u16
platformFeeBps: Buffer.from(rawData.slice(rawData.byteLength - 1, rawData.byteLength)), // u8
};

payload.set(swapConfig.inputAmount);
payload.set(swapConfig.outputAmount, 8);
payload.set(swapConfig.slippageBps, 16);
if (swapConfig.protocolConfig.length < MAX_SWAP_CONFIG_SIZE) {
swapRouteConfig.set([swapConfig.protocolConfig.length], 0);
swapRouteConfig.set(swapConfig.protocolConfig, 1);
} else if (swapConfig.protocolConfig.length == MAX_SWAP_CONFIG_SIZE) {
payload.set(swapConfig.protocolConfig, 0);
} else {
throw `Error: Currently Gateway only support swap config under ${MAX_SWAP_CONFIG_SIZE} bytes,\nplease change to another route to fix it or wait for the updates.`;
}
const { _swapAmountConfig, _swapRouteConfig } = this._getSwapConfig(swapIx.data);
_swapAmountConfig.copy(payload);
_swapRouteConfig.copy(swapRouteConfig);
(this._gatewayParams.swapAmountConfig as Uint8Array[]).push(payload.subarray(0, 18));
(this._gatewayParams.swapRouteConfig as Uint8Array[]).push(swapRouteConfig);

Expand Down Expand Up @@ -252,6 +171,10 @@ export class ProtocolJupiter implements IProtocolSwap {
return JSBI.toNumber(this._bestRoute.outAmount);
}

getAddressLookupTables(): anchor.web3.AddressLookupTableAccount[] {
return this._addressLookupTableAccounts;
}

async getRoute() {
return {
...this._bestRoute,
Expand All @@ -271,25 +194,56 @@ export class ProtocolJupiter implements IProtocolSwap {
routeInfo,
userPublicKey: this._params.userKey,
});
const isLegible =
routeInfo.marketInfos.length <= MAX_ROUTE_HOP && swapTransaction.serialize().length <= MAX_RAW_SWAP_TX_SIZE;
if (isLegible) console.log(swapTransaction.serialize().length);
return { isLegible, swapTransaction, addressLookupTableAccounts };
}

let isTxV2 = true;
if ((swapTransaction as anchor.web3.Transaction).instructions) {
isTxV2 = false;
}
private _getSwapConfig(data: Buffer): {
_swapAmountConfig: Buffer;
_swapRouteConfig: Buffer;
} {
let _swapAmountConfig = Buffer.alloc(PAYLOAD_SIZE);
let _swapRouteConfig = Buffer.alloc(MAX_SWAP_CONFIG_SIZE);
// Extract config
const rawData = Uint8Array.from(data);
const swapConfig = {
protocolConfig: Buffer.from(rawData.slice(8, rawData.byteLength - 19)), // (regular)7 - 9 bytes, but sometimes(split swap) might be upto 17 bytes
inputAmount: Buffer.from(rawData.slice(rawData.byteLength - 19, rawData.byteLength - 11)), // u64
outputAmount: Buffer.from(rawData.slice(rawData.byteLength - 11, rawData.byteLength - 3)), // u64
slippageBps: Buffer.from(rawData.slice(rawData.byteLength - 3, rawData.byteLength - 1)), // u16
platformFeeBps: Buffer.from(rawData.slice(rawData.byteLength - 1, rawData.byteLength)), // u8
};

if (isTxV2) {
const versionedTx = swapTransaction as anchor.web3.VersionedTransaction;
const isLegible =
versionedTx.message.staticAccountKeys.length <= MAX_STATIC_KEYS
? versionedTx.serialize().length < MAX_RAW_SWAP_TX_SIZE
? true
: false
: false;
return { isLegible, swapTransaction, addressLookupTableAccounts };
_swapAmountConfig.set(swapConfig.inputAmount);
_swapAmountConfig.set(swapConfig.outputAmount, 8);
_swapAmountConfig.set(swapConfig.slippageBps, 16);
if (swapConfig.protocolConfig.length < MAX_SWAP_CONFIG_SIZE) {
_swapRouteConfig.set([swapConfig.protocolConfig.length], 0);
_swapRouteConfig.set(swapConfig.protocolConfig, 1);
} else if (swapConfig.protocolConfig.length == MAX_SWAP_CONFIG_SIZE) {
_swapAmountConfig.set(swapConfig.protocolConfig, 0);
} else {
const legacyTx = swapTransaction as anchor.web3.Transaction;
const isLegible = legacyTx.serialize().length <= MAX_RAW_SWAP_TX_SIZE;
return { isLegible, swapTransaction, addressLookupTableAccounts };
throw `Error: Currently Gateway only support swap config under ${MAX_SWAP_CONFIG_SIZE} bytes,\nplease change to another route to fix it or wait for the updates.`;
}

return { _swapAmountConfig, _swapRouteConfig };
}

private async _wrap(ix: anchor.web3.TransactionInstruction): Promise<anchor.web3.TransactionInstruction> {
const swapDiscriminator = Buffer.from(sigHash("global", "swap"), "hex");
ix.data = swapDiscriminator;
ix.programId = GATEWAY_PROGRAM_ID;
ix.keys = [
{ pubkey: this._gatewayStateKey, isSigner: false, isWritable: true },
{ pubkey: JUPITER_ADAPTER_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: JUPITER_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: await getActivityIndex(this._params.userKey), isSigner: false, isWritable: false },
{ pubkey: getGatewayAuthority(), isSigner: false, isWritable: false },
...ix.keys,
];

return ix;
}
}
4 changes: 3 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import { RouteInfo, TransactionFeeInfo } from "@jup-ag/core";
export const PAYLOAD_SIZE = 32;

export interface IProtocolSwap {
swap: () => Promise<{ txs: (anchor.web3.Transaction | anchor.web3.VersionedTransaction)[]; input: Buffer }>;
swap: () => Promise<{ txs: anchor.web3.Transaction[]; input: Buffer }>;
getSwapMinOutAmount: () => number;

build?: () => void;
getRoute?: () => any;
getAddressLookupTables?: () => anchor.web3.AddressLookupTableAccount[];
}

export interface IProtocolPool {
Expand Down Expand Up @@ -243,6 +244,7 @@ export interface GatewayMetadata {
toTokenMint?: anchor.web3.PublicKey;
addLiquidityTokenMint?: anchor.web3.PublicKey;
removeLiquiditySingleToTokenMint?: anchor.web3.PublicKey;
addressLookupTables?: anchor.web3.PublicKey[];
}

export interface SwapParams {
Expand Down
43 changes: 19 additions & 24 deletions tests/testAdapterJupiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,26 @@ describe("Gateway", () => {

anchor.setProvider(provider);

const zapInAmount = 10000;
const zapInAmount = 1000000;

it("Swap in Jupiter", async () => {
const swapParams1: SwapParams = {
protocol: SupportedProtocols.Jupiter,
fromTokenMint: new PublicKey(
// "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" // USDC
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" // USDC
// "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R" // RAY
"So11111111111111111111111111111111111111112" // WSOL
// "So11111111111111111111111111111111111111112" // WSOL
// "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt" // SRM
// "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj" // stSOL
// "2QHx6MmrsAXSKLynJ55GofBbveYaDPLvn6qgdefey5za" // GMT
// "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE" // Orca
),
toTokenMint: new PublicKey(
// "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R" // RAY
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" // USDT
// "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" // USDT
// "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" // USDC
// "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt" // SRM
"orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE" // Orca
// "So11111111111111111111111111111111111111112" // WSOL
// "GENEtH5amGSi8kHAtQoezp1XEXwZJ8vcuePYnXdKrMYz" // GENE
// "66edZnAPEJSxnAK4SckuupssXpbu5doV57FUcghaqPsY" // PRGC
Expand Down Expand Up @@ -97,32 +101,23 @@ describe("Gateway", () => {
// console.log(`swapInAmount: ${gateway.params.swapInAmount}`);
// console.log(`swapMinOutAmount: ${gateway.params.swapMinOutAmount}`);

const txs = gateway.transactions();
const txs = await gateway.v0Transactions();
console.log(txs);

console.log("======");
console.log("Txs are sent...");
const recentBlockhash = await connection.getLatestBlockhash();
for (let tx of txs) {
if ((tx as anchor.web3.Transaction).instructions) {
const sig = await provider.sendAndConfirm(tx as anchor.web3.Transaction, [], {
skipPreflight: true,
commitment: "confirmed",
} as unknown as anchor.web3.ConfirmOptions);
console.log(sig);
} else {
const txV2 = tx as anchor.web3.VersionedTransaction;
txV2.message.recentBlockhash = recentBlockhash.blockhash;
console.log(txV2.serialize().length);
txV2.sign([wallet.payer]);
let versionMessage = txV2.serialize();
//const result = sendAndConfirmTransaction(connection, tx, wallet);
const sig = await connection.sendRawTransaction(versionMessage, {
skipPreflight: true,
commitment: "confirmed",
} as unknown as anchor.web3.ConfirmOptions);
console.log(sig);
}
tx.message.recentBlockhash = recentBlockhash.blockhash;
console.log(tx.serialize().length);
tx.sign([wallet.payer]);
let versionMessage = tx.serialize();
//const result = sendAndConfirmTransaction(connection, tx, wallet);
const sig = await connection.sendRawTransaction(versionMessage, {
skipPreflight: true,
commitment: "confirmed",
} as unknown as anchor.web3.ConfirmOptions);
console.log(sig);
}
console.log("Txs are executed");
console.log("======");
Expand Down

0 comments on commit 7f92b3c

Please sign in to comment.