From bcb535aec610623f43178fdbbbafd4222d2b077a Mon Sep 17 00:00:00 2001 From: Yuru Shao Date: Sun, 9 Jun 2024 19:12:16 -0700 Subject: [PATCH 1/5] anchor: add test suite for api integ tests --- anchor/Anchor.toml | 7 ++-- anchor/src/client/jupiter.ts | 1 - anchor/tests/glam_api_tx.spec.ts | 69 ++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 anchor/tests/glam_api_tx.spec.ts diff --git a/anchor/Anchor.toml b/anchor/Anchor.toml index 4c108f21..1bf70fcf 100644 --- a/anchor/Anchor.toml +++ b/anchor/Anchor.toml @@ -15,14 +15,15 @@ glam = "Gco1pcjxCMYjKJjSNJ7mKV7qezeUTE7arXJgy7PAPNRc" url = "https://api.apr.dev" [provider] -cluster = "localnet" +# cluster = "localnet" #cluster = "devnet" -#cluster = "mainnet" +cluster = "mainnet" wallet = "~/.config/solana/id.json" [scripts] +test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_api_tx" #test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_crud" -test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_investor" +# test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_investor" #test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_drift" #test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_staking" #test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_jupiter" diff --git a/anchor/src/client/jupiter.ts b/anchor/src/client/jupiter.ts index 7a9c8b13..74110d9f 100644 --- a/anchor/src/client/jupiter.ts +++ b/anchor/src/client/jupiter.ts @@ -194,7 +194,6 @@ export class JupiterClient { const addressLookupTableAccounts = await this.getAdressLookupTableAccounts( addressLookupTableAddresses ); - let payerPublicKey; try { payerPublicKey = await this.base.getWalletSigner().publicKey; diff --git a/anchor/tests/glam_api_tx.spec.ts b/anchor/tests/glam_api_tx.spec.ts new file mode 100644 index 00000000..cb22edc4 --- /dev/null +++ b/anchor/tests/glam_api_tx.spec.ts @@ -0,0 +1,69 @@ +import * as anchor from "@coral-xyz/anchor"; +import { + Transaction, + SystemProgram, + sendAndConfirmTransaction +} from "@solana/web3.js"; +import { GlamClient } from "../src/client"; + +const API = "https://api.glam.systems"; + +describe("glam_api_tx", () => { + const glamClient = new GlamClient(); + + it("Wrap sol", async () => { + const response = await fetch(`${API}/tx/wsol/wrap`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + manager: "gLJHKPrZLGBiBZ33hFgZh6YnsEhTVxuRT17UCqNp6ff", + fund: "4gAcSdfSAxVPcxj2Hi3AvKKViGat3iUysDD5ZzbqhDTk", + amount: 1000000 + }) + }); + const { tx } = await response.json(); + console.log("Wrap tx:", tx); + const t = Transaction.from(Buffer.from(tx, "hex")); + t.sign(glamClient.getWalletSigner()); + try { + const txId = await glamClient.provider.connection.sendRawTransaction( + t.serialize() + ); + console.log("Wrap txId:", txId); + } catch (error) { + console.log("Error", error); + throw error; + } + }); + + it("unwrap", async () => { + const response = await fetch(`${API}/tx/wsol/unwrap`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + manager: "gLJHKPrZLGBiBZ33hFgZh6YnsEhTVxuRT17UCqNp6ff", + fund: "4gAcSdfSAxVPcxj2Hi3AvKKViGat3iUysDD5ZzbqhDTk" + }) + }); + const { tx } = await response.json(); + console.log("unwrap tx", tx); + + const t = Transaction.from(Buffer.from(tx, "hex")); + t.recentBlockhash = ( + await glamClient.provider.connection.getLatestBlockhash() + ).blockhash; + console.log("unwrap recentBlockhash", t.recentBlockhash); + t.sign(glamClient.getWalletSigner()); + try { + const txId = await glamClient.provider.connection.sendRawTransaction( + t.serialize() + ); + console.log("Wrap txId:", txId); + } catch (error) { + console.log("Error", error); + throw error; + } + }); +}); From 698d4dcf5afb656750b644b7122d91319a4043cb Mon Sep 17 00:00:00 2001 From: Yuru Shao Date: Sun, 9 Jun 2024 22:03:10 -0700 Subject: [PATCH 2/5] Fix manager address empty --- anchor/src/client/base.ts | 6 ++-- anchor/src/client/jupiter.ts | 12 +++++++- anchor/tests/glam_api_tx.spec.ts | 49 ++++++++++++++++++++++++++++++-- api/src/routers/tx.ts | 2 +- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/anchor/src/client/base.ts b/anchor/src/client/base.ts index eb04aee3..b3aed3d7 100644 --- a/anchor/src/client/base.ts +++ b/anchor/src/client/base.ts @@ -133,11 +133,11 @@ export class BaseClient { } getManager(): PublicKey { - return this.provider?.publicKey || new PublicKey(0); + return this.provider?.publicKey; } - getManagerAta(mint: PublicKey): PublicKey { - return getAssociatedTokenAddressSync(mint, this.getManager()); + getManagerAta(mint: PublicKey, manager?: PublicKey): PublicKey { + return getAssociatedTokenAddressSync(mint, this.getManager() || manager); } getWalletSigner(): Keypair { diff --git a/anchor/src/client/jupiter.ts b/anchor/src/client/jupiter.ts index 74110d9f..2768202d 100644 --- a/anchor/src/client/jupiter.ts +++ b/anchor/src/client/jupiter.ts @@ -174,6 +174,15 @@ export class JupiterClient { const swapIx: { data: any; keys: AccountMeta[] } = this.ixDataToTransactionInstruction(swapInstruction); + let inputAta; + if (this.base.getManager()) { + inputAta = this.base.getManagerAta(new PublicKey(inputMint)); + } else { + // When called from API, we cannot get manager from provider + // We need to pass the manager from client side + inputAta = this.base.getManagerAta(new PublicKey(inputMint), manager); + } + const instructions = [ ...computeBudgetInstructions.map(this.ixDataToTransactionInstruction), await this.base.program.methods @@ -181,7 +190,7 @@ export class JupiterClient { .accounts({ fund, manager, - inputAta: this.base.getManagerAta(new PublicKey(inputMint)), + inputAta, treasury: this.base.getTreasuryPDA(fund), outputAta: destinationTokenAccount, inputMint, @@ -194,6 +203,7 @@ export class JupiterClient { const addressLookupTableAccounts = await this.getAdressLookupTableAccounts( addressLookupTableAddresses ); + let payerPublicKey; try { payerPublicKey = await this.base.getWalletSigner().publicKey; diff --git a/anchor/tests/glam_api_tx.spec.ts b/anchor/tests/glam_api_tx.spec.ts index cb22edc4..e52110fd 100644 --- a/anchor/tests/glam_api_tx.spec.ts +++ b/anchor/tests/glam_api_tx.spec.ts @@ -2,15 +2,22 @@ import * as anchor from "@coral-xyz/anchor"; import { Transaction, SystemProgram, - sendAndConfirmTransaction + sendAndConfirmTransaction, + PublicKey, + VersionedTransaction } from "@solana/web3.js"; import { GlamClient } from "../src/client"; -const API = "https://api.glam.systems"; +// const API = "https://api.glam.systems"; +const API = "http://localhost:8080"; +const wsol = new PublicKey("So11111111111111111111111111111111111111112"); +const msol = new PublicKey("mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So"); +const usdc = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); describe("glam_api_tx", () => { const glamClient = new GlamClient(); + /* it("Wrap sol", async () => { const response = await fetch(`${API}/tx/wsol/wrap`, { method: "POST", @@ -66,4 +73,42 @@ describe("glam_api_tx", () => { throw error; } }); + */ + + it("jupiter swap", async () => { + const response = await fetch(`${API}/tx/jupiter/swap`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + fund: "4gAcSdfSAxVPcxj2Hi3AvKKViGat3iUysDD5ZzbqhDTk", + manager: "gLJHKPrZLGBiBZ33hFgZh6YnsEhTVxuRT17UCqNp6ff", + quote: { + inputMint: wsol.toBase58(), + outputMint: msol.toBase58(), + amount: 10000000, + autoSlippage: true, + autoSlippageCollisionUsdValue: 1000, + swapMode: "ExactIn", + onlyDirectRoutes: false, + asLegacyTransaction: false, + maxAccounts: 20 + } + }) + }); + const { tx, versioned } = await response.json(); + // console.log("is versioned:", versioned, "jupiter swap tx:", tx); + + const vTx = VersionedTransaction.deserialize(Buffer.from(tx, "hex")); + try { + const txId = await ( + glamClient.provider as anchor.AnchorProvider + ).sendAndConfirm(vTx, [glamClient.getWalletSigner()]); + console.log("jupiter swap txId", txId); + } catch (error) { + console.error("Error", error); + throw error; + } + }, 30_000); }); diff --git a/api/src/routers/tx.ts b/api/src/routers/tx.ts index 88ef5a7c..77ece039 100644 --- a/api/src/routers/tx.ts +++ b/api/src/routers/tx.ts @@ -10,7 +10,7 @@ const jupiterSwapTx = async (client, req, res) => { const fund = validatePubkey(req.body.fund); const manager = validatePubkey(req.body.manager); - if (fund === undefined || manager === undefined) { + if (!fund || !manager) { return res.sendStatus(400); } From 1938ef207a0d310399ff226079417c53badf6c81 Mon Sep 17 00:00:00 2001 From: Yuru Shao Date: Sun, 9 Jun 2024 22:36:59 -0700 Subject: [PATCH 3/5] Send tx with sendAndConfirmTransaction --- anchor/tests/glam_api_tx.spec.ts | 68 ++++++++++++-------------------- 1 file changed, 25 insertions(+), 43 deletions(-) diff --git a/anchor/tests/glam_api_tx.spec.ts b/anchor/tests/glam_api_tx.spec.ts index e52110fd..16ad43fd 100644 --- a/anchor/tests/glam_api_tx.spec.ts +++ b/anchor/tests/glam_api_tx.spec.ts @@ -1,7 +1,6 @@ import * as anchor from "@coral-xyz/anchor"; import { Transaction, - SystemProgram, sendAndConfirmTransaction, PublicKey, VersionedTransaction @@ -12,78 +11,63 @@ import { GlamClient } from "../src/client"; const API = "http://localhost:8080"; const wsol = new PublicKey("So11111111111111111111111111111111111111112"); const msol = new PublicKey("mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So"); -const usdc = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); +const manager = "gLJHKPrZLGBiBZ33hFgZh6YnsEhTVxuRT17UCqNp6ff"; +const fund = "4gAcSdfSAxVPcxj2Hi3AvKKViGat3iUysDD5ZzbqhDTk"; describe("glam_api_tx", () => { const glamClient = new GlamClient(); - /* - it("Wrap sol", async () => { + it("Wrap 0.001 sol", async () => { const response = await fetch(`${API}/tx/wsol/wrap`, { method: "POST", headers: { "content-type": "application/json" }, - body: JSON.stringify({ - manager: "gLJHKPrZLGBiBZ33hFgZh6YnsEhTVxuRT17UCqNp6ff", - fund: "4gAcSdfSAxVPcxj2Hi3AvKKViGat3iUysDD5ZzbqhDTk", - amount: 1000000 - }) + body: JSON.stringify({ manager, fund, amount: 1000000 }) }); const { tx } = await response.json(); console.log("Wrap tx:", tx); - const t = Transaction.from(Buffer.from(tx, "hex")); - t.sign(glamClient.getWalletSigner()); + try { - const txId = await glamClient.provider.connection.sendRawTransaction( - t.serialize() + const txId = await sendAndConfirmTransaction( + glamClient.provider.connection, + Transaction.from(Buffer.from(tx, "hex")), + [glamClient.getWalletSigner()] ); console.log("Wrap txId:", txId); } catch (error) { console.log("Error", error); throw error; } - }); + }, 30_000); - it("unwrap", async () => { + it("Unwrap wsol", async () => { const response = await fetch(`${API}/tx/wsol/unwrap`, { method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - manager: "gLJHKPrZLGBiBZ33hFgZh6YnsEhTVxuRT17UCqNp6ff", - fund: "4gAcSdfSAxVPcxj2Hi3AvKKViGat3iUysDD5ZzbqhDTk" - }) + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ manager, fund }) }); const { tx } = await response.json(); - console.log("unwrap tx", tx); + console.log("Unwrap tx", tx); - const t = Transaction.from(Buffer.from(tx, "hex")); - t.recentBlockhash = ( - await glamClient.provider.connection.getLatestBlockhash() - ).blockhash; - console.log("unwrap recentBlockhash", t.recentBlockhash); - t.sign(glamClient.getWalletSigner()); try { - const txId = await glamClient.provider.connection.sendRawTransaction( - t.serialize() + const txId = await sendAndConfirmTransaction( + glamClient.provider.connection, + Transaction.from(Buffer.from(tx, "hex")), + [glamClient.getWalletSigner()] ); - console.log("Wrap txId:", txId); + console.log("Unwrap txId:", txId); } catch (error) { console.log("Error", error); throw error; } - }); - */ + }, 30_000); - it("jupiter swap", async () => { + it("Jupiter swap", async () => { const response = await fetch(`${API}/tx/jupiter/swap`, { method: "POST", - headers: { - "Content-Type": "application/json" - }, + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - fund: "4gAcSdfSAxVPcxj2Hi3AvKKViGat3iUysDD5ZzbqhDTk", - manager: "gLJHKPrZLGBiBZ33hFgZh6YnsEhTVxuRT17UCqNp6ff", + fund, + manager, quote: { inputMint: wsol.toBase58(), outputMint: msol.toBase58(), @@ -97,9 +81,7 @@ describe("glam_api_tx", () => { } }) }); - const { tx, versioned } = await response.json(); - // console.log("is versioned:", versioned, "jupiter swap tx:", tx); - + const { tx } = await response.json(); const vTx = VersionedTransaction.deserialize(Buffer.from(tx, "hex")); try { const txId = await ( From a9fd31c9ffd30d88edeb3fb154d9c18af649ad1c Mon Sep 17 00:00:00 2001 From: Yuru Shao Date: Sun, 9 Jun 2024 23:28:16 -0700 Subject: [PATCH 4/5] Set confirm options --- anchor/src/client/base.ts | 8 ++++++-- anchor/src/client/jupiter.ts | 5 +++-- anchor/tests/glam_api_tx.spec.ts | 29 +++++++++++++++++++++++------ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/anchor/src/client/base.ts b/anchor/src/client/base.ts index b3aed3d7..dc289713 100644 --- a/anchor/src/client/base.ts +++ b/anchor/src/client/base.ts @@ -133,11 +133,15 @@ export class BaseClient { } getManager(): PublicKey { - return this.provider?.publicKey; + const managerPublicKey = this.provider?.publicKey; + if (!managerPublicKey) { + throw new Error("Manager public key cannot be retrieved from provider"); + } + return managerPublicKey; } getManagerAta(mint: PublicKey, manager?: PublicKey): PublicKey { - return getAssociatedTokenAddressSync(mint, this.getManager() || manager); + return getAssociatedTokenAddressSync(mint, manager || this.getManager()); } getWalletSigner(): Keypair { diff --git a/anchor/src/client/jupiter.ts b/anchor/src/client/jupiter.ts index 2768202d..2b2232f1 100644 --- a/anchor/src/client/jupiter.ts +++ b/anchor/src/client/jupiter.ts @@ -175,9 +175,10 @@ export class JupiterClient { this.ixDataToTransactionInstruction(swapInstruction); let inputAta; - if (this.base.getManager()) { + try { inputAta = this.base.getManagerAta(new PublicKey(inputMint)); - } else { + } catch (e) { + console.log("Cannot get manager ata:", e); // When called from API, we cannot get manager from provider // We need to pass the manager from client side inputAta = this.base.getManagerAta(new PublicKey(inputMint), manager); diff --git a/anchor/tests/glam_api_tx.spec.ts b/anchor/tests/glam_api_tx.spec.ts index 16ad43fd..fb14fc4b 100644 --- a/anchor/tests/glam_api_tx.spec.ts +++ b/anchor/tests/glam_api_tx.spec.ts @@ -3,16 +3,31 @@ import { Transaction, sendAndConfirmTransaction, PublicKey, - VersionedTransaction + VersionedTransaction, + ConfirmOptions } from "@solana/web3.js"; import { GlamClient } from "../src/client"; -// const API = "https://api.glam.systems"; -const API = "http://localhost:8080"; +/** + * This test suite is a demonstration of how to interact with the glam API. + * + * Before running this test suite, make sure you [provider] is correclty set up in Anchor.toml: + * 1) cluster: set to "mainnet-beta" + * 2) wallet: set to the path of the manager wallet + * + * To run the tests (optional to skip build, must skip deploy): + * anchor test --skip-build --skip-deploy + */ + +const API = "https://api.glam.systems"; const wsol = new PublicKey("So11111111111111111111111111111111111111112"); const msol = new PublicKey("mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So"); const manager = "gLJHKPrZLGBiBZ33hFgZh6YnsEhTVxuRT17UCqNp6ff"; const fund = "4gAcSdfSAxVPcxj2Hi3AvKKViGat3iUysDD5ZzbqhDTk"; +const confirmOptions: ConfirmOptions = { + commitment: "confirmed", + maxRetries: 3 +}; describe("glam_api_tx", () => { const glamClient = new GlamClient(); @@ -30,7 +45,8 @@ describe("glam_api_tx", () => { const txId = await sendAndConfirmTransaction( glamClient.provider.connection, Transaction.from(Buffer.from(tx, "hex")), - [glamClient.getWalletSigner()] + [glamClient.getWalletSigner()], + confirmOptions ); console.log("Wrap txId:", txId); } catch (error) { @@ -52,7 +68,8 @@ describe("glam_api_tx", () => { const txId = await sendAndConfirmTransaction( glamClient.provider.connection, Transaction.from(Buffer.from(tx, "hex")), - [glamClient.getWalletSigner()] + [glamClient.getWalletSigner()], + confirmOptions ); console.log("Unwrap txId:", txId); } catch (error) { @@ -86,7 +103,7 @@ describe("glam_api_tx", () => { try { const txId = await ( glamClient.provider as anchor.AnchorProvider - ).sendAndConfirm(vTx, [glamClient.getWalletSigner()]); + ).sendAndConfirm(vTx, [glamClient.getWalletSigner()], confirmOptions); console.log("jupiter swap txId", txId); } catch (error) { console.error("Error", error); From 08f151c4ba9f827cd6aa4714a49dc20958fe520e Mon Sep 17 00:00:00 2001 From: Yuru Shao Date: Sun, 9 Jun 2024 23:32:05 -0700 Subject: [PATCH 5/5] Update Anchor.toml --- anchor/Anchor.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/anchor/Anchor.toml b/anchor/Anchor.toml index 1bf70fcf..879208bc 100644 --- a/anchor/Anchor.toml +++ b/anchor/Anchor.toml @@ -15,20 +15,20 @@ glam = "Gco1pcjxCMYjKJjSNJ7mKV7qezeUTE7arXJgy7PAPNRc" url = "https://api.apr.dev" [provider] -# cluster = "localnet" +cluster = "localnet" #cluster = "devnet" -cluster = "mainnet" +#cluster = "mainnet" wallet = "~/.config/solana/id.json" [scripts] -test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_api_tx" #test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_crud" -# test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_investor" +test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_investor" #test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_drift" #test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_staking" #test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_jupiter" #test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_openfunds" #test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_wsol" +#test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern glam_api_tx" #test = "../node_modules/.bin/nx run --skip-nx-cache anchor:jest --verbose --testPathPattern tests/ --testNamePattern devnet" [test]