Skip to content

Creating Positions via the TypeScript SDK doesn't work #234

@raphtlw

Description

@raphtlw

I'm trying to use initializePositionAndAddLiquidityByStrategy to open a position in the liquidity pool. The transaction returns a signature string but when I go on my wallet the balances still remain the same.

Am I doing something wrong here? Any help would be appreciated!

"use client";

import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { NumberInput } from "@/components/ui/number-input";
import { SGDC_DECIMALS, USDC_DECIMALS } from "@/lib/constants";
import { getConnection } from "@/lib/solana/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import DLMM, { StrategyType } from "@meteora-ag/dlmm";
import { useWallet } from "@solana/wallet-adapter-react";
import {
  ComputeBudgetProgram,
  Keypair,
  PublicKey,
  Transaction,
} from "@solana/web3.js";
import { BN } from "bn.js";
import dynamic from "next/dynamic";
import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import * as z from "zod";
import { getPriceQuote } from "./_actions";

const WalletMultiButton = dynamic(
  () =>
    import("@/components/solana-wallet").then((mod) => mod.WalletMultiButton),
  { ssr: false },
);

const depositSchema = z.object({
  sgdc: z.number("SGDC must be a number").min(0.01, "Minimum 0.01"),
});

type DepositFormValues = z.infer<typeof depositSchema>;

export type JitoRegion = "mainnet" | "amsterdam" | "frankfurt" | "ny" | "tokyo";
export const JitoEndpoints = {
  mainnet: "https://mainnet.block-engine.jito.wtf/api/v1/transactions",
  amsterdam:
    "https://amsterdam.mainnet.block-engine.jito.wtf/api/v1/transactions",
  frankfurt:
    "https://frankfurt.mainnet.block-engine.jito.wtf/api/v1/transactions",
  ny: "https://ny.mainnet.block-engine.jito.wtf/api/v1/transactions",
  tokyo: "https://tokyo.mainnet.block-engine.jito.wtf/api/v1/transactions",
};
export function getJitoEndpoint(region: JitoRegion) {
  return JitoEndpoints[region];
}
/**
 * Send a transaction using Jito. This only supports sending a single transaction on mainnet only.
 * See https://jito-labs.gitbook.io/mev/searcher-resources/json-rpc-api-reference/transactions-endpoint/sendtransaction.
 * @param args.serialisedTx - A single transaction to be sent, in serialised form
 * @param args.region - The region of the Jito endpoint to use
 */
export async function sendTxUsingJito(
  serializedTx: Uint8Array | Buffer | number[],
) {
  const rpcEndpoint = getJitoEndpoint("mainnet");
  const payload = {
    jsonrpc: "2.0",
    id: 1,
    method: "sendTransaction",
    params: [serializedTx.toString("base64"), { encoding: "base64" }],
  };
  const res = await fetch(`${rpcEndpoint}?bundleOnly=true`, {
    method: "POST",
    body: JSON.stringify(payload),
    headers: { "Content-Type": "application/json" },
  });
  const json = await res.json();
  console.log("Jito response:", json);
  if (json.error) {
    throw new Error(json.error.message);
  }
  return json;
}

export default function LiquidityPage() {
  const wallet = useWallet();
  const [marketPrice, setMarketPrice] = useState<number | null>(null);
  const [dlmmPool, setDlmmPool] = useState<DLMM | null>(null);

  const form = useForm<DepositFormValues>({
    resolver: zodResolver(depositSchema),
    defaultValues: { sgdc: 0 },
  });

  useEffect(() => {
    (async () => {
      if (!wallet.connected) return;

      const pythQuote = await getPriceQuote().catch((e) => {
        console.error(e);
        toast.error("Failed to fetch USD/SGD price");
      });

      if (!pythQuote) return;

      const [pythData, price] = pythQuote;

      setMarketPrice(1 / price);

      DLMM.create(
        getConnection(),
        new PublicKey(process.env.NEXT_PUBLIC_USDC_POOL_ADDRESS!),
      )
        .then(setDlmmPool)
        .catch((e) => {
          console.error(e);
          toast.error("Failed to init pool");
        });
    })();
  }, [wallet.connected]);

  // live-calc USDC needed
  const sgdcVal = form.watch("sgdc") || 0;
  const usdcVal = useMemo(() => {
    if (!marketPrice) return 0;
    return sgdcVal * marketPrice;
  }, [marketPrice, sgdcVal]);

  const onSubmit = form.handleSubmit(async ({ sgdc }) => {
    if (!wallet.publicKey || !wallet.signTransaction) {
      toast.error("Connect a wallet that can sign transactions");
      return;
    }

    if (!dlmmPool || marketPrice === null) {
      toast.error("Pool or price not ready");
      return;
    }

    try {
      const activeBin = await dlmmPool.getActiveBin();

      const TOTAL_RANGE_INTERVAL = 10; // 10 bins on each side of the active bin
      const minBinId = activeBin.binId - TOTAL_RANGE_INTERVAL;
      const maxBinId = activeBin.binId + TOTAL_RANGE_INTERVAL;

      const totalXAmount = new BN(sgdc * 10 ** SGDC_DECIMALS);
      const totalYAmount = new BN(sgdc * marketPrice * 10 ** USDC_DECIMALS);
      const newImbalancePosition = new Keypair();

      const createPositionTx =
        await dlmmPool.initializePositionAndAddLiquidityByStrategy({
          positionPubKey: newImbalancePosition.publicKey,
          totalXAmount,
          totalYAmount,
          strategy: {
            maxBinId,
            minBinId,
            strategyType: StrategyType.Spot,
          },
          user: wallet.publicKey,
          slippage: 50,
        });

      const transaction = new Transaction()
        .add(ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }))
        .add(createPositionTx);
      transaction.recentBlockhash = (
        await getConnection().getLatestBlockhash()
      ).blockhash;
      transaction.feePayer = wallet.publicKey;
      transaction.sign(newImbalancePosition);
      const signedTx = await wallet.signTransaction(transaction);
      const txResult = await sendTxUsingJito(signedTx.serialize());
      console.log("Transaction result:", txResult);

      toast.success("Liquidity deposited successfully!", {
        // action: {
        //   label: "View Tx",
        //   onClick: () => window.open(`https://solscan.io/tx/${txResult}`),
        // },
      });
      form.reset();
    } catch (err) {
      console.error(err);
      if (err instanceof Error) {
        toast.error(err.message || "Deposit failed");
      }
    }
  });

  return (
    <main className="relative flex flex-col items-center">
      <div className="flex flex-col md:max-w-5xl w-full mt-10 gap-8 px-8 md:px-0">
        <Card>
          <CardHeader>
            <CardTitle>Deposit Liquidity</CardTitle>
            <CardDescription>
              Add SGDC ↔ USDC at live SGD/USD rate
            </CardDescription>
          </CardHeader>

          <CardContent className="space-y-6">
            {!wallet.connected ? (
              <WalletMultiButton className="w-full" />
            ) : (
              <>
                <div>
                  <p className="text-sm">Market SGD/USD:</p>
                  <p className="text-lg font-semibold">{marketPrice ?? "…"}</p>
                </div>

                <Form {...form}>
                  <form onSubmit={onSubmit} className="flex flex-col gap-4">
                    <FormField
                      control={form.control}
                      name="sgdc"
                      render={({ field }) => (
                        <FormItem>
                          <FormLabel>SGDC amount</FormLabel>
                          <FormControl>
                            <NumberInput
                              stepper={1}
                              thousandSeparator=","
                              decimalScale={2}
                              fixedDecimalScale
                              allowNegative={false}
                              prefix="SGDC "
                              min={0}
                              max={200_000}
                              {...field}
                              onChange={undefined}
                              onValueChange={(val) => {
                                field.onChange(val);
                              }}
                            />
                          </FormControl>
                          <FormMessage />
                        </FormItem>
                      )}
                    />

                    <div>
                      <p className="text-sm">USDC required</p>
                      <p className="text-lg font-semibold">{usdcVal} USDC</p>
                    </div>

                    <Button
                      type="submit"
                      className="w-full"
                      disabled={!marketPrice}
                    >
                      Deposit Liquidity
                    </Button>
                  </form>
                </Form>
              </>
            )}
          </CardContent>
        </Card>
      </div>
    </main>
  );
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions