Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions cpi-swap-program/package.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
{
"license": "ISC",
"license": "ISC",
"scripts": {
"lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
},
"dependencies": {
"@coral-xyz/anchor": "^0.30.1"
"@coral-xyz/anchor": "^0.31.1",
"@solana/web3.js": "^1.98.2",
"axios": "^1.10.0"
},
"devDependencies": {
"chai": "^4.3.4",
"mocha": "^9.0.3",
"ts-mocha": "^10.0.0",
"@types/bn.js": "^5.1.0",
"@types/chai": "^4.3.0",
"@types/mocha": "^9.0.0",
"typescript": "^4.3.5",
"prettier": "^2.6.2"
"chai": "^4.3.4",
"mocha": "^9.0.3",
"prettier": "^2.6.2",
"ts-mocha": "^10.0.0",
"typescript": "^4.3.5"
}
}
4 changes: 2 additions & 2 deletions cpi-swap-program/programs/cpi-swap-program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
anchor-debug = []

[dependencies]
anchor-lang = { version = "0.30.1", default-features = true, features = [
anchor-lang = { version = "0.31.1", default-features = true, features = [
"init-if-needed",
"event-cpi",
] }
anchor-spl = { version = "0.30.1", default-features = true }
anchor-spl = { version = "0.31.1", default-features = true }
160 changes: 155 additions & 5 deletions cpi-swap-program/tests/cpi-swap-program.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,166 @@
import * as anchor from "@coral-xyz/anchor";
import {
PublicKey,
AddressLookupTableAccount,
TransactionMessage,
Keypair,
VersionedTransaction,
Signer,
} from "@solana/web3.js";
import { Program } from "@coral-xyz/anchor";
import { CpiSwapProgram } from "../target/types/cpi_swap_program";
import axios from "axios";
import { TOKEN_PROGRAM_ID } from "@coral-xyz/anchor/dist/cjs/utils/token";

describe("cpi-swap-program", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
anchor.setProvider(
anchor.AnchorProvider.local("https://api.mainnet-beta.solana.com", {
preflightCommitment: "confirmed",
commitment: "confirmed",
})
);

const program = anchor.workspace.CpiSwapProgram as Program<CpiSwapProgram>;
const provider = anchor.getProvider();

// replace with your own keypair
const payer: Signer = Keypair.generate();

const connection = provider.connection;

// swaping tokens in a PDA, CPI into JUPITER Aggregator v6
it("Is initialized!", async () => {
// Add your test here.
const tx = await program.methods.initialize().rpc();
console.log("Your transaction signature", tx);
const VAULT_SEED = Buffer.from("vault");

// derive the vault PDA
const [vaultPDA, vaultBump] = PublicKey.findProgramAddressSync(
[VAULT_SEED],
program.programId
);

// replace with your valid input and output mint
// For example, swaping 1 USDC and USDT
const inputMint = new PublicKey(
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
);
const outputMint = new PublicKey(
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
);

// amount in raw format (e.g., 1 SOL = 1000000000)
const amount = "1000000";

// get the swap instructions from Jupiter API
const swap = await get_swap(vaultPDA, inputMint, outputMint, amount);

// instructions
const computeBudgetIxs = swap.computeBudgetInstructions.map(deserializeIx);
const setupInstructions = swap.setupInstructions.map(deserializeIx);
const cleanupInstructions = swap.cleanupInstruction
? swap.cleanupInstruction?.map(deserializeIx)
: [];
const otherInstructions = swap.otherInstructions
? swap.otherInstructions.map(deserializeIx)
: [];

// get the accounts needed to cpi into the Jupiter program
const remainingAccounts = swap.swapInstruction.accounts.map((acc: any) => ({
pubkey: new PublicKey(acc.pubkey),
isWritable: acc.isWritable,
isSigner: false,
}));

// decode the instruction data
const data = Buffer.from(swap.swapInstruction.data, "base64");

const ix = await program.methods
.swap(data)
.accounts({
inputMint,
inputMintProgram: TOKEN_PROGRAM_ID,
outputMint,
outputMintProgram: TOKEN_PROGRAM_ID,
})
.remainingAccounts(remainingAccounts)
.instruction();

const latestBlockhash = await connection.getLatestBlockhash();

const altAddresses = swap.addressLookupTableAddresses || [];

// resolve Address Lookup Tables (ALT)
const altLookups = await Promise.all(
altAddresses.map(async (address: any) => {
const alt = await connection.getAddressLookupTable(
new PublicKey(address)
);
if (!alt.value) throw new Error(`ALT not found: ${address}`);
return new AddressLookupTableAccount({
key: new PublicKey(address),
state: alt.value.state,
});
})
);

// Build Transaction Message with ALT
const messageV0 = new TransactionMessage({
payerKey: payer.publicKey,
recentBlockhash: latestBlockhash.blockhash,
instructions: [
...computeBudgetIxs,
...setupInstructions,
ix,
...cleanupInstructions,
...otherInstructions,
],
}).compileToV0Message(altLookups);

const tx = new VersionedTransaction(messageV0);
tx.sign([payer]);

const sig = await connection.sendTransaction(tx);

console.log("✅ Signature:", sig);
});
});

async function get_swap(
address: PublicKey,
input_mint: PublicKey,
output_mint: PublicKey,
amount: string
) {
// reference - https://dev.jup.ag/docs/api/swap-api/quote
const quote_url = `https://lite-api.jup.ag/swap/v1/quote?inputMint=${input_mint.toString()}&outputMint=${output_mint.toString()}&amount=${amount}`;
const quote = await axios.get(quote_url);

// reference - https://dev.jup.ag/docs/api/swap-api/swap-instructions
let config = {
method: "post",
maxBodyLength: Infinity,
url: "https://lite-api.jup.ag/swap/v1/swap-instructions",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
data: JSON.stringify({
userPublicKey: address.toString(),
quoteResponse: quote.data,
}),
};

const swap = await axios.request(config);
return swap.data;
}

function deserializeIx(ix: any) {
return {
programId: new PublicKey(ix.programId),
keys: ix.accounts.map((acc: any) => ({
pubkey: new PublicKey(acc.pubkey),
isWritable: acc.isWritable,
isSigner: acc.isSigner,
})),
data: Buffer.from(ix.data, "base64"),
};
}
Loading