Forráskód Böngészése

feat: byreal CLMM copy trade system

Complete implementation of a copy trading system for byreal.io DEX on Solana:

- Monitor: WebSocket + polling for target wallet TX detection with time-based dedup
- Parser: Anchor IDL discriminator + log-based TX parsing with event decoding,
  supports compound decrease+close TXs (prioritizes close_position detection)
- Copier: Price-based multiplier scaling, Jupiter Ultra ExactOut swap for token
  acquisition, generous slippage (99%) for decrease/close operations
- Pool info: Byreal API integration with caching, displayReversed support
- Web UI: Dashboard with active positions + close button, wallet balances,
  addresses management, position mappings, copy history, settings
- Database: SQLite via better-sqlite3 for addresses, positions, history, settings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
lushdog@outlook.com 2 hete
szülő
commit
34bae68e7b
100 módosított fájl, 29775 hozzáadás és 131 törlés
  1. 20 0
      .env.example
  2. 6 0
      .gitignore
  3. 1 0
      .pnpm-approve-builds.json
  4. 5 0
      .prettierignore
  5. 7 0
      .prettierrc
  6. 0 26
      app/globals.css
  7. 0 34
      app/layout.tsx
  8. 0 65
      app/page.tsx
  9. 0 0
      data/.gitkeep
  10. 5 4
      next.config.ts
  11. 19 2
      package.json
  12. 697 0
      pnpm-lock.yaml
  13. 8 0
      pnpm-workspace.yaml
  14. 239 0
      src/app/addresses/page.tsx
  15. 95 0
      src/app/api/addresses/route.ts
  16. 9 0
      src/app/api/history/route.ts
  17. 15 0
      src/app/api/monitor/start/route.ts
  18. 7 0
      src/app/api/monitor/status/route.ts
  19. 15 0
      src/app/api/monitor/stop/route.ts
  20. 120 0
      src/app/api/positions/route.ts
  21. 19 0
      src/app/api/settings/route.ts
  22. 65 0
      src/app/api/wallet/balance/route.ts
  23. 0 0
      src/app/favicon.ico
  24. 29 0
      src/app/globals.css
  25. 113 0
      src/app/history/page.tsx
  26. 25 0
      src/app/layout.tsx
  27. 358 0
      src/app/page.tsx
  28. 209 0
      src/app/positions/page.tsx
  29. 177 0
      src/app/settings/page.tsx
  30. 25 0
      src/components/layout/header.tsx
  31. 43 0
      src/components/layout/sidebar.tsx
  32. 52 0
      src/lib/clmm-sdk/.gitignore
  33. 10 0
      src/lib/clmm-sdk/.prettierignore
  34. 4 0
      src/lib/clmm-sdk/.prettierrc
  35. 58 0
      src/lib/clmm-sdk/README.md
  36. 36 0
      src/lib/clmm-sdk/package.json
  37. 80 0
      src/lib/clmm-sdk/src/calculate.ts
  38. 6 0
      src/lib/clmm-sdk/src/client/apis/commonModels.ts
  39. 89 0
      src/lib/clmm-sdk/src/client/apis/index.ts
  40. 107 0
      src/lib/clmm-sdk/src/client/apis/ky.ts
  41. 87 0
      src/lib/clmm-sdk/src/client/apis/poolsModels.ts
  42. 22 0
      src/lib/clmm-sdk/src/client/apis/positionModels.ts
  43. 43 0
      src/lib/clmm-sdk/src/client/apis/swapModels.ts
  44. 10 0
      src/lib/clmm-sdk/src/client/apis/tickModels.ts
  45. 1382 0
      src/lib/clmm-sdk/src/client/chain/index.ts
  46. 178 0
      src/lib/clmm-sdk/src/client/chain/models.ts
  47. 408 0
      src/lib/clmm-sdk/src/client/chain/utils.ts
  48. 84 0
      src/lib/clmm-sdk/src/client/index.ts
  49. 42 0
      src/lib/clmm-sdk/src/client/liquidity/abstract.ts
  50. 76 0
      src/lib/clmm-sdk/src/client/liquidity/clmm.ts
  51. 14 0
      src/lib/clmm-sdk/src/client/liquidity/types.ts
  52. 105 0
      src/lib/clmm-sdk/src/client/token.ts
  53. 30 0
      src/lib/clmm-sdk/src/constants.ts
  54. 5 0
      src/lib/clmm-sdk/src/index.ts
  55. 1203 0
      src/lib/clmm-sdk/src/instructions/baseInstruction.ts
  56. 41 0
      src/lib/clmm-sdk/src/instructions/constants.ts
  57. 155 0
      src/lib/clmm-sdk/src/instructions/getRawData.ts
  58. 8 0
      src/lib/clmm-sdk/src/instructions/index.ts
  59. 547 0
      src/lib/clmm-sdk/src/instructions/instruction.ts
  60. 255 0
      src/lib/clmm-sdk/src/instructions/layout.ts
  61. 211 0
      src/lib/clmm-sdk/src/instructions/libs/marshmallow/bufferLayout.ts
  62. 374 0
      src/lib/clmm-sdk/src/instructions/libs/marshmallow/index.ts
  63. 63 0
      src/lib/clmm-sdk/src/instructions/models.ts
  64. 131 0
      src/lib/clmm-sdk/src/instructions/pda.ts
  65. 7242 0
      src/lib/clmm-sdk/src/instructions/target/idl/byreal_amm_v3.json
  66. 5328 0
      src/lib/clmm-sdk/src/instructions/target/types/byreal_amm_v3.ts
  67. 4148 0
      src/lib/clmm-sdk/src/instructions/target/types/raydium_amm_v3.ts
  68. 79 0
      src/lib/clmm-sdk/src/instructions/utils/binaryUtils.ts
  69. 111 0
      src/lib/clmm-sdk/src/instructions/utils/fetchWalletTokenAccounts.ts
  70. 29 0
      src/lib/clmm-sdk/src/instructions/utils/getTickArrayBitmapExtension.ts
  71. 106 0
      src/lib/clmm-sdk/src/instructions/utils/getTickArrayInfo.ts
  72. 28 0
      src/lib/clmm-sdk/src/instructions/utils/index.ts
  73. 617 0
      src/lib/clmm-sdk/src/instructions/utils/liquidityMath.test.ts
  74. 212 0
      src/lib/clmm-sdk/src/instructions/utils/liquidityMath.ts
  75. 42 0
      src/lib/clmm-sdk/src/instructions/utils/mathUtils.ts
  76. 97 0
      src/lib/clmm-sdk/src/instructions/utils/models.ts
  77. 164 0
      src/lib/clmm-sdk/src/instructions/utils/poolStateUtils.ts
  78. 412 0
      src/lib/clmm-sdk/src/instructions/utils/poolUtils.ts
  79. 160 0
      src/lib/clmm-sdk/src/instructions/utils/position.ts
  80. 220 0
      src/lib/clmm-sdk/src/instructions/utils/sqrtPriceMath.test.ts
  81. 194 0
      src/lib/clmm-sdk/src/instructions/utils/sqrtPriceMath.ts
  82. 393 0
      src/lib/clmm-sdk/src/instructions/utils/swapMath.ts
  83. 567 0
      src/lib/clmm-sdk/src/instructions/utils/tick.ts
  84. 189 0
      src/lib/clmm-sdk/src/instructions/utils/tickArrayUtils.ts
  85. 227 0
      src/lib/clmm-sdk/src/instructions/utils/tickMath.test.ts
  86. 60 0
      src/lib/clmm-sdk/src/instructions/utils/tickMath.ts
  87. 227 0
      src/lib/clmm-sdk/src/instructions/utils/tickarrayBitmap.ts
  88. 97 0
      src/lib/clmm-sdk/src/instructions/utils/transfer.ts
  89. 14 0
      src/lib/clmm-sdk/src/playgrounds/01_get_raw_position_info_list.ts
  90. 17 0
      src/lib/clmm-sdk/src/playgrounds/02_get_raw_position_info.ts
  91. 13 0
      src/lib/clmm-sdk/src/playgrounds/03_get_raw_pool_info.ts
  92. 42 0
      src/lib/clmm-sdk/src/playgrounds/04_get_position_info_list.ts
  93. 23 0
      src/lib/clmm-sdk/src/playgrounds/05_align_price.ts
  94. 32 0
      src/lib/clmm-sdk/src/playgrounds/06_get_amount_from_another_amount.ts
  95. 83 0
      src/lib/clmm-sdk/src/playgrounds/07_create_position_baseA.ts
  96. 82 0
      src/lib/clmm-sdk/src/playgrounds/07_create_position_baseB.ts
  97. 40 0
      src/lib/clmm-sdk/src/playgrounds/08_decrease_liquidity.ts
  98. 82 0
      src/lib/clmm-sdk/src/playgrounds/09_create_position_side_tokenA.ts
  99. 82 0
      src/lib/clmm-sdk/src/playgrounds/10_create_position_side_tokenB.ts
  100. 69 0
      src/lib/clmm-sdk/src/playgrounds/11_decrease_liquidity_simulate_amount.ts

+ 20 - 0
.env.example

@@ -0,0 +1,20 @@
+# Solana RPC URL
+SOL_ENDPOINT=https://api.mainnet-beta.solana.com
+
+# Local wallet private key (base58 encoded)
+SOL_SECRET_KEY=
+
+# Jupiter API key (from https://portal.jup.ag/)
+JUPITER_API_KEY=
+
+# Copy multiplier (1.0 = same as target, 2.0 = 2x, 0.5 = half)
+COPY_MULTIPLIER=1.0
+
+# Max USD per copy operation (cap total position value)
+COPY_MAX_USD=1000
+
+# Default slippage for copy operations (0.02 = 2%)
+COPY_SLIPPAGE=0.02
+
+# Monitoring poll interval in milliseconds
+MONITOR_POLL_INTERVAL=5000

+ 6 - 0
.gitignore

@@ -32,6 +32,12 @@ yarn-error.log*
 
 # env files (can opt-in for committing if needed)
 .env*
+!.env.example
+
+# database
+/data/*.db
+/data/*.db-wal
+/data/*.db-shm
 
 # vercel
 .vercel

+ 1 - 0
.pnpm-approve-builds.json

@@ -0,0 +1 @@
+{"better-sqlite3": true, "bigint-buffer": true, "bufferutil": true, "utf-8-validate": true}

+ 5 - 0
.prettierignore

@@ -0,0 +1,5 @@
+node_modules
+.next
+dist
+src/lib/clmm-sdk
+pnpm-lock.yaml

+ 7 - 0
.prettierrc

@@ -0,0 +1,7 @@
+{
+  "singleQuote": true,
+  "semi": false,
+  "printWidth": 100,
+  "trailingComma": "all",
+  "tabWidth": 2
+}

+ 0 - 26
app/globals.css

@@ -1,26 +0,0 @@
-@import "tailwindcss";
-
-:root {
-  --background: #ffffff;
-  --foreground: #171717;
-}
-
-@theme inline {
-  --color-background: var(--background);
-  --color-foreground: var(--foreground);
-  --font-sans: var(--font-geist-sans);
-  --font-mono: var(--font-geist-mono);
-}
-
-@media (prefers-color-scheme: dark) {
-  :root {
-    --background: #0a0a0a;
-    --foreground: #ededed;
-  }
-}
-
-body {
-  background: var(--background);
-  color: var(--foreground);
-  font-family: Arial, Helvetica, sans-serif;
-}

+ 0 - 34
app/layout.tsx

@@ -1,34 +0,0 @@
-import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
-import "./globals.css";
-
-const geistSans = Geist({
-  variable: "--font-geist-sans",
-  subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
-  variable: "--font-geist-mono",
-  subsets: ["latin"],
-});
-
-export const metadata: Metadata = {
-  title: "Create Next App",
-  description: "Generated by create next app",
-};
-
-export default function RootLayout({
-  children,
-}: Readonly<{
-  children: React.ReactNode;
-}>) {
-  return (
-    <html lang="en">
-      <body
-        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
-      >
-        {children}
-      </body>
-    </html>
-  );
-}

+ 0 - 65
app/page.tsx

@@ -1,65 +0,0 @@
-import Image from "next/image";
-
-export default function Home() {
-  return (
-    <div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
-      <main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
-        <Image
-          className="dark:invert"
-          src="/next.svg"
-          alt="Next.js logo"
-          width={100}
-          height={20}
-          priority
-        />
-        <div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
-          <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
-            To get started, edit the page.tsx file.
-          </h1>
-          <p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
-            Looking for a starting point or more instructions? Head over to{" "}
-            <a
-              href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
-              className="font-medium text-zinc-950 dark:text-zinc-50"
-            >
-              Templates
-            </a>{" "}
-            or the{" "}
-            <a
-              href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
-              className="font-medium text-zinc-950 dark:text-zinc-50"
-            >
-              Learning
-            </a>{" "}
-            center.
-          </p>
-        </div>
-        <div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
-          <a
-            className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
-            href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
-            target="_blank"
-            rel="noopener noreferrer"
-          >
-            <Image
-              className="dark:invert"
-              src="/vercel.svg"
-              alt="Vercel logomark"
-              width={16}
-              height={16}
-            />
-            Deploy Now
-          </a>
-          <a
-            className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
-            href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
-            target="_blank"
-            rel="noopener noreferrer"
-          >
-            Documentation
-          </a>
-        </div>
-      </main>
-    </div>
-  );
-}

+ 0 - 0
data/.gitkeep


+ 5 - 4
next.config.ts

@@ -1,7 +1,8 @@
-import type { NextConfig } from "next";
+import type { NextConfig } from 'next'
 
 const nextConfig: NextConfig = {
-  /* config options here */
-};
+  serverExternalPackages: ['better-sqlite3'],
+  turbopack: {},
+}
 
-export default nextConfig;
+export default nextConfig

+ 19 - 2
package.json

@@ -6,20 +6,37 @@
     "dev": "next dev",
     "build": "next build",
     "start": "next start",
-    "lint": "eslint"
+    "lint": "eslint",
+    "format": "prettier --write \"src/**/*.{ts,tsx,css}\""
   },
   "dependencies": {
+    "@coral-xyz/anchor": "^0.31.1",
+    "@noble/hashes": "^1.8.0",
+    "@solana/buffer-layout": "^4.0.1",
+    "@solana/spl-token": "^0.4.14",
+    "@solana/web3.js": "^1.98.4",
+    "better-sqlite3": "^11.10.0",
+    "bn.js": "^5.2.3",
+    "bs58": "^6.0.0",
+    "decimal.js": "^10.6.0",
+    "ky": "^1.14.3",
+    "lodash-es": "^4.17.23",
     "next": "16.1.6",
     "react": "19.2.3",
-    "react-dom": "19.2.3"
+    "react-dom": "19.2.3",
+    "swr": "^2.4.1"
   },
   "devDependencies": {
     "@tailwindcss/postcss": "^4",
+    "@types/better-sqlite3": "^7.6.13",
+    "@types/bn.js": "^5.2.0",
+    "@types/lodash-es": "^4.17.12",
     "@types/node": "^20",
     "@types/react": "^19",
     "@types/react-dom": "^19",
     "eslint": "^9",
     "eslint-config-next": "16.1.6",
+    "prettier": "^3.8.1",
     "tailwindcss": "^4",
     "typescript": "^5"
   }

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 697 - 0
pnpm-lock.yaml


+ 8 - 0
pnpm-workspace.yaml

@@ -1,3 +1,11 @@
+approveBuilds: true
+
+onlyBuiltDependencies:
+  - better-sqlite3
+  - bigint-buffer
+  - bufferutil
+  - utf-8-validate
+
 ignoredBuiltDependencies:
   - sharp
   - unrs-resolver

+ 239 - 0
src/app/addresses/page.tsx

@@ -0,0 +1,239 @@
+'use client'
+
+import { useState } from 'react'
+import useSWR from 'swr'
+
+const fetcher = (url: string) => fetch(url).then((r) => r.json())
+
+interface AddressRow {
+  id: number
+  address: string
+  label: string | null
+  enabled: number
+  copy_multiplier: number | null
+  copy_max_usd: number | null
+  created_at: string
+}
+
+function AddressItem({
+  addr,
+  onToggle,
+  onDelete,
+  onUpdate,
+}: {
+  addr: AddressRow
+  onToggle: (id: number, enabled: boolean) => void
+  onDelete: (id: number) => void
+  onUpdate: () => void
+}) {
+  const [editing, setEditing] = useState(false)
+  const [multiplier, setMultiplier] = useState(addr.copy_multiplier?.toString() ?? '')
+  const [maxUsd, setMaxUsd] = useState(addr.copy_max_usd?.toString() ?? '')
+  const [saving, setSaving] = useState(false)
+
+  const handleSave = async () => {
+    setSaving(true)
+    try {
+      await fetch('/api/addresses', {
+        method: 'PUT',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({
+          id: addr.id,
+          copyMultiplier: multiplier || '',
+          copyMaxUsd: maxUsd || '',
+        }),
+      })
+      setEditing(false)
+      onUpdate()
+    } finally {
+      setSaving(false)
+    }
+  }
+
+  return (
+    <div className="py-3 border-b border-zinc-800/50 last:border-0">
+      <div className="flex items-center justify-between">
+        <div className="flex-1 min-w-0">
+          <div className="flex items-center gap-2">
+            <code className="text-xs text-zinc-300 truncate">{addr.address}</code>
+            {addr.label && <span className="text-xs text-zinc-500">({addr.label})</span>}
+          </div>
+          <div className="flex items-center gap-3 mt-1 text-[10px] text-zinc-500">
+            <span>Added {new Date(addr.created_at + 'Z').toLocaleDateString()}</span>
+            <span>
+              Multiplier:{' '}
+              <span className="text-zinc-400">
+                {addr.copy_multiplier != null ? `${addr.copy_multiplier}x` : 'default'}
+              </span>
+            </span>
+            <span>
+              Max USD:{' '}
+              <span className="text-zinc-400">
+                {addr.copy_max_usd != null ? `$${addr.copy_max_usd}` : 'default'}
+              </span>
+            </span>
+          </div>
+        </div>
+        <div className="flex items-center gap-2 ml-4">
+          <button
+            onClick={() => setEditing(!editing)}
+            className="text-xs px-2 py-1 rounded bg-zinc-500/20 text-zinc-400 hover:bg-zinc-500/30"
+          >
+            {editing ? 'Cancel' : 'Settings'}
+          </button>
+          <button
+            onClick={() => onToggle(addr.id, !addr.enabled)}
+            className={`text-xs px-2 py-1 rounded ${
+              addr.enabled ? 'bg-green-500/20 text-green-400' : 'bg-zinc-500/20 text-zinc-400'
+            }`}
+          >
+            {addr.enabled ? 'Enabled' : 'Disabled'}
+          </button>
+          <button
+            onClick={() => onDelete(addr.id)}
+            className="text-xs px-2 py-1 rounded bg-red-500/20 text-red-400 hover:bg-red-500/30"
+          >
+            Delete
+          </button>
+        </div>
+      </div>
+      {editing && (
+        <div className="mt-2 flex items-end gap-2 pl-1">
+          <div>
+            <label className="block text-[10px] text-zinc-500 mb-0.5">Multiplier</label>
+            <input
+              type="number"
+              step="0.1"
+              min="0.01"
+              placeholder="default"
+              value={multiplier}
+              onChange={(e) => setMultiplier(e.target.value)}
+              className="w-24 px-2 py-1 text-xs rounded border border-[var(--border)] bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:border-indigo-500"
+            />
+          </div>
+          <div>
+            <label className="block text-[10px] text-zinc-500 mb-0.5">Max USD</label>
+            <input
+              type="number"
+              step="100"
+              min="1"
+              placeholder="default"
+              value={maxUsd}
+              onChange={(e) => setMaxUsd(e.target.value)}
+              className="w-28 px-2 py-1 text-xs rounded border border-[var(--border)] bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:border-indigo-500"
+            />
+          </div>
+          <button
+            onClick={handleSave}
+            disabled={saving}
+            className="px-3 py-1 text-xs rounded bg-indigo-500 text-white hover:bg-indigo-400 disabled:opacity-50 transition-colors"
+          >
+            {saving ? 'Saving...' : 'Save'}
+          </button>
+          <span className="text-[10px] text-zinc-600">Leave empty to use global defaults</span>
+        </div>
+      )}
+    </div>
+  )
+}
+
+export default function AddressesPage() {
+  const { data: addresses, mutate } = useSWR('/api/addresses', fetcher, {
+    refreshInterval: 5000,
+  })
+  const [newAddress, setNewAddress] = useState('')
+  const [newLabel, setNewLabel] = useState('')
+  const [adding, setAdding] = useState(false)
+
+  const handleAdd = async (e: React.FormEvent) => {
+    e.preventDefault()
+    if (!newAddress.trim()) return
+
+    setAdding(true)
+    try {
+      await fetch('/api/addresses', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({ address: newAddress.trim(), label: newLabel.trim() || undefined }),
+      })
+      setNewAddress('')
+      setNewLabel('')
+      mutate()
+    } finally {
+      setAdding(false)
+    }
+  }
+
+  const handleDelete = async (id: number) => {
+    await fetch('/api/addresses', {
+      method: 'DELETE',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ id }),
+    })
+    mutate()
+  }
+
+  const handleToggle = async (id: number, enabled: boolean) => {
+    await fetch('/api/addresses', {
+      method: 'PATCH',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ id, enabled }),
+    })
+    mutate()
+  }
+
+  return (
+    <div className="space-y-6">
+      <h2 className="text-lg font-semibold">Watched Addresses</h2>
+
+      <form
+        onSubmit={handleAdd}
+        className="rounded-lg border border-[var(--border)] bg-[var(--card)] p-4 space-y-3"
+      >
+        <h3 className="text-sm font-medium">Add Address</h3>
+        <div className="flex gap-2">
+          <input
+            type="text"
+            placeholder="Solana wallet address"
+            value={newAddress}
+            onChange={(e) => setNewAddress(e.target.value)}
+            className="flex-1 px-3 py-1.5 text-sm rounded border border-[var(--border)] bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:border-indigo-500"
+          />
+          <input
+            type="text"
+            placeholder="Label (optional)"
+            value={newLabel}
+            onChange={(e) => setNewLabel(e.target.value)}
+            className="w-40 px-3 py-1.5 text-sm rounded border border-[var(--border)] bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:border-indigo-500"
+          />
+          <button
+            type="submit"
+            disabled={adding || !newAddress.trim()}
+            className="px-4 py-1.5 text-sm rounded bg-indigo-500 text-white hover:bg-indigo-400 disabled:opacity-50 transition-colors"
+          >
+            Add
+          </button>
+        </div>
+      </form>
+
+      <div className="rounded-lg border border-[var(--border)] bg-[var(--card)] p-4">
+        <h3 className="text-sm font-medium mb-3">Addresses ({addresses?.length || 0})</h3>
+        {!addresses?.length ? (
+          <p className="text-xs text-zinc-500">No addresses added yet</p>
+        ) : (
+          <div>
+            {(addresses as AddressRow[]).map((addr) => (
+              <AddressItem
+                key={addr.id}
+                addr={addr}
+                onToggle={handleToggle}
+                onDelete={handleDelete}
+                onUpdate={() => mutate()}
+              />
+            ))}
+          </div>
+        )}
+      </div>
+    </div>
+  )
+}

+ 95 - 0
src/app/api/addresses/route.ts

@@ -0,0 +1,95 @@
+import { NextRequest, NextResponse } from 'next/server'
+import {
+  getWatchedAddresses,
+  addWatchedAddress,
+  removeWatchedAddress,
+  toggleWatchedAddress,
+  updateWatchedAddressSettings,
+} from '@/lib/db/queries'
+import { MonitorService } from '@/lib/monitor/index'
+
+export async function GET() {
+  const addresses = getWatchedAddresses()
+  return NextResponse.json(addresses)
+}
+
+export async function POST(req: NextRequest) {
+  const body = await req.json()
+  const { address, label } = body
+
+  if (!address || typeof address !== 'string') {
+    return NextResponse.json({ error: 'address is required' }, { status: 400 })
+  }
+
+  // Validate Solana address format (32-44 chars, base58)
+  if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address)) {
+    return NextResponse.json({ error: 'Invalid Solana address' }, { status: 400 })
+  }
+
+  addWatchedAddress(address, label)
+
+  // Reload monitor addresses if running
+  const monitor = MonitorService.getInstance()
+  if (monitor.getStatus().running) {
+    monitor.reloadAddresses()
+  }
+
+  return NextResponse.json({ success: true })
+}
+
+export async function DELETE(req: NextRequest) {
+  const body = await req.json()
+  const { id } = body
+
+  if (!id) {
+    return NextResponse.json({ error: 'id is required' }, { status: 400 })
+  }
+
+  removeWatchedAddress(id)
+
+  const monitor = MonitorService.getInstance()
+  if (monitor.getStatus().running) {
+    monitor.reloadAddresses()
+  }
+
+  return NextResponse.json({ success: true })
+}
+
+export async function PATCH(req: NextRequest) {
+  const body = await req.json()
+  const { id, enabled } = body
+
+  if (!id || enabled === undefined) {
+    return NextResponse.json({ error: 'id and enabled are required' }, { status: 400 })
+  }
+
+  toggleWatchedAddress(id, enabled)
+
+  const monitor = MonitorService.getInstance()
+  if (monitor.getStatus().running) {
+    monitor.reloadAddresses()
+  }
+
+  return NextResponse.json({ success: true })
+}
+
+/**
+ * PUT /api/addresses - 更新地址的独立倍率和最大值设置
+ * body: { id, copyMultiplier?, copyMaxUsd? }
+ */
+export async function PUT(req: NextRequest) {
+  const body = await req.json()
+  const { id, copyMultiplier, copyMaxUsd } = body
+
+  if (!id) {
+    return NextResponse.json({ error: 'id is required' }, { status: 400 })
+  }
+
+  updateWatchedAddressSettings(id, {
+    copyMultiplier:
+      copyMultiplier !== undefined && copyMultiplier !== '' ? Number(copyMultiplier) : null,
+    copyMaxUsd: copyMaxUsd !== undefined && copyMaxUsd !== '' ? Number(copyMaxUsd) : null,
+  })
+
+  return NextResponse.json({ success: true })
+}

+ 9 - 0
src/app/api/history/route.ts

@@ -0,0 +1,9 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { getCopyHistory } from '@/lib/db/queries'
+
+export async function GET(req: NextRequest) {
+  const limit = parseInt(req.nextUrl.searchParams.get('limit') || '50', 10)
+  const offset = parseInt(req.nextUrl.searchParams.get('offset') || '0', 10)
+  const history = getCopyHistory(limit, offset)
+  return NextResponse.json(history)
+}

+ 15 - 0
src/app/api/monitor/start/route.ts

@@ -0,0 +1,15 @@
+import { NextResponse } from 'next/server'
+import { MonitorService } from '@/lib/monitor/index'
+
+export async function POST() {
+  try {
+    const monitor = MonitorService.getInstance()
+    await monitor.start()
+    return NextResponse.json({ success: true, status: monitor.getStatus() })
+  } catch (e) {
+    return NextResponse.json(
+      { error: e instanceof Error ? e.message : 'Unknown error' },
+      { status: 500 },
+    )
+  }
+}

+ 7 - 0
src/app/api/monitor/status/route.ts

@@ -0,0 +1,7 @@
+import { NextResponse } from 'next/server'
+import { MonitorService } from '@/lib/monitor/index'
+
+export async function GET() {
+  const monitor = MonitorService.getInstance()
+  return NextResponse.json(monitor.getStatus())
+}

+ 15 - 0
src/app/api/monitor/stop/route.ts

@@ -0,0 +1,15 @@
+import { NextResponse } from 'next/server'
+import { MonitorService } from '@/lib/monitor/index'
+
+export async function POST() {
+  try {
+    const monitor = MonitorService.getInstance()
+    await monitor.stop()
+    return NextResponse.json({ success: true, status: monitor.getStatus() })
+  } catch (e) {
+    return NextResponse.json(
+      { error: e instanceof Error ? e.message : 'Unknown error' },
+      { status: 500 },
+    )
+  }
+}

+ 120 - 0
src/app/api/positions/route.ts

@@ -0,0 +1,120 @@
+import { NextRequest, NextResponse } from 'next/server'
+import {
+  getPositionMappings,
+  getPositionMappingById,
+  deletePositionMapping,
+  updatePositionMappingStatus,
+} from '@/lib/db/queries'
+import { CopyEngine } from '@/lib/copier/index'
+import { getPoolInfoMap, getPoolLabel } from '@/lib/pool-info'
+
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+const { TickMath } = require('@/lib/clmm-sdk/dist/index.js')
+
+export async function GET(req: NextRequest) {
+  const status = req.nextUrl.searchParams.get('status') || undefined
+  const positions = getPositionMappings(status)
+
+  // Enrich positions with pool info and price range
+  const poolMap = await getPoolInfoMap()
+
+  const enriched = positions.map((pos) => {
+    const pool = poolMap.get(pos.pool_id)
+    let poolLabel = ''
+    let priceLower = ''
+    let priceUpper = ''
+
+    if (pool) {
+      poolLabel = getPoolLabel(pool)
+      const decimalsA = pool.mintA.mintInfo.decimals
+      const decimalsB = pool.mintB.mintInfo.decimals
+      const baseIn = !pool.displayReversed
+
+      // Calculate price from tick using SDK TickMath
+      try {
+        const lower = TickMath.getPriceFromTick({
+          tick: pos.tick_lower,
+          decimalsA,
+          decimalsB,
+          baseIn,
+        })
+        const upper = TickMath.getPriceFromTick({
+          tick: pos.tick_upper,
+          decimalsA,
+          decimalsB,
+          baseIn,
+        })
+        priceLower = lower.toString()
+        priceUpper = upper.toString()
+      } catch {
+        // Fallback: show tick values if price calculation fails
+      }
+    }
+
+    return {
+      ...pos,
+      pool_label: poolLabel,
+      price_lower: priceLower,
+      price_upper: priceUpper,
+    }
+  })
+
+  return NextResponse.json(enriched)
+}
+
+/**
+ * DELETE /api/positions?id=123 - 从数据库删除仓位映射(不上链)
+ */
+export async function DELETE(req: NextRequest) {
+  const id = req.nextUrl.searchParams.get('id')
+  if (!id) {
+    return NextResponse.json({ error: 'Missing id parameter' }, { status: 400 })
+  }
+
+  const mapping = getPositionMappingById(Number(id))
+  if (!mapping) {
+    return NextResponse.json({ error: 'Position not found' }, { status: 404 })
+  }
+
+  deletePositionMapping(Number(id))
+  return NextResponse.json({ success: true })
+}
+
+/**
+ * POST /api/positions - 手动关仓(上链)
+ * body: { id: number }
+ */
+export async function POST(req: NextRequest) {
+  try {
+    const body = await req.json()
+    const { id } = body
+
+    if (!id) {
+      return NextResponse.json({ error: 'Missing id' }, { status: 400 })
+    }
+
+    const mapping = getPositionMappingById(Number(id))
+    if (!mapping) {
+      return NextResponse.json({ error: 'Position not found' }, { status: 404 })
+    }
+
+    if (!mapping.our_nft_mint) {
+      return NextResponse.json({ error: 'No NFT mint - position not yet created' }, { status: 400 })
+    }
+
+    if (mapping.status === 'closed') {
+      return NextResponse.json({ error: 'Position already closed' }, { status: 400 })
+    }
+
+    const engine = new CopyEngine()
+    const txid = await engine.manualClosePosition(mapping.our_nft_mint, mapping.pool_id)
+
+    updatePositionMappingStatus(mapping.target_nft_mint, 'closed')
+
+    return NextResponse.json({ success: true, txid })
+  } catch (e) {
+    const msg = e instanceof Error ? e.message : String(e)
+    console.error('[API] Manual close position failed:', msg)
+    return NextResponse.json({ error: msg }, { status: 500 })
+  }
+}

+ 19 - 0
src/app/api/settings/route.ts

@@ -0,0 +1,19 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { getAllSettings, setSetting } from '@/lib/db/queries'
+
+export async function GET() {
+  const settings = getAllSettings()
+  return NextResponse.json(settings)
+}
+
+export async function PUT(req: NextRequest) {
+  const body = await req.json()
+  const { key, value } = body
+
+  if (!key || value === undefined) {
+    return NextResponse.json({ error: 'key and value are required' }, { status: 400 })
+  }
+
+  setSetting(key, String(value))
+  return NextResponse.json({ success: true })
+}

+ 65 - 0
src/app/api/wallet/balance/route.ts

@@ -0,0 +1,65 @@
+import { NextResponse } from 'next/server'
+import { PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js'
+import { getConnection } from '@/lib/solana/connection'
+import { getUserAddress } from '@/lib/solana/wallet'
+
+const KNOWN_TOKENS: Record<string, { symbol: string; decimals: number }> = {
+  EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: { symbol: 'USDC', decimals: 6 },
+  Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: { symbol: 'USDT', decimals: 6 },
+  Bybit2vBJGhPF52GBdNaQfUJ6ZpThSgHBobjWZpLPb4B: { symbol: 'BBSOL', decimals: 9 },
+}
+
+export async function GET() {
+  try {
+    const connection = getConnection()
+    const userAddress = getUserAddress()
+
+    // Get SOL balance
+    const solBalance = await connection.getBalance(userAddress)
+
+    // Get token balances
+    const tokenAccounts = await connection.getParsedTokenAccountsByOwner(userAddress, {
+      programId: new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'),
+    })
+
+    // Also get Token-2022 accounts
+    const token2022Accounts = await connection.getParsedTokenAccountsByOwner(userAddress, {
+      programId: new PublicKey('TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'),
+    })
+
+    const allAccounts = [...tokenAccounts.value, ...token2022Accounts.value]
+
+    const tokens = allAccounts
+      .map((acc) => {
+        const info = acc.account.data.parsed.info
+        const mint = info.mint as string
+        const amount = info.tokenAmount.uiAmount as number
+        const known = KNOWN_TOKENS[mint]
+        return {
+          mint,
+          symbol: known?.symbol || mint.slice(0, 6) + '...',
+          amount,
+          decimals: info.tokenAmount.decimals as number,
+          rawAmount: info.tokenAmount.amount as string,
+        }
+      })
+      .filter((t) => t.amount > 0)
+      .sort((a, b) => {
+        // Known tokens first
+        const aKnown = KNOWN_TOKENS[a.mint] ? 0 : 1
+        const bKnown = KNOWN_TOKENS[b.mint] ? 0 : 1
+        return aKnown - bKnown || b.amount - a.amount
+      })
+
+    return NextResponse.json({
+      address: userAddress.toBase58(),
+      sol: solBalance / LAMPORTS_PER_SOL,
+      tokens,
+    })
+  } catch (e) {
+    return NextResponse.json(
+      { error: e instanceof Error ? e.message : 'Failed to fetch balance' },
+      { status: 500 },
+    )
+  }
+}

+ 0 - 0
app/favicon.ico → src/app/favicon.ico


+ 29 - 0
src/app/globals.css

@@ -0,0 +1,29 @@
+@import 'tailwindcss';
+
+:root {
+  --background: #0f1117;
+  --foreground: #e4e4e7;
+  --card: #1a1b23;
+  --border: #27272a;
+  --accent: #6366f1;
+  --accent-hover: #818cf8;
+  --success: #22c55e;
+  --danger: #ef4444;
+  --warning: #f59e0b;
+  --muted: #71717a;
+}
+
+@theme inline {
+  --color-background: var(--background);
+  --color-foreground: var(--foreground);
+}
+
+body {
+  background: var(--background);
+  color: var(--foreground);
+  font-family:
+    'Inter',
+    system-ui,
+    -apple-system,
+    sans-serif;
+}

+ 113 - 0
src/app/history/page.tsx

@@ -0,0 +1,113 @@
+'use client'
+
+import useSWR from 'swr'
+
+const fetcher = (url: string) => fetch(url).then((r) => r.json())
+
+export default function HistoryPage() {
+  const { data } = useSWR('/api/history?limit=100', fetcher, { refreshInterval: 5000 })
+
+  const rows = data || []
+
+  return (
+    <div className="space-y-6">
+      <h2 className="text-lg font-semibold">Copy Trade History</h2>
+
+      <div className="rounded-lg border border-[var(--border)] bg-[var(--card)] p-4">
+        {rows.length === 0 ? (
+          <p className="text-xs text-zinc-500">No history yet</p>
+        ) : (
+          <div className="overflow-x-auto">
+            <table className="w-full text-xs">
+              <thead>
+                <tr className="text-zinc-500 border-b border-[var(--border)]">
+                  <th className="text-left py-2 pr-3">Time</th>
+                  <th className="text-left py-2 pr-3">Operation</th>
+                  <th className="text-left py-2 pr-3">Target</th>
+                  <th className="text-left py-2 pr-3">Target TX</th>
+                  <th className="text-left py-2 pr-3">Our TX</th>
+                  <th className="text-left py-2 pr-3">Amount A</th>
+                  <th className="text-left py-2 pr-3">Amount B</th>
+                  <th className="text-left py-2 pr-3">Status</th>
+                  <th className="text-left py-2">Error</th>
+                </tr>
+              </thead>
+              <tbody>
+                {rows.map(
+                  (row: {
+                    id: number
+                    created_at: string
+                    operation: string
+                    target_address: string
+                    target_tx_sig: string
+                    our_tx_sig: string | null
+                    our_amount_a: string | null
+                    our_amount_b: string | null
+                    status: string
+                    error_message: string | null
+                  }) => (
+                    <tr key={row.id} className="border-b border-zinc-800/50">
+                      <td className="py-2 pr-3 text-zinc-500 whitespace-nowrap">
+                        {new Date(row.created_at + 'Z').toLocaleString()}
+                      </td>
+                      <td className="py-2 pr-3 whitespace-nowrap">{row.operation}</td>
+                      <td className="py-2 pr-3 text-zinc-400">
+                        {row.target_address.slice(0, 4)}...{row.target_address.slice(-4)}
+                      </td>
+                      <td className="py-2 pr-3">
+                        <a
+                          href={`https://solscan.io/tx/${row.target_tx_sig}`}
+                          target="_blank"
+                          rel="noopener noreferrer"
+                          className="text-indigo-400 hover:underline"
+                        >
+                          {row.target_tx_sig.slice(0, 8)}...
+                        </a>
+                      </td>
+                      <td className="py-2 pr-3">
+                        {row.our_tx_sig ? (
+                          <a
+                            href={`https://solscan.io/tx/${row.our_tx_sig}`}
+                            target="_blank"
+                            rel="noopener noreferrer"
+                            className="text-indigo-400 hover:underline"
+                          >
+                            {row.our_tx_sig.slice(0, 8)}...
+                          </a>
+                        ) : (
+                          <span className="text-zinc-500">-</span>
+                        )}
+                      </td>
+                      <td className="py-2 pr-3 text-zinc-400">{row.our_amount_a || '-'}</td>
+                      <td className="py-2 pr-3 text-zinc-400">{row.our_amount_b || '-'}</td>
+                      <td className="py-2 pr-3">
+                        <span
+                          className={`px-1.5 py-0.5 rounded text-[10px] ${
+                            row.status === 'success'
+                              ? 'bg-green-500/20 text-green-400'
+                              : row.status === 'failed'
+                                ? 'bg-red-500/20 text-red-400'
+                                : row.status === 'executing'
+                                  ? 'bg-yellow-500/20 text-yellow-400'
+                                  : row.status === 'skipped'
+                                    ? 'bg-zinc-500/20 text-zinc-400'
+                                    : 'bg-blue-500/20 text-blue-400'
+                          }`}
+                        >
+                          {row.status}
+                        </span>
+                      </td>
+                      <td className="py-2 text-red-400 text-[10px] max-w-[200px] truncate">
+                        {row.error_message || ''}
+                      </td>
+                    </tr>
+                  ),
+                )}
+              </tbody>
+            </table>
+          </div>
+        )}
+      </div>
+    </div>
+  )
+}

+ 25 - 0
src/app/layout.tsx

@@ -0,0 +1,25 @@
+import type { Metadata } from 'next'
+import './globals.css'
+import { Sidebar } from '@/components/layout/sidebar'
+import { Header } from '@/components/layout/header'
+
+export const metadata: Metadata = {
+  title: 'Byreal Copy Trade',
+  description: 'CLMM Position Copy Trading System',
+}
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+  return (
+    <html lang="en">
+      <body className="antialiased">
+        <div className="flex min-h-screen">
+          <Sidebar />
+          <div className="flex-1 flex flex-col">
+            <Header />
+            <main className="flex-1 p-6">{children}</main>
+          </div>
+        </div>
+      </body>
+    </html>
+  )
+}

+ 358 - 0
src/app/page.tsx

@@ -0,0 +1,358 @@
+'use client'
+
+import { useState } from 'react'
+import useSWR from 'swr'
+
+const fetcher = (url: string) => fetch(url).then((r) => r.json())
+
+function StatusCard() {
+  const { data: status, mutate } = useSWR('/api/monitor/status', fetcher, {
+    refreshInterval: 3000,
+  })
+  const [error, setError] = useState<string | null>(null)
+  const [loading, setLoading] = useState(false)
+
+  const handleStart = async () => {
+    setError(null)
+    setLoading(true)
+    try {
+      const res = await fetch('/api/monitor/start', { method: 'POST' })
+      const data = await res.json()
+      if (!res.ok) {
+        setError(data.error || 'Failed to start')
+      } else {
+        mutate(data.status, { revalidate: false })
+      }
+    } catch (e) {
+      setError(e instanceof Error ? e.message : 'Network error')
+    } finally {
+      setLoading(false)
+    }
+  }
+
+  const handleStop = async () => {
+    setError(null)
+    setLoading(true)
+    try {
+      const res = await fetch('/api/monitor/stop', { method: 'POST' })
+      const data = await res.json()
+      if (!res.ok) {
+        setError(data.error || 'Failed to stop')
+      } else {
+        mutate(data.status, { revalidate: false })
+      }
+    } catch (e) {
+      setError(e instanceof Error ? e.message : 'Network error')
+    } finally {
+      setLoading(false)
+    }
+  }
+
+  return (
+    <div className="rounded-lg border border-[var(--border)] bg-[var(--card)] p-4">
+      <div className="flex items-center justify-between mb-3">
+        <h3 className="text-sm font-medium">Monitor Status</h3>
+        <span
+          className={`text-xs px-2 py-0.5 rounded-full ${
+            status?.running ? 'bg-green-500/20 text-green-400' : 'bg-zinc-500/20 text-zinc-400'
+          }`}
+        >
+          {status?.running ? 'Running' : 'Stopped'}
+        </span>
+      </div>
+      <p className="text-xs text-zinc-500 mb-3">Watching {status?.watchedCount || 0} addresses</p>
+      {error && (
+        <p className="text-xs text-red-400 mb-2 bg-red-500/10 px-2 py-1 rounded">{error}</p>
+      )}
+      {status?.errors?.length > 0 && (
+        <div className="mb-3 max-h-20 overflow-y-auto space-y-1">
+          {status.errors.map((err: string, i: number) => (
+            <p key={i} className="text-[10px] text-red-400/80 bg-red-500/5 px-2 py-0.5 rounded truncate" title={err}>
+              {err}
+            </p>
+          ))}
+        </div>
+      )}
+      <div className="flex gap-2">
+        <button
+          onClick={handleStart}
+          disabled={status?.running || loading}
+          className="px-3 py-1.5 text-xs rounded bg-indigo-500 text-white hover:bg-indigo-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
+        >
+          {loading && !status?.running ? 'Starting...' : 'Start'}
+        </button>
+        <button
+          onClick={handleStop}
+          disabled={!status?.running || loading}
+          className="px-3 py-1.5 text-xs rounded bg-red-500/20 text-red-400 hover:bg-red-500/30 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
+        >
+          {loading && status?.running ? 'Stopping...' : 'Stop'}
+        </button>
+      </div>
+    </div>
+  )
+}
+
+function WalletBalances() {
+  const { data } = useSWR('/api/wallet/balance', fetcher, { refreshInterval: 15000 })
+
+  return (
+    <div className="rounded-lg border border-[var(--border)] bg-[var(--card)] p-4">
+      <h3 className="text-sm font-medium mb-3">Wallet Balances</h3>
+      {data?.error ? (
+        <p className="text-xs text-zinc-500">Configure SOL_SECRET_KEY to view balances</p>
+      ) : (
+        <div className="max-h-32 overflow-y-auto space-y-1.5">
+          <div className="flex justify-between text-xs">
+            <span className="text-zinc-500">SOL</span>
+            <span>{data?.sol?.toFixed(4) || '...'}</span>
+          </div>
+          {data?.tokens?.map((t: { mint: string; symbol: string; amount: number }) => (
+            <div key={t.mint} className="flex justify-between text-xs">
+              <span className="text-zinc-500">{t.symbol}</span>
+              <span>{t.amount.toFixed(4)}</span>
+            </div>
+          ))}
+        </div>
+      )}
+    </div>
+  )
+}
+
+interface PositionRow {
+  id: number
+  target_address: string
+  target_nft_mint: string
+  our_nft_mint: string | null
+  pool_id: string
+  pool_label: string
+  tick_lower: number
+  tick_upper: number
+  price_lower: string
+  price_upper: string
+  status: string
+  created_at: string
+  updated_at: string
+}
+
+function formatPrice(price: string): string {
+  if (!price) return ''
+  const n = parseFloat(price)
+  if (isNaN(n)) return price
+  if (n >= 1000) return n.toFixed(2)
+  if (n >= 1) return n.toFixed(4)
+  if (n >= 0.0001) return n.toFixed(6)
+  return n.toExponential(3)
+}
+
+function ActivePositions() {
+  const { data, mutate } = useSWR('/api/positions?status=active', fetcher, { refreshInterval: 5000 })
+  const [closingId, setClosingId] = useState<number | null>(null)
+
+  const rows: PositionRow[] = data || []
+
+  const handleClose = async (row: PositionRow) => {
+    if (!row.our_nft_mint) return
+    if (
+      !confirm(
+        `关闭仓位 ${row.pool_label || row.pool_id.slice(0, 8)}?\n将移除全部流动性并swap回USDC。`,
+      )
+    )
+      return
+
+    setClosingId(row.id)
+    try {
+      const res = await fetch('/api/positions', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({ id: row.id }),
+      })
+      const data = await res.json()
+      if (!res.ok) {
+        alert(data.error || 'Close failed')
+      } else {
+        mutate()
+      }
+    } catch (e) {
+      alert(e instanceof Error ? e.message : 'Close failed')
+    } finally {
+      setClosingId(null)
+    }
+  }
+
+  return (
+    <div className="rounded-lg border border-[var(--border)] bg-[var(--card)] p-4">
+      <div className="flex items-center justify-between mb-3">
+        <h3 className="text-sm font-medium">Active Positions</h3>
+        <span className="text-xs text-zinc-500">{rows.length} open</span>
+      </div>
+      {rows.length === 0 ? (
+        <p className="text-xs text-zinc-500">No active positions</p>
+      ) : (
+        <div className="space-y-2 max-h-64 overflow-y-auto">
+          {rows.map((row) => (
+            <div
+              key={row.id}
+              className="flex items-center justify-between gap-3 rounded-md bg-zinc-800/40 px-3 py-2"
+            >
+              <div className="min-w-0 flex-1 space-y-0.5">
+                <div className="flex items-center gap-2 text-xs">
+                  {row.pool_label ? (
+                    <span className="font-medium text-zinc-200">{row.pool_label}</span>
+                  ) : (
+                    <span className="font-mono text-zinc-300">
+                      {row.pool_id.slice(0, 6)}...{row.pool_id.slice(-4)}
+                    </span>
+                  )}
+                  {row.price_lower && row.price_upper ? (
+                    <span className="text-[10px] text-zinc-500">
+                      {formatPrice(row.price_lower)} ~ {formatPrice(row.price_upper)}
+                    </span>
+                  ) : (
+                    <span className="text-[10px] text-zinc-500">
+                      [{row.tick_lower} ~ {row.tick_upper}]
+                    </span>
+                  )}
+                </div>
+                <div className="flex items-center gap-2 text-[10px] text-zinc-500">
+                  <span>
+                    Target:{' '}
+                    <a
+                      href={`https://solscan.io/account/${row.target_address}`}
+                      target="_blank"
+                      rel="noopener noreferrer"
+                      className="text-indigo-400/70 hover:underline"
+                    >
+                      {row.target_address.slice(0, 4)}...{row.target_address.slice(-4)}
+                    </a>
+                  </span>
+                  {row.our_nft_mint && (
+                    <span>
+                      NFT:{' '}
+                      <a
+                        href={`https://solscan.io/token/${row.our_nft_mint}`}
+                        target="_blank"
+                        rel="noopener noreferrer"
+                        className="text-indigo-400/70 hover:underline"
+                      >
+                        {row.our_nft_mint.slice(0, 6)}...
+                      </a>
+                    </span>
+                  )}
+                </div>
+              </div>
+              <div className="flex items-center gap-2 shrink-0">
+                {row.our_nft_mint && (
+                  <button
+                    onClick={() => handleClose(row)}
+                    disabled={closingId === row.id}
+                    className="px-2 py-0.5 text-[10px] rounded bg-orange-500/20 text-orange-400 hover:bg-orange-500/30 disabled:opacity-50 transition-colors"
+                  >
+                    {closingId === row.id ? 'Closing...' : 'Close'}
+                  </button>
+                )}
+                <span className="px-1.5 py-0.5 rounded text-[10px] bg-green-500/20 text-green-400">
+                  active
+                </span>
+              </div>
+            </div>
+          ))}
+        </div>
+      )}
+    </div>
+  )
+}
+
+function RecentCopies() {
+  const { data } = useSWR('/api/history?limit=10', fetcher, { refreshInterval: 5000 })
+
+  const rows = data || []
+
+  return (
+    <div className="rounded-lg border border-[var(--border)] bg-[var(--card)] p-4">
+      <h3 className="text-sm font-medium mb-3">Recent Copy Operations</h3>
+      {rows.length === 0 ? (
+        <p className="text-xs text-zinc-500">No copy operations yet</p>
+      ) : (
+        <div className="overflow-x-auto">
+          <table className="w-full text-xs">
+            <thead>
+              <tr className="text-zinc-500 border-b border-[var(--border)]">
+                <th className="text-left py-2 pr-3">Time</th>
+                <th className="text-left py-2 pr-3">Operation</th>
+                <th className="text-left py-2 pr-3">Target</th>
+                <th className="text-left py-2 pr-3">Status</th>
+                <th className="text-left py-2">TX</th>
+              </tr>
+            </thead>
+            <tbody>
+              {rows.map(
+                (row: {
+                  id: number
+                  created_at: string
+                  operation: string
+                  target_address: string
+                  status: string
+                  our_tx_sig: string | null
+                }) => (
+                  <tr key={row.id} className="border-b border-zinc-800/50">
+                    <td className="py-2 pr-3 text-zinc-500">
+                      {new Date(row.created_at + 'Z').toLocaleTimeString()}
+                    </td>
+                    <td className="py-2 pr-3">{row.operation}</td>
+                    <td className="py-2 pr-3 text-zinc-500">
+                      {row.target_address.slice(0, 4)}...{row.target_address.slice(-4)}
+                    </td>
+                    <td className="py-2 pr-3">
+                      <span
+                        className={`px-1.5 py-0.5 rounded text-[10px] ${
+                          row.status === 'success'
+                            ? 'bg-green-500/20 text-green-400'
+                            : row.status === 'failed'
+                              ? 'bg-red-500/20 text-red-400'
+                              : row.status === 'executing'
+                                ? 'bg-yellow-500/20 text-yellow-400'
+                                : 'bg-zinc-500/20 text-zinc-400'
+                        }`}
+                      >
+                        {row.status}
+                      </span>
+                    </td>
+                    <td className="py-2">
+                      {row.our_tx_sig ? (
+                        <a
+                          href={`https://solscan.io/tx/${row.our_tx_sig}`}
+                          target="_blank"
+                          rel="noopener noreferrer"
+                          className="text-indigo-400 hover:underline"
+                        >
+                          {row.our_tx_sig.slice(0, 8)}...
+                        </a>
+                      ) : (
+                        <span className="text-zinc-500">-</span>
+                      )}
+                    </td>
+                  </tr>
+                ),
+              )}
+            </tbody>
+          </table>
+        </div>
+      )}
+    </div>
+  )
+}
+
+export default function DashboardPage() {
+  return (
+    <div className="space-y-6">
+      <h2 className="text-lg font-semibold">Dashboard</h2>
+      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
+        <StatusCard />
+        <WalletBalances />
+      </div>
+      <ActivePositions />
+      <RecentCopies />
+    </div>
+  )
+}

+ 209 - 0
src/app/positions/page.tsx

@@ -0,0 +1,209 @@
+'use client'
+
+import { useState } from 'react'
+import useSWR from 'swr'
+
+const fetcher = (url: string) => fetch(url).then((r) => r.json())
+
+interface PositionRow {
+  id: number
+  target_address: string
+  target_nft_mint: string
+  our_nft_mint: string | null
+  pool_id: string
+  pool_label: string
+  tick_lower: number
+  tick_upper: number
+  price_lower: string
+  price_upper: string
+  status: string
+  created_at: string
+}
+
+function formatPrice(price: string): string {
+  if (!price) return ''
+  const n = parseFloat(price)
+  if (isNaN(n)) return price
+  if (n >= 1000) return n.toFixed(2)
+  if (n >= 1) return n.toFixed(4)
+  if (n >= 0.0001) return n.toFixed(6)
+  return n.toExponential(3)
+}
+
+export default function PositionsPage() {
+  const { data: positions, mutate } = useSWR('/api/positions', fetcher, { refreshInterval: 5000 })
+  const [closingId, setClosingId] = useState<number | null>(null)
+  const [deletingId, setDeletingId] = useState<number | null>(null)
+  const [error, setError] = useState<string | null>(null)
+
+  const rows: PositionRow[] = positions || []
+
+  const handleClose = async (row: PositionRow) => {
+    if (
+      !confirm(
+        `Close position ${row.our_nft_mint?.slice(0, 8)}...? This will remove all liquidity on-chain and swap tokens back to USDC.`,
+      )
+    ) {
+      return
+    }
+    setClosingId(row.id)
+    setError(null)
+    try {
+      const res = await fetch('/api/positions', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({ id: row.id }),
+      })
+      const data = await res.json()
+      if (!res.ok) {
+        setError(data.error || 'Close failed')
+      } else {
+        mutate()
+      }
+    } catch (e) {
+      setError(e instanceof Error ? e.message : 'Close failed')
+    } finally {
+      setClosingId(null)
+    }
+  }
+
+  const handleDelete = async (row: PositionRow) => {
+    if (
+      !confirm(
+        `Delete position mapping #${row.id}? This only removes the record from the database, not from the blockchain.`,
+      )
+    ) {
+      return
+    }
+    setDeletingId(row.id)
+    setError(null)
+    try {
+      const res = await fetch(`/api/positions?id=${row.id}`, { method: 'DELETE' })
+      const data = await res.json()
+      if (!res.ok) {
+        setError(data.error || 'Delete failed')
+      } else {
+        mutate()
+      }
+    } catch (e) {
+      setError(e instanceof Error ? e.message : 'Delete failed')
+    } finally {
+      setDeletingId(null)
+    }
+  }
+
+  return (
+    <div className="space-y-6">
+      <h2 className="text-lg font-semibold">Position Mappings</h2>
+
+      {error && (
+        <div className="rounded-lg border border-red-500/30 bg-red-500/10 p-3 text-xs text-red-400">
+          {error}
+          <button onClick={() => setError(null)} className="ml-2 underline">
+            dismiss
+          </button>
+        </div>
+      )}
+
+      <div className="rounded-lg border border-[var(--border)] bg-[var(--card)] p-4">
+        {rows.length === 0 ? (
+          <p className="text-xs text-zinc-500">No position mappings yet</p>
+        ) : (
+          <div className="overflow-x-auto">
+            <table className="w-full text-xs">
+              <thead>
+                <tr className="text-zinc-500 border-b border-[var(--border)]">
+                  <th className="text-left py-2 pr-3">Target</th>
+                  <th className="text-left py-2 pr-3">Target NFT</th>
+                  <th className="text-left py-2 pr-3">Our NFT</th>
+                  <th className="text-left py-2 pr-3">Pool</th>
+                  <th className="text-left py-2 pr-3">Price Range</th>
+                  <th className="text-left py-2 pr-3">Status</th>
+                  <th className="text-left py-2 pr-3">Created</th>
+                  <th className="text-left py-2">Actions</th>
+                </tr>
+              </thead>
+              <tbody>
+                {rows.map((row) => (
+                  <tr key={row.id} className="border-b border-zinc-800/50">
+                    <td className="py-2 pr-3 text-zinc-400">
+                      {row.target_address.slice(0, 4)}...{row.target_address.slice(-4)}
+                    </td>
+                    <td className="py-2 pr-3">
+                      <a
+                        href={`https://solscan.io/token/${row.target_nft_mint}`}
+                        target="_blank"
+                        rel="noopener noreferrer"
+                        className="text-indigo-400 hover:underline"
+                      >
+                        {row.target_nft_mint.slice(0, 6)}...
+                      </a>
+                    </td>
+                    <td className="py-2 pr-3">
+                      {row.our_nft_mint ? (
+                        <a
+                          href={`https://solscan.io/token/${row.our_nft_mint}`}
+                          target="_blank"
+                          rel="noopener noreferrer"
+                          className="text-indigo-400 hover:underline"
+                        >
+                          {row.our_nft_mint.slice(0, 6)}...
+                        </a>
+                      ) : (
+                        <span className="text-zinc-500">pending</span>
+                      )}
+                    </td>
+                    <td className="py-2 pr-3 text-zinc-400">
+                      {row.pool_label || `${row.pool_id.slice(0, 6)}...`}
+                    </td>
+                    <td className="py-2 pr-3 text-zinc-400">
+                      {row.price_lower && row.price_upper
+                        ? `${formatPrice(row.price_lower)} ~ ${formatPrice(row.price_upper)}`
+                        : `${row.tick_lower} ~ ${row.tick_upper}`}
+                    </td>
+                    <td className="py-2 pr-3">
+                      <span
+                        className={`px-1.5 py-0.5 rounded text-[10px] ${
+                          row.status === 'active'
+                            ? 'bg-green-500/20 text-green-400'
+                            : row.status === 'closed'
+                              ? 'bg-zinc-500/20 text-zinc-400'
+                              : 'bg-red-500/20 text-red-400'
+                        }`}
+                      >
+                        {row.status}
+                      </span>
+                    </td>
+                    <td className="py-2 pr-3 text-zinc-500">
+                      {new Date(row.created_at + 'Z').toLocaleDateString()}
+                    </td>
+                    <td className="py-2">
+                      <div className="flex gap-1.5">
+                        {row.status === 'active' && row.our_nft_mint && (
+                          <button
+                            onClick={() => handleClose(row)}
+                            disabled={closingId === row.id}
+                            className="px-2 py-0.5 text-[10px] rounded bg-orange-500/20 text-orange-400 hover:bg-orange-500/30 disabled:opacity-50 transition-colors"
+                          >
+                            {closingId === row.id ? 'Closing...' : 'Close'}
+                          </button>
+                        )}
+                        <button
+                          onClick={() => handleDelete(row)}
+                          disabled={deletingId === row.id}
+                          className="px-2 py-0.5 text-[10px] rounded bg-red-500/20 text-red-400 hover:bg-red-500/30 disabled:opacity-50 transition-colors"
+                        >
+                          {deletingId === row.id ? 'Deleting...' : 'Delete'}
+                        </button>
+                      </div>
+                    </td>
+                  </tr>
+                ))}
+              </tbody>
+            </table>
+          </div>
+        )}
+      </div>
+    </div>
+  )
+}

+ 177 - 0
src/app/settings/page.tsx

@@ -0,0 +1,177 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import useSWR from 'swr'
+
+const fetcher = (url: string) => fetch(url).then((r) => r.json())
+
+export default function SettingsPage() {
+  const { data: settings, mutate } = useSWR('/api/settings', fetcher)
+  const { data: wallet } = useSWR('/api/wallet/balance', fetcher)
+
+  const [multiplier, setMultiplier] = useState('1.0')
+  const [maxUsd, setMaxUsd] = useState('1000')
+  const [slippage, setSlippage] = useState('0.02')
+  const [pollInterval, setPollInterval] = useState('5000')
+  const [swapAfterClose, setSwapAfterClose] = useState(true)
+  const [saving, setSaving] = useState(false)
+
+  useEffect(() => {
+    if (settings) {
+      if (settings.copy_multiplier) setMultiplier(settings.copy_multiplier)
+      if (settings.copy_max_usd) setMaxUsd(settings.copy_max_usd)
+      if (settings.copy_slippage) setSlippage(settings.copy_slippage)
+      if (settings.monitor_poll_interval) setPollInterval(settings.monitor_poll_interval)
+      if (settings.swap_after_close !== undefined) setSwapAfterClose(settings.swap_after_close !== 'false')
+    }
+  }, [settings])
+
+  const handleSave = async () => {
+    setSaving(true)
+    try {
+      await Promise.all([
+        fetch('/api/settings', {
+          method: 'PUT',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({ key: 'copy_multiplier', value: multiplier }),
+        }),
+        fetch('/api/settings', {
+          method: 'PUT',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({ key: 'copy_max_usd', value: maxUsd }),
+        }),
+        fetch('/api/settings', {
+          method: 'PUT',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({ key: 'copy_slippage', value: slippage }),
+        }),
+        fetch('/api/settings', {
+          method: 'PUT',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({ key: 'monitor_poll_interval', value: pollInterval }),
+        }),
+        fetch('/api/settings', {
+          method: 'PUT',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({ key: 'swap_after_close', value: String(swapAfterClose) }),
+        }),
+      ])
+      mutate()
+    } finally {
+      setSaving(false)
+    }
+  }
+
+  return (
+    <div className="space-y-6">
+      <h2 className="text-lg font-semibold">Settings</h2>
+
+      <div className="rounded-lg border border-[var(--border)] bg-[var(--card)] p-4 space-y-4">
+        <h3 className="text-sm font-medium">Wallet Info</h3>
+        <div className="text-xs space-y-1">
+          <div className="flex gap-2">
+            <span className="text-zinc-500 w-20">Address:</span>
+            <code className="text-zinc-300">{wallet?.address || 'Not configured'}</code>
+          </div>
+          <div className="flex gap-2">
+            <span className="text-zinc-500 w-20">SOL:</span>
+            <span>{wallet?.sol?.toFixed(4) || '-'}</span>
+          </div>
+        </div>
+      </div>
+
+      <div className="rounded-lg border border-[var(--border)] bg-[var(--card)] p-4 space-y-4">
+        <h3 className="text-sm font-medium">Copy Trading Parameters (Global Defaults)</h3>
+        <p className="text-[10px] text-zinc-600">
+          These are global defaults. Each watched address can override Multiplier and Max USD in the
+          Addresses page.
+        </p>
+        <div className="space-y-3">
+          <div>
+            <label className="block text-xs text-zinc-500 mb-1">
+              Default Multiplier (1.0 = same as target, 2.0 = 2x, 0.5 = half)
+            </label>
+            <input
+              type="number"
+              step="0.1"
+              min="0.01"
+              max="100"
+              value={multiplier}
+              onChange={(e) => setMultiplier(e.target.value)}
+              className="w-48 px-3 py-1.5 text-sm rounded border border-[var(--border)] bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:border-indigo-500"
+            />
+          </div>
+          <div>
+            <label className="block text-xs text-zinc-500 mb-1">
+              Default Max USD per Copy (cap total position value in USD)
+            </label>
+            <input
+              type="number"
+              step="100"
+              min="1"
+              max="1000000"
+              value={maxUsd}
+              onChange={(e) => setMaxUsd(e.target.value)}
+              className="w-48 px-3 py-1.5 text-sm rounded border border-[var(--border)] bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:border-indigo-500"
+            />
+          </div>
+          <div>
+            <label className="block text-xs text-zinc-500 mb-1">
+              Slippage Tolerance (0.02 = 2%)
+            </label>
+            <input
+              type="number"
+              step="0.01"
+              min="0.001"
+              max="0.5"
+              value={slippage}
+              onChange={(e) => setSlippage(e.target.value)}
+              className="w-48 px-3 py-1.5 text-sm rounded border border-[var(--border)] bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:border-indigo-500"
+            />
+          </div>
+          <div>
+            <label className="block text-xs text-zinc-500 mb-1">Poll Interval (ms)</label>
+            <input
+              type="number"
+              step="1000"
+              min="1000"
+              max="60000"
+              value={pollInterval}
+              onChange={(e) => setPollInterval(e.target.value)}
+              className="w-48 px-3 py-1.5 text-sm rounded border border-[var(--border)] bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:border-indigo-500"
+            />
+          </div>
+          <div className="flex items-center gap-3">
+            <button
+              type="button"
+              onClick={() => setSwapAfterClose(!swapAfterClose)}
+              className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${
+                swapAfterClose ? 'bg-indigo-500' : 'bg-zinc-600'
+              }`}
+            >
+              <span
+                className={`inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform ${
+                  swapAfterClose ? 'translate-x-4.5' : 'translate-x-0.5'
+                }`}
+              />
+            </button>
+            <label className="text-xs text-zinc-500">
+              Swap tokens back to USDC after closing position
+            </label>
+          </div>
+        </div>
+        <button
+          onClick={handleSave}
+          disabled={saving}
+          className="px-4 py-1.5 text-sm rounded bg-indigo-500 text-white hover:bg-indigo-400 disabled:opacity-50 transition-colors"
+        >
+          {saving ? 'Saving...' : 'Save Settings'}
+        </button>
+        <p className="text-[10px] text-zinc-600">
+          Note: Changes take effect on the next copy operation. Env variables (SOL_ENDPOINT,
+          SOL_SECRET_KEY, JUPITER_API_KEY) must be set in .env.local.
+        </p>
+      </div>
+    </div>
+  )
+}

+ 25 - 0
src/components/layout/header.tsx

@@ -0,0 +1,25 @@
+'use client'
+
+import useSWR from 'swr'
+
+const fetcher = (url: string) => fetch(url).then((r) => r.json())
+
+export function Header() {
+  const { data } = useSWR('/api/wallet/balance', fetcher, { refreshInterval: 30000 })
+
+  return (
+    <header className="border-b border-[var(--border)] px-6 py-3 flex items-center justify-between bg-[var(--card)]">
+      <h2 className="text-sm font-medium">Copy Trading Dashboard</h2>
+      <div className="flex items-center gap-4 text-xs">
+        {data?.address && (
+          <span className="text-[var(--muted)]">
+            {data.address.slice(0, 4)}...{data.address.slice(-4)}
+          </span>
+        )}
+        {data?.sol !== undefined && (
+          <span className="text-[var(--foreground)]">{data.sol.toFixed(4)} SOL</span>
+        )}
+      </div>
+    </header>
+  )
+}

+ 43 - 0
src/components/layout/sidebar.tsx

@@ -0,0 +1,43 @@
+'use client'
+
+import Link from 'next/link'
+import { usePathname } from 'next/navigation'
+
+const NAV_ITEMS = [
+  { href: '/', label: 'Dashboard' },
+  { href: '/addresses', label: 'Watched Addresses' },
+  { href: '/positions', label: 'Positions' },
+  { href: '/history', label: 'History' },
+  { href: '/settings', label: 'Settings' },
+]
+
+export function Sidebar() {
+  const pathname = usePathname()
+
+  return (
+    <aside className="w-56 min-h-screen border-r border-[var(--border)] bg-[var(--card)] p-4 flex flex-col">
+      <div className="mb-8">
+        <h1 className="text-lg font-bold text-[var(--accent)]">Byreal Copy Trade</h1>
+        <p className="text-xs text-[var(--muted)] mt-1">CLMM Position Copier</p>
+      </div>
+      <nav className="flex flex-col gap-1">
+        {NAV_ITEMS.map((item) => {
+          const active = pathname === item.href
+          return (
+            <Link
+              key={item.href}
+              href={item.href}
+              className={`px-3 py-2 rounded text-sm transition-colors ${
+                active
+                  ? 'bg-[var(--accent)] text-white'
+                  : 'text-[var(--muted)] hover:text-[var(--foreground)] hover:bg-[var(--border)]'
+              }`}
+            >
+              {item.label}
+            </Link>
+          )
+        })}
+      </nav>
+    </aside>
+  )
+}

+ 52 - 0
src/lib/clmm-sdk/.gitignore

@@ -0,0 +1,52 @@
+# compiled output
+dist
+tmp
+out-tsc
+
+# dependencies
+node_modules
+output
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+yarn-error.log
+testem.log
+/typings
+
+# System Files
+.DS_Store
+Thumbs.db
+
+
+test-output
+
+.env.local
+.env
+
+vite.config.*.timestamp*
+vitest.config.*.timestamp*
+
+.cursor/mcp.json
+
+.cursor/rules/nx-rules.mdc
+.github/instructions/nx.instructions.md

+ 10 - 0
src/lib/clmm-sdk/.prettierignore

@@ -0,0 +1,10 @@
+# 忽略以下文件夹不被 Prettier 格式化
+node_modules
+/dist
+/.next
+/out
+/tmp
+/coverage
+/.nx/cache
+/.nx/workspace-data
+pnpm-lock.yaml

+ 4 - 0
src/lib/clmm-sdk/.prettierrc

@@ -0,0 +1,4 @@
+{
+  "singleQuote": true,
+  "printWidth": 120
+}

+ 58 - 0
src/lib/clmm-sdk/README.md

@@ -0,0 +1,58 @@
+# byreal-clmm-sdk
+
+## Install
+
+```bash
+yarn
+```
+
+## Running Playground Code
+
+```bash
+bun run src/playgrounds/xxxx.ts
+```
+
+Tips: When running examples in playgrounds, you need to set the private key in .env
+
+## Running Unit Tests
+
+```bash
+yarn vitest
+```
+
+## Build
+
+```bash
+yarn build
+```
+
+## Utility functions
+
+```ts
+// price -> tick
+SqrtPriceMath.getTickFromPrice;
+// tick -> sqrtPriceX64
+SqrtPriceMath.getSqrtPriceX64FromTick;
+// sqrtPriceX64 -> price
+SqrtPriceMath.sqrtPriceX64ToPrice;
+
+// price -> sqrtPriceX64
+SqrtPriceMath.priceToSqrtPriceX64;
+
+// sqrtPriceX64 -> tick
+SqrtPriceMath.getTickFromSqrtPriceX64;
+
+// price -> tick
+SqrtPriceMath.getTickFromPrice;
+
+// tick -> price
+TickMath.getPriceFromTick;
+
+// price -> tick
+// price -> sqrtPriceX64
+// price -> roundedPrice
+TickMath.getTickAlignedPriceDetails;
+
+// Calculate the position address from the nft address
+getPdaPersonalPositionAddress;
+```

+ 36 - 0
src/lib/clmm-sdk/package.json

@@ -0,0 +1,36 @@
+{
+  "name": "@byreal/clmm-sdk",
+  "version": "0.0.1",
+  "private": true,
+  "main": "./dist/index.js",
+  "module": "./dist/index.js",
+  "types": "./dist/index.d.ts",
+  "exports": {
+    "./package.json": "./package.json",
+    ".": {
+      "types": "./dist/index.d.ts",
+      "import": "./dist/index.js",
+      "default": "./dist/index.js"
+    }
+  },
+  "scripts": {
+    "test": "vitest",
+    "build": "tsc"
+  },
+  "dependencies": {
+    "@coral-xyz/anchor": "^0.31.1",
+    "@noble/hashes": "^1.8.0",
+    "@solana/buffer-layout": "^4.0.1",
+    "@solana/spl-token": "^0.4.13",
+    "@solana/web3.js": "^1.98.2",
+    "bn.js": "^5.2.1",
+    "bs58": "^6.0.0",
+    "decimal.js": "^10.5.0",
+    "dotenv": "^16.5.0",
+    "ky": "^1.8.1",
+    "lodash-es": "^4.17.21",
+    "tslib": "^2.3.0",
+    "typescript": "~5.7.2",
+    "vitest": "^3.0.9"
+  }
+}

+ 80 - 0
src/lib/clmm-sdk/src/calculate.ts

@@ -0,0 +1,80 @@
+// Various calculations
+
+import { Decimal } from 'decimal.js';
+
+import { MAX_TICK, MIN_TICK, PoolUtils, SqrtPriceMath, TickMath } from './instructions/index.js';
+
+/**
+ * Calculate tick-aligned price range
+ * @param poolInfo
+ * @param startPrice
+ * @param endPrice
+ * @returns
+ */
+export function calculateTickAlignedPriceRange(info: {
+  tickSpacing: number;
+  mintDecimalsA: number;
+  mintDecimalsB: number;
+  startPrice: string | number;
+  endPrice: string | number;
+}) {
+  const priceInTickLower = TickMath.getTickAlignedPriceDetails(
+    new Decimal(info.startPrice),
+    info.tickSpacing,
+    info.mintDecimalsA,
+    info.mintDecimalsB
+  );
+  const priceInTickUpper = TickMath.getTickAlignedPriceDetails(
+    new Decimal(info.endPrice),
+    info.tickSpacing,
+    info.mintDecimalsA,
+    info.mintDecimalsB
+  );
+  return {
+    priceInTickLower,
+    priceInTickUpper,
+  };
+}
+
+/**
+  Using the following process, reassemble a function that takes a tick + tickSpacing and returns a new price
+
+// tick + tickSpacing -> sqrtPriceX64
+SqrtPriceMath.getSqrtPriceX64FromTick;
+// sqrtPriceX64 -> price
+SqrtPriceMath.sqrtPriceX64ToPrice;
+ */
+
+export function calculatePriceFromTick(
+  tick: number | string,
+  mintDecimalsA: number,
+  mintDecimalsB: number,
+  tickSpacing: number | string
+) {
+  let tickWithSpacing = Number(tick) + Number(tickSpacing);
+
+  // If tickWithSpacing is out of MIN_TICK and MAX_TICK range, use the nearest max or min tick
+  if (tickWithSpacing < MIN_TICK || tickWithSpacing > MAX_TICK) {
+    console.warn(`!!! tickWithSpacing ${tickWithSpacing} is out of range, using the nearest tick !!!`);
+    tickWithSpacing = Number(tick);
+  }
+  const sqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tickWithSpacing);
+  const price = SqrtPriceMath.sqrtPriceX64ToPrice(sqrtPriceX64, mintDecimalsA, mintDecimalsB);
+  return {
+    price: price.toString(),
+    tick: tickWithSpacing,
+  };
+}
+
+/**
+ * Calculate the available price range from a TickSpacing, returns an object containing min and max prices
+ * @param tickSpacing
+ * @returns
+ */
+export function calculatePriceRangeFromTickSpacing(tickSpacing: number, mintDecimalsA: number, mintDecimalsB: number) {
+  const { minTickBoundary, maxTickBoundary } = PoolUtils.tickRange(tickSpacing);
+  return {
+    min: calculatePriceFromTick(minTickBoundary, mintDecimalsA, mintDecimalsB, tickSpacing),
+    max: calculatePriceFromTick(maxTickBoundary, mintDecimalsA, mintDecimalsB, tickSpacing),
+  };
+}

+ 6 - 0
src/lib/clmm-sdk/src/client/apis/commonModels.ts

@@ -0,0 +1,6 @@
+export interface IDynamicFeeResp {
+  extreme: number;
+  high: number;
+  low: number;
+  medium: number;
+}

+ 89 - 0
src/lib/clmm-sdk/src/client/apis/index.ts

@@ -0,0 +1,89 @@
+import { IDynamicFeeResp } from './commonModels.js';
+import { createApiInstance, ApiInstance } from './ky.js';
+import { IPoolsByIdsReq, IPoolsReq, IPoolsResp } from './poolsModels.js';
+import { IMyPositionsReq, IMyPositionsResp } from './positionModels.js';
+import { ITick, ITickReq } from './tickModels.js';
+
+export type Cluster = 'mainnet' | 'devnet';
+
+export const API_URLS = {
+  BASE_HOST: 'https://your-api-host.com',
+  // ========= swap scenario =========
+  // Price inquiry
+  SWAP: 'swap',
+  // ========= Others =========
+
+  // Get pool information
+  POOL_INFO_LIST: 'dex/v1/pools/info/list',
+  POOL_INFO_BY_IDS: 'dex/v1/pools/info/ids',
+
+  // Get user positions
+  MY_POSITIONS: 'dex/v1/position/list',
+
+  // Get tick information
+  TICK: 'router/v1/query-service/list-line-position',
+
+  // Dynamic fees
+  DYNAMIC_FEE: 'dex/v1/main/auto-fee',
+};
+
+export type ApiConfigInfo = Partial<typeof API_URLS>;
+
+export interface IApiParams {
+  cluster: Cluster;
+  timeout: number;
+  urlConfigs: ApiConfigInfo;
+}
+
+export class Api {
+  public cluster: Cluster;
+
+  public api: ApiInstance;
+
+  public urlConfigs: ApiConfigInfo = API_URLS;
+
+  constructor(params?: IApiParams) {
+    const { cluster = 'mainnet', timeout = 10000, urlConfigs } = params || {};
+
+    this.cluster = cluster;
+    this.urlConfigs = {
+      ...API_URLS,
+      ...urlConfigs,
+    };
+
+    this.api = createApiInstance(this.urlConfigs.BASE_HOST || API_URLS.BASE_HOST, timeout);
+  }
+
+  async getPools(params: IPoolsReq) {
+    const data = await this.api.get<IPoolsResp>(`${this.urlConfigs.POOL_INFO_LIST || API_URLS.POOL_INFO_LIST}`, {
+      params,
+    });
+    return data;
+  }
+
+  async getPoolsByIds(params: IPoolsByIdsReq) {
+    const result = await this.api.get<IPoolsResp>(`${this.urlConfigs.POOL_INFO_BY_IDS || API_URLS.POOL_INFO_BY_IDS}`, {
+      params,
+    });
+    return result;
+  }
+
+  async getMyPositions(params: IMyPositionsReq) {
+    const data = await this.api.get<IMyPositionsResp>(`${this.urlConfigs.MY_POSITIONS || API_URLS.MY_POSITIONS}`, {
+      params,
+    });
+    return data;
+  }
+
+  async getTicks(params: ITickReq) {
+    const data = await this.api.get<ITick[]>(this.urlConfigs.TICK || API_URLS.TICK, {
+      params,
+    });
+    return data;
+  }
+
+  async getDynamicFee() {
+    const data = await this.api.get<IDynamicFeeResp>(this.urlConfigs.DYNAMIC_FEE || API_URLS.DYNAMIC_FEE);
+    return data;
+  }
+}

+ 107 - 0
src/lib/clmm-sdk/src/client/apis/ky.ts

@@ -0,0 +1,107 @@
+import ky, { Input, Options, KyInstance } from 'ky';
+
+export class RequestError extends Error {
+  code?: number;
+  url?: string;
+  constructor(message: string, code?: number, url?: string) {
+    super(message);
+    this.code = code;
+    this.url = url;
+  }
+}
+
+function handleResponseData(data: any, url?: string) {
+  const retCode = data?.ret_code ?? data?.retCode;
+  if (retCode === 0) {
+    return {
+      // FIXME temporary adaptation for swap, will be modified later
+      data: data.result.data || data.result,
+      retCode: data.result.code || retCode,
+      retMsg: data.result.msg,
+      // FIXME temporary adaptation for swap, will be modified later
+      success: data.result.success || true,
+    };
+  } else {
+    throw new RequestError(data?.ret_msg || data?.retMsg || 'Unknown error', data?.ret_code || data?.retCode, url);
+  }
+}
+
+export interface IApiInstanceResp<T> {
+  data: T;
+  retCode: number;
+  retMsg: string;
+  success: boolean;
+}
+
+export type ApiInstance = {
+  get<T>(input: Input, options?: Options & { params?: Record<string, any> }): Promise<IApiInstanceResp<T>>;
+  post<T>(input: Input, options?: Options): Promise<IApiInstanceResp<T>>;
+  put<T>(input: Input, options?: Options): Promise<IApiInstanceResp<T>>;
+  delete<T>(input: Input, options?: Options): Promise<IApiInstanceResp<T>>;
+  _kyInstance: KyInstance;
+  _ky: KyInstance;
+};
+
+/**
+ * 
+ * // GET
+const { data } = await api.get('your/path', { searchParams: { foo: 1 } });
+
+// POST
+const { data } = await api.post('your/path', { json: { foo: 1 } });
+
+// PUT
+const { data } = await api.put('your/path', { json: { foo: 1 } });
+
+// DELETE
+const { data } = await api.delete('your/path');
+ * 
+ * @param baseURL 
+ * @param timeout 
+ * @returns 
+ */
+export function createApiInstance(baseURL: string, timeout = 10000): ApiInstance {
+  const kyInstance = ky.create({
+    prefixUrl: baseURL,
+    timeout,
+    hooks: {
+      afterResponse: [
+        async (_request, _options, response) => {
+          const data = await response.clone().json();
+          const resp = handleResponseData(data, response.url);
+          // Key: wrap into Response object
+          return new Response(JSON.stringify(resp), {
+            status: response.status,
+            statusText: response.statusText,
+            headers: response.headers,
+          });
+        },
+      ],
+    },
+  });
+
+  // Common request method
+  async function request<T>(input: Input, options?: Options): Promise<IApiInstanceResp<T>> {
+    const result = await kyInstance(input, options).json<IApiInstanceResp<T>>();
+    return result;
+  }
+
+  return {
+    get: <T>(input: Input, options?: Options & { params?: Record<string, any> }) => {
+      let finalOptions = { method: 'get', ...options } as Options;
+      if (options?.params) {
+        finalOptions = {
+          ...finalOptions,
+          searchParams: options.params,
+        };
+        delete (finalOptions as any).params;
+      }
+      return request<T>(input, finalOptions);
+    },
+    post: <T>(input: Input, options?: Options) => request<T>(input, { method: 'post', ...options }),
+    put: <T>(input: Input, options?: Options) => request<T>(input, { method: 'put', ...options }),
+    delete: <T>(input: Input, options?: Options) => request<T>(input, { method: 'delete', ...options }),
+    _kyInstance: kyInstance,
+    _ky: ky,
+  };
+}

+ 87 - 0
src/lib/clmm-sdk/src/client/apis/poolsModels.ts

@@ -0,0 +1,87 @@
+export enum PoolCategory {
+  DEFAULT = 'default',
+  STABLES = 'stables',
+  RESET_LAUNCH_PAD = 'resetLaunchPad',
+}
+export interface PoolReward {
+  address: string;
+  apr: string;
+  dailyAmount: string;
+  decimals: number;
+  endTimestamp: number;
+  logoURI: string;
+  name: string;
+  price: string;
+  symbol: string;
+}
+
+export interface IPoolsReq {
+  // ids?: string[];
+  category?: PoolCategory;
+  sortField?: 'tvl' | 'volumeUsd24h' | 'feeUsd24h' | 'apr24h';
+  sortType?: 'asc' | 'desc';
+  incentiveOnly?: boolean; // todo: field to be determined, need to confirm with server
+  page: number;
+  pageSize: number;
+}
+
+export interface IPoolsResp {
+  list: PoolInfo[];
+  total: number;
+}
+
+export interface IPoolsByIdsReq {
+  ids: string[];
+}
+
+export type IPoolsByIdsResp = IPoolsResp;
+
+export interface Mint {
+  address: string;
+  decimals: number;
+  logoURI: string;
+  name: string;
+  symbol: string;
+  programId: string;
+  price: string;
+  priceChange24h: string;
+  tvl: string;
+  volumeUsd24h: string;
+}
+
+export interface PoolInfo {
+  poolAddress: string;
+  programId: string;
+  feeRate: string;
+  mintA: Mint;
+  mintB: Mint;
+  mintAVaultAddress: string;
+  mintBVaultAddress: string;
+  mintAmountA: string;
+  mintAmountB: string;
+  openTime: string;
+  config: {
+    defaultRange: number;
+    defaultRangePoint: number[];
+    fundFeeRate: string;
+    id: string;
+    index: number;
+    protocolFeeRate: string;
+    tickSpacing: number;
+    tradeFeeRate: string;
+  };
+  apr24h: string;
+  apr24hChange: string;
+  feeUsd24h: string;
+  apr7d: string;
+  feeUsd7d: string;
+  volumeUsd24h: string;
+  tvl: string;
+  ratio: string; // tokenB per tokenA
+  day: {
+    ratioHigh: string;
+    ratioLow: string;
+  };
+  category?: PoolCategory;
+  rewards?: PoolReward[];
+}

+ 22 - 0
src/lib/clmm-sdk/src/client/apis/positionModels.ts

@@ -0,0 +1,22 @@
+import { PoolInfo } from './poolsModels.js';
+export interface IMyPositionsReq {
+  userAddress: string;
+}
+
+export interface IMyPositionsResp {
+  positions: IPosition[];
+  poolMap: IPoolMap;
+}
+
+export interface IPosition {
+  activeBurnNft: boolean; // If the NFT is actively destroyed in the case of non-reduction of liquidity, the position is still there, but the deposit tokens cannot be withdrawn
+  lpNftMintAddress: string;
+  lpNftTokenAddress: string;
+  lpProviderAddress: string;
+  personalPositionAddress: string;
+  poolAddress: string;
+}
+
+export interface IPoolMap {
+  [address: string]: PoolInfo;
+}

+ 43 - 0
src/lib/clmm-sdk/src/client/apis/swapModels.ts

@@ -0,0 +1,43 @@
+/**
+ * Swap request interface, used to handle token swaps
+ *
+ * @property {string} inputMint - Input token contract address
+ * @property {string} outputMint - Output token contract address
+ * @property {string} amount - Swap amount, unit is Lamport
+ * @property {('in'|'out')} swapMode - Swap mode, 'in' means fixed input amount, 'out' means fixed output amount
+ * @property {string} slippageBps - Slippage tolerance, in basis points (1 basis point = 0.01%)
+ * @property {string} computeUnitPriceMicroLamports - Compute unit price, unit is microLamport
+ * @property {string} [quoteOnly] - Whether to only get quotes without executing transactions
+ * @property {string} [userPublicKey] - User wallet address
+ */
+export interface ISwapRequest {
+  inputMint: string;
+  outputMint: string;
+  amount: string;
+  swapMode: 'in' | 'out';
+  slippageBps: string;
+  computeUnitPriceMicroLamports: string;
+  quoteOnly?: string;
+  userPublicKey?: string;
+}
+
+/**
+ * 交换响应接口,包含交换结果信息
+ *
+ * @property {string} [transaction] - 构建后的交易编码
+ * @property {string} inputMint - 输入代币合约地址
+ * @property {string} outputMint - 输出代币合约地址
+ * @property {string} outMount - 输出数量
+ * @property {string} otherAmountThreshold - 最少收到数量
+ * @property {number} priceImpactPct - 价格影响百分比
+ * @property {string} inMount - 输入数量
+ */
+export interface ISwapResponse {
+  transaction?: string;
+  inputMint: string;
+  outputMint: string;
+  outMount: string;
+  otherAmountThreshold: string;
+  priceImpactPct: number;
+  inMount: string;
+}

+ 10 - 0
src/lib/clmm-sdk/src/client/apis/tickModels.ts

@@ -0,0 +1,10 @@
+export interface ITickReq {
+  poolId: string;
+}
+
+export interface ITick {
+  tick: number;
+  price: string;
+  originalPrice: string;
+  liquidity: string;
+}

+ 1382 - 0
src/lib/clmm-sdk/src/client/chain/index.ts

@@ -0,0 +1,1382 @@
+import {
+  TOKEN_PROGRAM_ID,
+  NATIVE_MINT,
+  createInitializeAccountInstruction,
+  createCloseAccountInstruction,
+  createAssociatedTokenAccountIdempotentInstruction,
+  AccountLayout,
+  MintLayout,
+  TOKEN_2022_PROGRAM_ID,
+} from '@solana/spl-token';
+import { Connection, PublicKey, VersionedTransaction, SystemProgram, TransactionInstruction } from '@solana/web3.js';
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import { BYREAL_CLMM_PROGRAM_ID, U64_IGNORE_RANGE, MEMO_PROGRAM_ID } from '../../constants.js';
+import {
+  IPoolLayoutWithId,
+  IPersonalPositionLayout,
+  PositionUtils,
+  RawDataUtils,
+  SqrtPriceMath,
+  TickArrayLayout,
+  TickMath,
+  TickUtils,
+  Instruction,
+  getATAAddress,
+  ITokenInfo,
+  getPdaMintExAccount,
+  getPdaTickArrayAddress,
+  PoolUtils,
+  getTickArrayBitmapExtension,
+  getTickArrayInfo,
+  getPdaExBitmapAccount,
+  MIN_SQRT_PRICE_X64,
+  MAX_SQRT_PRICE_X64,
+  TickArrayUtils,
+  TickArrayContainer,
+} from '../../instructions/index';
+import { generatePubKey } from '../../utils/generatePubKey';
+import { makeTransaction, sendTransaction, estimateComputeUnits, DEFAULT_COMPUTE_UNIT_PRICE } from '../../utils/index';
+
+import {
+  IAddLiquidityParams,
+  IClosePositionParams,
+  ICollectFeesParams,
+  ICreatePoolParams,
+  IQuoteSwapExactOutParams,
+  IQuoteSwapExactOutReturn,
+  ISwapExactOutParams,
+  ICreatePositionParams,
+  IDecreaseFullLiquidityParams,
+  IDecreaseLiquidityParams,
+  IInstructionReturn,
+  ParamsWithSignerCallback,
+  ICollectAllFeesParams,
+  SignerCallback,
+  IGetPositionInfoByNftMintReturn,
+  ICalculateCreatePositionFee,
+  IQouteSwapParams,
+  IQouteSwapReturn,
+  ISwapParams,
+} from './models';
+import {
+  alignPriceToTickPrice,
+  calculateApr,
+  calculateRewardApr,
+  calculateRangeAprs,
+  getAmountAFromAmountB,
+  getAmountBFromAmountA,
+  getTokenProgramId,
+} from './utils';
+
+/*
+ * Chain class: Encapsulates chain-level operations related to CLMM (Concentrated Liquidity Market Maker)
+
+ * Includes position, pool, token information retrieval, liquidity operations, fee collection, etc.
+ * Mainly depends on Solana web3.js, @solana/spl-token instruction tools
+ */
+export class Chain {
+  public connection: Connection;
+  public programId: PublicKey;
+  // Cache rent fee calculation results
+  private rentFeeCache: { [key: number]: number } = {};
+
+  /**
+   * Constructor
+   * @param params.connection Solana chain connection object
+   * @param params.programId CLMM program ID, default is CLMM_PROGRAM_ID
+   */
+  constructor(params: { connection: Connection; programId?: PublicKey }) {
+    const { connection, programId = BYREAL_CLMM_PROGRAM_ID } = params;
+    this.connection = connection;
+    this.programId = programId;
+  }
+
+  /**
+   * Get all CLMM position information for a specified account
+   * @param userAddress User wallet address
+   * @returns Promise<IPersonalPositionLayout[]> Position information list
+   */
+  public async getRawPositionInfoListByUserAddress(userAddress: PublicKey): Promise<IPersonalPositionLayout[]> {
+    return RawDataUtils.getRawPositionInfoListByUserAddress({
+      connection: this.connection,
+      programId: this.programId,
+      userAddress,
+    });
+  }
+
+  /**
+   * Get the corresponding position information based on the NFT mint address
+   * @param nftMint NFT mint address
+   * @returns Promise<IPersonalPositionLayout | null> Position information
+   */
+  public async getRawPositionInfoByNftMint(nftMint: PublicKey): Promise<IPersonalPositionLayout | null> {
+    return RawDataUtils.getRawPositionInfoByNftMint({
+      connection: this.connection,
+      programId: this.programId,
+      nftMint,
+    });
+  }
+
+  /**
+   * Get the corresponding pool information based on the pool address
+   * @param poolId Pool address or PublicKey
+   * @returns Promise<IPoolLayoutWithId> Pool information
+   */
+  public async getRawPoolInfoByPoolId(poolId: string | PublicKey): Promise<IPoolLayoutWithId> {
+    const poolInfo = await RawDataUtils.getRawPoolInfoByPoolId({
+      connection: this.connection,
+      poolId,
+    });
+    if (!poolInfo) throw new Error(`pool info not found, poolId: ${String(poolId)}`);
+    return poolInfo;
+  }
+
+  /**
+   * Get the corresponding token information based on the token mint address
+   * @param mintAddress Token mint address
+   * @returns Promise<...> Token information
+   */
+  public async getRawTokenInfoByMint(
+    mintAddress: PublicKey
+  ): Promise<(ReturnType<typeof MintLayout.decode> & { owner: PublicKey }) | null> {
+    const tokenInfo = await RawDataUtils.getRawTokenInfoByMint({
+      connection: this.connection,
+      mintAddress,
+    });
+    if (!tokenInfo) throw new Error(`token info not found, mintAddress: ${String(mintAddress)}`);
+    return tokenInfo;
+  }
+
+  /**
+   * Get the simplified token information (including address, precision, and programId)
+   * @param mintAddress Token mint address
+   * @returns Promise<ITokenInfo>
+   */
+  public async getTokenInfoByMint(mintAddress: PublicKey): Promise<ITokenInfo> {
+    const tokenInfo = await this.getRawTokenInfoByMint(mintAddress);
+    if (!tokenInfo) throw new Error(`token info not found, mintAddress: ${String(mintAddress)}`);
+    return {
+      address: mintAddress.toBase58(),
+      decimals: tokenInfo.decimals,
+      programId: tokenInfo.owner.toBase58(),
+    };
+  }
+
+  /**
+   * Get the detailed position information, including price range, token amount, fee, etc.
+   * @param nftMint NFT mint address
+   * @returns Promise<{...}> Detailed position information
+   */
+  public async getPositionInfoByNftMint(nftMint: PublicKey): Promise<IGetPositionInfoByNftMintReturn | null> {
+    const rawPositionInfo = await this.getRawPositionInfoByNftMint(nftMint);
+    if (!rawPositionInfo) return null;
+    const rawPoolInfo = await this.getRawPoolInfoByPoolId(rawPositionInfo.poolId);
+    const { mintDecimalsA, mintDecimalsB, tickSpacing, programId } = rawPoolInfo;
+
+    // Calculate the price corresponding to tickLower/tickUpper
+    const priceLower = TickMath.getPriceFromTick({
+      tick: rawPositionInfo.tickLower,
+      decimalsA: mintDecimalsA,
+      decimalsB: mintDecimalsB,
+    });
+    const priceUpper = TickMath.getPriceFromTick({
+      tick: rawPositionInfo.tickUpper,
+      decimalsA: mintDecimalsA,
+      decimalsB: mintDecimalsB,
+    });
+    // Calculate the actual token amount held in the position
+    const { amountA, amountB } = PositionUtils.getAmountsFromLiquidity({
+      poolInfo: rawPoolInfo,
+      ownerPosition: rawPositionInfo,
+      liquidity: rawPositionInfo.liquidity,
+      slippage: 0,
+      add: false,
+      epochInfo: await this.connection.getEpochInfo(),
+    });
+    // Calculate the amount of tokens displayed on the UI
+    const [pooledAmountA, pooledAmountB] = [
+      new Decimal(amountA.amount.toString()).div(10 ** mintDecimalsA),
+      new Decimal(amountB.amount.toString()).div(10 ** mintDecimalsB),
+    ];
+    // Get the tickArray address
+    const [tickLowerArrayAddress, tickUpperArrayAddress] = [
+      TickUtils.getTickArrayAddressByTick(
+        new PublicKey(programId),
+        new PublicKey(rawPositionInfo.poolId),
+        rawPositionInfo.tickLower,
+        tickSpacing
+      ),
+      TickUtils.getTickArrayAddressByTick(
+        new PublicKey(programId),
+        new PublicKey(rawPositionInfo.poolId),
+        rawPositionInfo.tickUpper,
+        tickSpacing
+      ),
+    ];
+    // Get the tickArray data
+    const tickArrayRes = await this.connection.getMultipleAccountsInfo([tickLowerArrayAddress, tickUpperArrayAddress]);
+    if (!tickArrayRes[0] || !tickArrayRes[1]) throw new Error('tick data not found');
+
+    // Parse as containers (supports both fixed and dynamic tick arrays)
+    const tickArrayLowerContainer = TickArrayUtils.parseTickArrayContainer(tickArrayRes[0].data, tickLowerArrayAddress);
+    const tickArrayUpperContainer = TickArrayUtils.parseTickArrayContainer(tickArrayRes[1].data, tickUpperArrayAddress);
+
+    // Get the tick state using container helper
+    const tickLowerState = TickArrayUtils.getTickStateFromContainer(
+      tickArrayLowerContainer,
+      rawPositionInfo.tickLower,
+      rawPoolInfo.tickSpacing
+    );
+    const tickUpperState = TickArrayUtils.getTickStateFromContainer(
+      tickArrayUpperContainer,
+      rawPositionInfo.tickUpper,
+      rawPoolInfo.tickSpacing
+    );
+
+    // Validate tick states
+    if (!tickLowerState || !tickUpperState) {
+      throw new Error('Tick state not found in tick array');
+    }
+
+    // Calculate the fee (original logic unchanged)
+    const tokenFees = PositionUtils.getPositionFees(rawPoolInfo, rawPositionInfo, tickLowerState, tickUpperState);
+    // Filter out abnormal fees
+    const [tokenFeeAmountA, tokenFeeAmountB] = [
+      tokenFees.tokenFeeAmountA.gte(new BN(0)) && tokenFees.tokenFeeAmountA.lt(U64_IGNORE_RANGE)
+        ? tokenFees.tokenFeeAmountA
+        : new BN(0),
+      tokenFees.tokenFeeAmountB.gte(new BN(0)) && tokenFees.tokenFeeAmountB.lt(U64_IGNORE_RANGE)
+        ? tokenFees.tokenFeeAmountB
+        : new BN(0),
+    ];
+    return {
+      priceLower,
+      priceUpper,
+      uiPriceLower: priceLower.toFixed(mintDecimalsA),
+      uiPriceUpper: priceUpper.toFixed(mintDecimalsB),
+      tokenA: {
+        address: rawPoolInfo.mintA,
+        decimals: rawPoolInfo.mintDecimalsA,
+        amount: amountA.amount,
+        feeAmount: tokenFeeAmountA,
+        uiAmount: pooledAmountA.toString(),
+        uiFeeAmount: new Decimal(tokenFeeAmountA.toString())
+          .dividedBy(new Decimal(10).pow(mintDecimalsA))
+          .toFixed(mintDecimalsA),
+      },
+      tokenB: {
+        address: rawPoolInfo.mintB,
+        decimals: rawPoolInfo.mintDecimalsB,
+        amount: amountB.amount,
+        feeAmount: tokenFeeAmountB,
+        uiAmount: pooledAmountB.toString(),
+        uiFeeAmount: new Decimal(tokenFeeAmountB.toString())
+          .dividedBy(new Decimal(10).pow(mintDecimalsB))
+          .toFixed(mintDecimalsB),
+      },
+      rawPositionInfo,
+      rawPoolInfo,
+    };
+  }
+
+  /**
+   * Create position instructions on the chain (does not directly send transactions)
+   * @param params Parameters required for creating a position
+   * @returns IInstructionReturn Contains instructions, signers, and transaction objects
+   */
+  public async createPositionInstructions(params: ICreatePositionParams): Promise<IInstructionReturn> {
+    const { userAddress, poolInfo, tickLower, tickUpper, base, baseAmount, otherAmountMax, transactionOptions, refererPosition } =
+      params;
+    const { mintA, mintB } = poolInfo;
+
+    // Calculate the actual required tokenA/B amount
+    const amountA = base === 'MintA' ? baseAmount : otherAmountMax;
+    const amountB = base === 'MintB' ? baseAmount : otherAmountMax;
+
+    const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
+      userAddress,
+      mintA,
+      mintB,
+      amountA,
+      amountB,
+    });
+
+    // Generate position creation instructions
+    const { instructions: positionInstructions, signers: positionSigners } =
+      await Instruction.openPositionFromBaseInstruction({
+        poolInfo,
+        ownerInfo: {
+          feePayer: userAddress,
+          wallet: userAddress,
+          tokenAccountA,
+          tokenAccountB,
+        },
+        tickLower,
+        tickUpper,
+        base,
+        baseAmount,
+        otherAmountMax,
+        withMetadata: 'create',
+      });
+
+    // Merge all instructions: ATA creation → pre → position → end
+    const instructions = [
+      ...preInstructions, // SOL/WSOL handling
+      ...positionInstructions, // Position creation
+      ...endInstructions, // Cleanup
+    ];
+
+    // Add memo instruction for referer position if provided
+    if (refererPosition) {
+      const memoText = `referer_position=${refererPosition}`;
+      const memoInstruction = new TransactionInstruction({
+        programId: MEMO_PROGRAM_ID,
+        keys: [{ pubkey: userAddress, isSigner: true, isWritable: false }],
+        data: Buffer.from(memoText, 'utf-8'),
+      });
+      instructions.push(memoInstruction);
+    }
+
+    const signers = [...positionSigners];
+
+    // Construct transaction object
+    const transaction = await makeTransaction({
+      connection: this.connection,
+      payerPublicKey: userAddress,
+      instructions,
+      signers,
+      options: transactionOptions,
+    });
+
+    return {
+      instructions,
+      signers,
+      nftAddress: positionSigners[0].publicKey.toString(),
+      transaction,
+    };
+  }
+
+  /**
+   * Calculate the rent fee required for creating a position
+   *
+   * @param params Parameters required for creating a position and options
+   */
+  public async calculateCreatePositionFee(
+    params: ICreatePositionParams & {
+      useCache?: boolean; // Whether to use cache, if true, the rent fee will not be recalculated through rpc; default is true
+    }
+  ): Promise<ICalculateCreatePositionFee> {
+    const {
+      userAddress,
+      poolInfo,
+      tickLower,
+      tickUpper,
+      base,
+      baseAmount,
+      otherAmountMax,
+      transactionOptions,
+      useCache = true,
+    } = params;
+
+    const computeUnitPrice = transactionOptions?.computeUnitPrice || DEFAULT_COMPUTE_UNIT_PRICE;
+
+    // Define account size constants (this is fixed hardcode)
+    const ACCOUNT_SIZES = {
+      NFT_MINT: 480,
+      NFT_HOLDER: 170,
+      PERSONAL_POSITION: 281,
+      TICK_ARRAY: 10240,
+    };
+
+    const { programId, poolId, tickSpacing } = poolInfo;
+    const tickArrayLowerStartIndex = TickUtils.getTickArrayStartIndexByTick(tickLower, tickSpacing);
+    const tickArrayUpperStartIndex = TickUtils.getTickArrayStartIndexByTick(tickUpper, tickSpacing);
+    const { publicKey: tickArrayLower } = getPdaTickArrayAddress(programId, poolId, tickArrayLowerStartIndex);
+    const { publicKey: tickArrayUpper } = getPdaTickArrayAddress(programId, poolId, tickArrayUpperStartIndex);
+
+    const [
+      blockhashData,
+      tickArrayLowerInfo,
+      tickArrayUpperInfo,
+      nftMintRentLamports,
+      nftHolderRentLamports,
+      personalPositionRentLamports,
+      tickArrayRentLamports,
+    ] = await Promise.all([
+      this.connection.getLatestBlockhash(),
+      this.connection.getAccountInfo(tickArrayLower),
+      this.connection.getAccountInfo(tickArrayUpper),
+      this.estimateRentFee(ACCOUNT_SIZES.NFT_MINT, useCache),
+      this.estimateRentFee(ACCOUNT_SIZES.NFT_HOLDER, useCache),
+      this.estimateRentFee(ACCOUNT_SIZES.PERSONAL_POSITION, useCache),
+      this.estimateRentFee(ACCOUNT_SIZES.TICK_ARRAY, useCache),
+    ]);
+
+    // Check if the tick array exists
+    const isTickArrayLowerExists = !!tickArrayLowerInfo;
+    const isTickArrayUpperExists = !!tickArrayUpperInfo;
+
+    // Prepare instructions to estimate compute units
+    const { mintA, mintB } = poolInfo;
+    const amountA = base === 'MintA' ? baseAmount : otherAmountMax;
+    const amountB = base === 'MintB' ? baseAmount : otherAmountMax;
+
+    const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
+      userAddress,
+      mintA,
+      mintB,
+      amountA,
+      amountB,
+    });
+
+    let positionInstructions: TransactionInstruction[] = [];
+
+    // Try to generate position creation instructions
+    try {
+      const { instructions } = await Instruction.openPositionFromBaseInstruction({
+        poolInfo,
+        ownerInfo: {
+          feePayer: userAddress,
+          wallet: userAddress,
+          tokenAccountA,
+          tokenAccountB,
+        },
+        tickLower,
+        tickUpper,
+        base,
+        baseAmount,
+        otherAmountMax,
+        withMetadata: 'create',
+      });
+      positionInstructions = instructions;
+    } catch {
+      // console.error('error ==> ', error);
+    }
+
+    // Estimate compute units and transaction fees
+    const computeUnits = await estimateComputeUnits(
+      this.connection,
+      [...preInstructions, ...positionInstructions, ...endInstructions],
+      userAddress,
+      blockhashData.blockhash
+    );
+
+    // 1 SOL = 10^9 lamports, 1 lamport = 10^6 microLamports
+    const transactionNetFee = (computeUnits * computeUnitPrice) / 10 ** 15;
+
+    const refundableFees = (nftMintRentLamports + nftHolderRentLamports + personalPositionRentLamports) / 10 ** 9;
+
+    // Unrefundable fees include transaction fees and newly created shared tick array accounts
+    let createTickFee = 0;
+
+    if (!isTickArrayLowerExists) {
+      createTickFee += tickArrayRentLamports / 10 ** 9;
+    }
+    if (!isTickArrayUpperExists) {
+      createTickFee += tickArrayRentLamports / 10 ** 9;
+    }
+
+    return {
+      unRefundableFees: transactionNetFee + createTickFee, // Unrefundable fees
+      transactionNetFee, // Transaction network fee
+      refundableFees, // Refundable fees
+      createTickFee, // Create tick array account fee
+    };
+  }
+
+  /**
+   * Create a new position and send a transaction
+   * @param params Parameters required for creating a position, including a signature callback
+   * @returns Promise<string> Transaction signature
+   */
+  public async createPosition(params: ParamsWithSignerCallback<ICreatePositionParams>): Promise<string> {
+    const { signerCallback } = params;
+    const { transaction } = await this.createPositionInstructions(params);
+    return sendTransaction({
+      connection: this.connection,
+      signTx: () => signerCallback(transaction),
+    });
+  }
+
+  /**
+   * Close the specified position (only when the liquidity is 0, it can be closed)
+   * @param params.userAddress User wallet address
+   * @param params.nftMint NFT mint address
+   * @returns IInstructionReturn Contains instructions and transaction objects
+   */
+  public async closePositionInstructions(params: IClosePositionParams): Promise<IInstructionReturn> {
+    const { userAddress, nftMint } = params;
+    // Get position detailed information
+    const positionInfo = await this.getPositionInfoByNftMint(nftMint);
+    if (!positionInfo) throw new Error('Position not found');
+    const mintA = positionInfo.rawPoolInfo.mintA;
+    const mintB = positionInfo.rawPoolInfo.mintB;
+    // Handle SOL/WSOL packaging
+    const { preInstructions, endInstructions } = await this.handleTokenAccount({
+      userAddress,
+      mintA,
+      mintB,
+    });
+    // Generate close position instructions
+    const { instructions: closeInstructions } = await Instruction.closePositionInstruction({
+      programId: this.programId,
+      nftMint,
+      ownerWallet: userAddress,
+    });
+    // Merge all instructions
+    const instructions = [...preInstructions, ...closeInstructions, ...endInstructions];
+    // Construct transaction object
+    const transaction = await makeTransaction({
+      connection: this.connection,
+      payerPublicKey: userAddress,
+      instructions,
+    });
+    return {
+      instructions,
+      transaction,
+    };
+  }
+
+  /**
+   * Close the specified position and send a transaction
+   * @param params.userAddress User wallet address
+   * @param params.nftMint NFT mint address
+   * @param params.signerCallback Signature callback
+   * @returns Promise<string> Transaction signature
+   */
+  public async closePosition(params: {
+    userAddress: PublicKey;
+    nftMint: PublicKey;
+    signerCallback: SignerCallback;
+  }): Promise<string> {
+    const { userAddress, nftMint, signerCallback } = params;
+    const { transaction } = await this.closePositionInstructions({
+      userAddress,
+      nftMint,
+    });
+    return sendTransaction({
+      connection: this.connection,
+      signTx: () => signerCallback(transaction),
+    });
+  }
+
+  /**
+   * Partially remove position liquidity, generate chain instructions
+   * @param params Contains user, position, removed liquidity amount, slippage, etc.
+   * @returns IInstructionReturn
+   */
+  public async decreaseLiquidityInstructions(params: IDecreaseLiquidityParams): Promise<IInstructionReturn> {
+    // Slippage is set to 2% by default
+    const { userAddress, nftMint, liquidity, slippage = 0.02 } = params;
+    // Get position raw information
+    const positionInfo = await this.getRawPositionInfoByNftMint(nftMint);
+    if (!positionInfo) throw new Error('Position not found');
+    // Check if the removed liquidity amount is valid
+    if (liquidity.gt(positionInfo.liquidity)) throw new Error('Liquidity is greater than position liquidity');
+    // Get pool information
+    const poolInfo = await this.getRawPoolInfoByPoolId(positionInfo.poolId);
+    // Handle SOL/WSOL packaging
+    const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
+      userAddress,
+      mintA: poolInfo.mintA,
+      mintB: poolInfo.mintB,
+    });
+    // Calculate the expected token amount after removing liquidity (considering slippage)
+    const { amountSlippageA, amountSlippageB } = PositionUtils.getAmountsFromLiquidity({
+      poolInfo,
+      ownerPosition: positionInfo,
+      liquidity,
+      slippage,
+      add: false,
+      epochInfo: await this.connection.getEpochInfo(),
+    });
+    // Calculate the minimum accepted token amount
+    const amountMinA = amountSlippageA.amount;
+    const amountMinB = amountSlippageB.amount;
+    // Generate remove liquidity instructions
+    const { instructions: decreaseInstructions } = await Instruction.decreaseLiquidityInstructions({
+      poolInfo,
+      ownerPosition: positionInfo,
+      ownerInfo: {
+        wallet: userAddress,
+        tokenAccountA,
+        tokenAccountB,
+      },
+      liquidity,
+      amountMinA,
+      amountMinB,
+    });
+    // Merge all instructions
+    const instructions = [...preInstructions, ...decreaseInstructions, ...endInstructions];
+    // Construct transaction object
+    const transaction = await makeTransaction({
+      connection: this.connection,
+      payerPublicKey: userAddress,
+      instructions,
+    });
+    return {
+      instructions,
+      transaction,
+    };
+  }
+
+  /**
+   * Partially remove position liquidity and send a transaction
+   * @param params Contains signature callback, etc.
+   * @returns Promise<string> Transaction signature
+   */
+  public async decreaseLiquidity(params: ParamsWithSignerCallback<IDecreaseLiquidityParams>): Promise<string> {
+    const { signerCallback } = params;
+    const { transaction } = await this.decreaseLiquidityInstructions(params);
+    return sendTransaction({
+      connection: this.connection,
+      signTx: () => signerCallback(transaction),
+    });
+  }
+
+  /**
+   * Remove all position liquidity (optional to automatically close position)
+   * @param params.closePosition Whether to close position automatically
+   * @param params Other parameters are the same as decreaseLiquidityInstructions
+   * @returns IInstructionReturn
+   */
+  public async decreaseFullLiquidityInstructions(params: IDecreaseFullLiquidityParams): Promise<IInstructionReturn> {
+    // Slippage is set to 2% by default
+    const { userAddress, nftMint, closePosition = true, slippage = 0.02 } = params;
+    // Get position raw information
+    const positionInfo = await this.getRawPositionInfoByNftMint(nftMint);
+    if (!positionInfo) throw new Error('Position not found');
+    // Get pool information
+    const poolInfo = await this.getRawPoolInfoByPoolId(positionInfo.poolId);
+    // Handle SOL/WSOL packaging
+    const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
+      userAddress,
+      mintA: poolInfo.mintA,
+      mintB: poolInfo.mintB,
+    });
+    // Use all position liquidity
+    const liquidity = positionInfo.liquidity;
+    // Calculate the expected token amount after removing liquidity (considering slippage)
+    const { amountSlippageA, amountSlippageB } = PositionUtils.getAmountsFromLiquidity({
+      poolInfo,
+      ownerPosition: positionInfo,
+      liquidity,
+      slippage,
+      add: false, // When reducing liquidity, set to false
+      epochInfo: await this.connection.getEpochInfo(),
+    });
+    // Minimum token amount after slippage adjustment
+    const amountMinA = amountSlippageA.amount;
+    const amountMinB = amountSlippageB.amount;
+    // Generate remove liquidity instructions
+    const { instructions: decreaseInstructions } = await Instruction.decreaseLiquidityInstructions({
+      poolInfo,
+      ownerPosition: positionInfo,
+      ownerInfo: {
+        wallet: userAddress,
+        tokenAccountA,
+        tokenAccountB,
+      },
+      liquidity,
+      amountMinA,
+      amountMinB,
+    });
+    // Merge all instructions
+    const instructions = [...preInstructions, ...decreaseInstructions, ...endInstructions];
+    // If you need to close the position, append the close instruction
+    if (closePosition) {
+      const { instructions: closeInstructions } = await Instruction.closePositionInstruction({
+        programId: this.programId,
+        nftMint,
+        ownerWallet: userAddress,
+      });
+      instructions.push(...closeInstructions);
+    }
+    // Construct transaction object
+    const transaction = await makeTransaction({
+      connection: this.connection,
+      payerPublicKey: userAddress,
+      instructions,
+    });
+    return {
+      instructions,
+      transaction,
+    };
+  }
+
+  /**
+   * Remove all position liquidity and send a transaction
+   * @param params Contains signature callback, etc.
+   * @returns Promise<string> Transaction signature
+   */
+  public async decreaseFullLiquidity(params: ParamsWithSignerCallback<IDecreaseFullLiquidityParams>): Promise<string> {
+    const { signerCallback } = params;
+    const { transaction } = await this.decreaseFullLiquidityInstructions(params);
+    return sendTransaction({
+      connection: this.connection,
+      signTx: () => signerCallback(transaction),
+    });
+  }
+
+  /**
+   * Collect fees for a single position (essentially removing 0 liquidity)
+   * @param params Contains user, position, etc.
+   * @returns IInstructionReturn
+   */
+  public async collectFeesInstructions(params: ICollectFeesParams): Promise<IInstructionReturn> {
+    const { userAddress, nftMint } = params;
+    // Reuse the decreaseLiquidity function, pass in liquidity as 0
+    return await this.decreaseLiquidityInstructions({
+      userAddress,
+      nftMint,
+      liquidity: new BN(0),
+    });
+  }
+
+  /**
+   * Collect fees for all positions of a user, automatically batch to avoid exceeding transaction size limit
+   * @param params.userAddress User wallet address
+   * @param params.nftMintList NFT mint list
+   * @returns { instructionsList, transactions } Batch instructions and transaction objects
+   */
+  public async collectAllPositionFeesInstructions(params: ICollectAllFeesParams): Promise<{
+    instructionsList: TransactionInstruction[][];
+    transactions: VersionedTransaction[];
+  }> {
+    const { userAddress, nftMintList } = params;
+    // Cache pool information to avoid duplicate requests
+    const poolInfoMap: Map<string, IPoolLayoutWithId> = new Map();
+    const allInstructions: TransactionInstruction[][] = [];
+    // let currentInstructions: TransactionInstruction[] = [];
+    // Get rent exemption lamports
+    const rentExemptLamports = await this.estimateRentFee(AccountLayout.span);
+    for (const nftMint of nftMintList) {
+      try {
+        const positionInfo = await this.getRawPositionInfoByNftMint(nftMint);
+        if (!positionInfo) throw new Error(`Position not found: ${nftMint.toBase58()}`);
+        const poolId = positionInfo.poolId.toBase58();
+        // Get or cache pool information
+        if (!poolInfoMap.has(poolId)) {
+          poolInfoMap.set(poolId, await this.getRawPoolInfoByPoolId(positionInfo.poolId));
+        }
+        const poolInfo = poolInfoMap.get(poolId);
+        if (!poolInfo) throw new Error(`Pool not found: ${poolId}`);
+        // Handle SOL/WSOL related
+        const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
+          userAddress,
+          mintA: poolInfo.mintA,
+          mintB: poolInfo.mintB,
+          rentExemptLamports,
+        });
+        // Generate instructions to collect fees (essentially removing 0 liquidity)
+        const { instructions: decreaseInstructions } = await Instruction.decreaseLiquidityInstructions({
+          poolInfo,
+          ownerPosition: positionInfo,
+          ownerInfo: {
+            wallet: userAddress,
+            tokenAccountA,
+            tokenAccountB,
+          },
+          liquidity: new BN(0),
+          amountMinA: new BN(0),
+          amountMinB: new BN(0),
+        });
+        // All instructions for the current NFT
+        const nftInstructions = [...preInstructions, ...decreaseInstructions, ...endInstructions];
+
+        // Check if adding this NFT's instructions will exceed the transaction size limit
+        // const tempInstructions = [...currentInstructions, ...nftInstructions];
+        // const isValidSize = checkV0TxSize({
+        //   instructions: tempInstructions,
+        //   payer: userAddress,
+        // });
+        // if (!isValidSize && currentInstructions.length > 0) {
+        // If adding will exceed the limit and there are existing instructions, save the current batch and start a new batch
+        allInstructions.push(nftInstructions);
+        // currentInstructions = nftInstructions;
+        // } else {
+        //   // If it doesn't exceed the limit or the current batch is empty, add to the current batch
+        //   currentInstructions.push(...nftInstructions);
+        // }
+      } catch (error) {
+        console.error('[collectAllPositionFeesInstructions] Collect position fees failed:', {
+          nftMint: nftMint.toBase58(),
+          error,
+        });
+      }
+    }
+    // Ensure the last batch of instructions is also added
+    // if (currentInstructions.length > 0) {
+    //   allInstructions.push(currentInstructions);
+    // }
+    // Create a transaction for each batch of instructions
+    const transactions: VersionedTransaction[] = [];
+    for (const instructions of allInstructions) {
+      try {
+        const transaction = await makeTransaction({
+          connection: this.connection,
+          payerPublicKey: userAddress,
+          instructions,
+        });
+        transactions.push(transaction);
+      } catch (error) {
+        console.error('[collectAllPositionFeesInstructions] Create transaction failed:', error);
+      }
+    }
+    return { instructionsList: allInstructions, transactions };
+  }
+
+  /**
+   * Collect fees for a single position and send a transaction
+   * @param params Contains signature callback, etc.
+   * @returns Promise<string> Transaction signature
+   */
+  public async collectFees(params: ParamsWithSignerCallback<ICollectFeesParams>): Promise<string> {
+    try {
+      const { signerCallback } = params;
+      const { transaction } = await this.collectFeesInstructions(params);
+      return await sendTransaction({
+        connection: this.connection,
+        signTx: async () => await signerCallback(transaction),
+      });
+    } catch (error) {
+      console.warn('collectFees failed:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * Add liquidity to an existing position, generate chain instructions
+   * @param params Contains user, position, liquidity amount, etc.
+   * @returns IInstructionReturn
+   */
+  public async addLiquidityInstructions(params: IAddLiquidityParams): Promise<IInstructionReturn> {
+    const { userAddress, nftMint, base, baseAmount, otherAmountMax, computeBudgetOptions = {} } = params;
+    // Get position raw information
+    const ownerPosition = await this.getRawPositionInfoByNftMint(nftMint);
+    if (!ownerPosition) throw new Error('Position not found');
+    // Get pool information
+    const poolInfo = await this.getRawPoolInfoByPoolId(ownerPosition.poolId);
+    // Calculate the required amount
+    const amountA = base === 'MintA' ? baseAmount : otherAmountMax;
+    const amountB = base === 'MintB' ? baseAmount : otherAmountMax;
+    // Handle SOL/WSOL packaging
+    const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
+      userAddress,
+      mintA: poolInfo.mintA,
+      mintB: poolInfo.mintB,
+      amountA,
+      amountB,
+    });
+    // Generate add liquidity instructions
+    const { instructions: increaseInstructions } = await Instruction.increasePositionFromBaseInstructions({
+      poolInfo,
+      ownerPosition,
+      ownerInfo: {
+        wallet: userAddress,
+        tokenAccountA,
+        tokenAccountB,
+      },
+      base,
+      baseAmount,
+      otherAmountMax,
+    });
+    // Merge all instructions
+    const instructions = [...preInstructions, ...increaseInstructions, ...endInstructions];
+    // Construct transaction object
+    const transaction = await makeTransaction({
+      connection: this.connection,
+      payerPublicKey: userAddress,
+      instructions,
+      options: {
+        ...computeBudgetOptions,
+      },
+    });
+    return {
+      instructions,
+      transaction,
+    };
+  }
+
+  /**
+   * Add liquidity to an existing position and send a transaction
+   * @param params Contains signature callback, etc.
+   * @returns Promise<string> Transaction signature
+   */
+  public async addLiquidity(params: ParamsWithSignerCallback<IAddLiquidityParams>): Promise<string> {
+    const { signerCallback } = params;
+    const { transaction } = await this.addLiquidityInstructions(params);
+    return sendTransaction({
+      connection: this.connection,
+      signTx: () => signerCallback(transaction),
+    });
+  }
+
+  /**
+   * Create pool instructions
+   * @param params Create pool parameters
+   * @returns IInstructionReturn
+   */
+  public async createPoolInstructions(params: ICreatePoolParams): Promise<IInstructionReturn> {
+    const { userAddress, poolManager, openTime, mintA, mintB, ammConfigId, initialPrice } = params;
+    // Convert price to BN format
+    const initialPriceDecimal = new Decimal(initialPrice);
+    const initialPriceX64 = SqrtPriceMath.priceToSqrtPriceX64(initialPriceDecimal, mintA.decimals, mintB.decimals);
+    const mintAAddress = new PublicKey(mintA.address);
+    const mintBAddress = new PublicKey(mintB.address);
+    // Handle Token-2022 extension account
+    const extendMintAccount: PublicKey[] = [];
+    const fetchAccounts: PublicKey[] = [];
+    if (mintA.programId === TOKEN_2022_PROGRAM_ID.toBase58()) {
+      fetchAccounts.push(getPdaMintExAccount(this.programId, mintAAddress).publicKey);
+    }
+    if (mintB.programId === TOKEN_2022_PROGRAM_ID.toBase58()) {
+      fetchAccounts.push(getPdaMintExAccount(this.programId, mintBAddress).publicKey);
+    }
+
+    // Verify account existence
+    if (fetchAccounts.length > 0) {
+      const extMintRes = await this.connection.getMultipleAccountsInfo(fetchAccounts);
+      extMintRes.forEach((r, idx) => {
+        if (r) extendMintAccount.push(fetchAccounts[idx]);
+      });
+    }
+
+    // Generate pool creation instructions
+    const { instructions } = await Instruction.createPoolInstruction({
+      programId: this.programId,
+      owner: userAddress,
+      poolManager,
+      mintA,
+      mintB,
+      ammConfigId,
+      initialPriceX64,
+      openTime,
+      extendMintAccount,
+    });
+    // Construct transaction object
+    const transaction = await makeTransaction({
+      connection: this.connection,
+      payerPublicKey: userAddress,
+      instructions,
+    });
+    return {
+      instructions,
+      transaction,
+    };
+  }
+
+  /**
+   * Create a new pool and send a transaction
+   * @param params Contains signature callback, etc.
+   * @returns Promise<string> Transaction signature
+   */
+  public async createPool(params: ParamsWithSignerCallback<ICreatePoolParams>): Promise<string> {
+    const { signerCallback } = params;
+    const { transaction } = await this.createPoolInstructions(params);
+    return sendTransaction({
+      connection: this.connection,
+      signTx: () => signerCallback(transaction),
+    });
+  }
+
+  public async qouteSwap(params: IQouteSwapParams): Promise<IQouteSwapReturn> {
+    // Slippage is set to 2% by default
+    const {
+      poolInfo,
+      inputTokenMint,
+      amountIn,
+      priceLimit = new Decimal(0),
+      slippage = 0.02,
+      catchLiquidityInsufficient,
+    } = params;
+
+    let sqrtPriceLimitX64: BN;
+    const isInputMintA = inputTokenMint.toBase58() === poolInfo.mintA.toBase58();
+
+    // TODO: Consider fee calculation for token2022 in the future
+
+    if (priceLimit.equals(new Decimal(0))) {
+      sqrtPriceLimitX64 = isInputMintA ? MIN_SQRT_PRICE_X64.add(new BN(1)) : MAX_SQRT_PRICE_X64.sub(new BN(1));
+    } else {
+      sqrtPriceLimitX64 = SqrtPriceMath.priceToSqrtPriceX64(priceLimit, poolInfo.mintDecimalsA, poolInfo.mintDecimalsB);
+    }
+
+    const exBitmapInfo = await getTickArrayBitmapExtension(this.programId, poolInfo.poolId, this.connection);
+
+    const ammConfig = await RawDataUtils.getRawAmmConfigByConfigId({
+      connection: this.connection,
+      configId: poolInfo.ammConfig,
+    });
+
+    const tickArrayInfo = await getTickArrayInfo({
+      connection: this.connection,
+      poolInfo,
+      exBitmapInfo,
+    });
+
+    if (!exBitmapInfo || !ammConfig) throw new Error('Failed to get tick array bitmap extension or amm config');
+
+    const { allTrade, expectedAmountOut, remainingAccounts, executionPrice, feeAmount } =
+      await PoolUtils.getOutputAmountAndRemainAccounts({
+        poolInfo,
+        exBitmapInfo,
+        ammConfig,
+        tickArrayInfo,
+        inputTokenMint,
+        inputAmount: amountIn,
+        sqrtPriceLimitX64,
+        catchLiquidityInsufficient,
+      });
+
+    const minAmountOut = expectedAmountOut
+      .mul(new BN(Math.floor((1 - slippage) * 10000000000)))
+      .div(new BN(10000000000));
+
+    return {
+      allTrade,
+      isInputMintA,
+      amountIn,
+      expectedAmountOut,
+      minAmountOut,
+      remainingAccounts,
+      executionPrice,
+      feeAmount,
+    };
+  }
+
+  public async swapInstructions(params: ISwapParams): Promise<IInstructionReturn> {
+    const { poolInfo, quoteReturn, userAddress } = params;
+
+    // Determine amounts based on which token is input
+    const amountA = quoteReturn.isInputMintA ? quoteReturn.amountIn : new BN(0);
+    const amountB = !quoteReturn.isInputMintA ? quoteReturn.amountIn : new BN(0);
+
+    const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
+      userAddress,
+      mintA: poolInfo.mintA,
+      mintB: poolInfo.mintB,
+      amountA,
+      amountB,
+    });
+
+    const exBitmapAddress = getPdaExBitmapAccount(poolInfo.programId, poolInfo.poolId).publicKey;
+
+    // quoteReturn.isBaseIn
+    const { instructions: swapInstruction } = await Instruction.swapBaseInInstruction({
+      poolInfo,
+      ownerInfo: {
+        wallet: userAddress,
+        tokenAccountA,
+        tokenAccountB,
+      },
+      amount: quoteReturn.amountIn,
+      // The minimum output amount after slippage calculation is passed here
+      otherAmountThreshold: quoteReturn.minAmountOut,
+      sqrtPriceLimitX64: quoteReturn.executionPrice,
+      isInputMintA: quoteReturn.isInputMintA,
+      tickArray: quoteReturn.remainingAccounts,
+      exTickArrayBitmap: exBitmapAddress,
+    });
+
+    const instructions = [...preInstructions, ...swapInstruction, ...endInstructions];
+
+    const transaction = await makeTransaction({
+      connection: this.connection,
+      payerPublicKey: userAddress,
+      instructions,
+    });
+
+    return {
+      transaction,
+      instructions,
+    };
+  }
+
+  /**
+   * Create swap exact out instructions
+   * @param params Contains pool info, quote return, and user address
+   * @returns IInstructionReturn
+   */
+  public async swapExactOutInstructions(params: {
+    poolInfo: IPoolLayoutWithId;
+    quoteReturn: IQuoteSwapExactOutReturn;
+    userAddress: PublicKey;
+  }): Promise<IInstructionReturn> {
+    const { poolInfo, quoteReturn, userAddress } = params;
+
+    // For exact output, we need to determine input amounts
+    // If outputting tokenA, we input tokenB
+    // If outputting tokenB, we input tokenA
+    const isInputMintA = !quoteReturn.isOutputMintA;
+    const amountA = isInputMintA ? quoteReturn.maxAmountIn : new BN(0);
+    const amountB = !isInputMintA ? quoteReturn.maxAmountIn : new BN(0);
+
+    const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
+      userAddress,
+      mintA: poolInfo.mintA,
+      mintB: poolInfo.mintB,
+      amountA,
+      amountB,
+    });
+
+    const exBitmapAddress = getPdaExBitmapAccount(poolInfo.programId, poolInfo.poolId).publicKey;
+
+    // For exact output, we need to use swapBaseOutInstruction
+    const { instructions: swapInstruction } = await Instruction.swapBaseOutInstruction({
+      poolInfo,
+      ownerInfo: {
+        wallet: userAddress,
+        tokenAccountA,
+        tokenAccountB,
+      },
+      amount: quoteReturn.amountOut,
+      // The maximum input amount (including slippage) is passed here
+      otherAmountThreshold: quoteReturn.maxAmountIn,
+      sqrtPriceLimitX64: quoteReturn.executionPrice,
+      isOutputMintA: quoteReturn.isOutputMintA,
+      tickArray: quoteReturn.remainingAccounts,
+      exTickArrayBitmap: exBitmapAddress,
+    });
+
+    const instructions = [...preInstructions, ...swapInstruction, ...endInstructions];
+
+    const transaction = await makeTransaction({
+      connection: this.connection,
+      payerPublicKey: userAddress,
+      instructions,
+    });
+
+    return {
+      transaction,
+      instructions,
+    };
+  }
+
+  public async swap(params: ParamsWithSignerCallback<ISwapParams>): Promise<string> {
+    const { signerCallback } = params;
+    const { transaction } = await this.swapInstructions(params);
+    return sendTransaction({
+      connection: this.connection,
+      signTx: () => signerCallback(transaction),
+    });
+  }
+
+  /**
+   * Execute swap exact out transaction
+   * @param params Contains signature callback, etc.
+   * @returns Promise<string> Transaction signature
+   */
+  public async swapExactOut(params: ParamsWithSignerCallback<ISwapExactOutParams>): Promise<string> {
+    const { signerCallback } = params;
+    const { transaction } = await this.swapExactOutInstructions(params);
+    return sendTransaction({
+      connection: this.connection,
+      signTx: () => signerCallback(transaction),
+    });
+  }
+
+  /**
+   * Quote swap exact output - calculate required input amount for desired output
+   * @param params Quote parameters including output amount and slippage
+   * @returns Quote result with expected input amount and other swap details
+   */
+  public async quoteSwapExactOut(params: IQuoteSwapExactOutParams): Promise<IQuoteSwapExactOutReturn> {
+    const {
+      poolInfo,
+      outputTokenMint,
+      amountOut,
+      priceLimit = new Decimal(0),
+      slippage = 0.02,
+      catchLiquidityInsufficient,
+    } = params;
+
+    let sqrtPriceLimitX64: BN;
+    const isOutputMintA = outputTokenMint.toBase58() === poolInfo.mintA.toBase58();
+
+    // For exact output, we need to determine if we're inputting token A or B
+    // If outputting A, we input B (zeroForOne = false)
+    // If outputting B, we input A (zeroForOne = true)
+    const zeroForOne = !isOutputMintA;
+
+    if (priceLimit.equals(new Decimal(0))) {
+      sqrtPriceLimitX64 = zeroForOne ? MIN_SQRT_PRICE_X64.add(new BN(1)) : MAX_SQRT_PRICE_X64.sub(new BN(1));
+    } else {
+      sqrtPriceLimitX64 = SqrtPriceMath.priceToSqrtPriceX64(priceLimit, poolInfo.mintDecimalsA, poolInfo.mintDecimalsB);
+    }
+
+    const exBitmapInfo = await getTickArrayBitmapExtension(this.programId, poolInfo.poolId, this.connection);
+
+    const ammConfig = await RawDataUtils.getRawAmmConfigByConfigId({
+      connection: this.connection,
+      configId: poolInfo.ammConfig,
+    });
+
+    const tickArrayInfo = await getTickArrayInfo({
+      connection: this.connection,
+      poolInfo,
+      exBitmapInfo,
+    });
+
+    if (!exBitmapInfo || !ammConfig) throw new Error('Failed to get tick array bitmap extension or amm config');
+
+    const { allTrade, expectedAmountIn, remainingAccounts, executionPrice, feeAmount } =
+      await PoolUtils.getInputAmountAndRemainAccounts({
+        poolInfo,
+        exBitmapInfo,
+        ammConfig,
+        tickArrayInfo,
+        outputTokenMint,
+        outputAmount: amountOut,
+        sqrtPriceLimitX64,
+        catchLiquidityInsufficient,
+      });
+
+    // For exact output, we calculate max input amount with slippage
+    // This is the maximum amount user is willing to pay
+    const maxAmountIn = expectedAmountIn.mul(new BN(Math.ceil((1 + slippage) * 10000000000))).div(new BN(10000000000));
+
+    return {
+      allTrade,
+      isOutputMintA,
+      amountOut,
+      expectedAmountIn,
+      maxAmountIn,
+      remainingAccounts,
+      executionPrice,
+      feeAmount,
+    };
+  }
+
+  /**
+   * Handle SOL/WSOL packaging logic, automatically generate related instructions
+   *
+   * @param params.userAddress User wallet address
+   * @param params.mintA Pool tokenA mint
+   * @param params.mintB Pool tokenB mint
+   * @param params.amountA Optional, tokenA quantity
+   * @param params.amountB Optional, tokenB quantity
+   * @param params.rentExemptLamports Optional, WSOL account rent exemption lamports
+   * @returns tokenAccountA/B, pre-instructions, post-instructions
+   */
+  private async handleTokenAccount(params: {
+    userAddress: PublicKey;
+    mintA: PublicKey;
+    mintB: PublicKey;
+    amountA?: BN;
+    amountB?: BN;
+    rentExemptLamports?: number;
+  }): Promise<{
+    tokenAccountA: PublicKey;
+    tokenAccountB: PublicKey;
+    tokenProgramIdA: PublicKey;
+    tokenProgramIdB: PublicKey;
+    preInstructions: TransactionInstruction[];
+    endInstructions: TransactionInstruction[];
+  }> {
+    const { userAddress, mintA, mintB, amountA, amountB } = params;
+    // Check if there is SOL involved
+    const isTokenASOL = mintA.toString() === NATIVE_MINT.toString();
+    const isTokenBSOL = mintB.toString() === NATIVE_MINT.toString();
+    // Default to use ATA account
+    const tokenProgramIdA = await getTokenProgramId(this.connection, mintA);
+    const tokenProgramIdB = await getTokenProgramId(this.connection, mintB);
+    let tokenAccountA = getATAAddress(userAddress, mintA, tokenProgramIdA).publicKey;
+    let tokenAccountB = getATAAddress(userAddress, mintB, tokenProgramIdB).publicKey;
+    const preInstructions: TransactionInstruction[] = [];
+    const endInstructions: TransactionInstruction[] = [];
+
+    if (!isTokenASOL) {
+      const accA = await this.connection.getAccountInfo(tokenAccountA);
+      if (!accA) {
+        preInstructions.push(
+          createAssociatedTokenAccountIdempotentInstruction(
+            userAddress,
+            tokenAccountA,
+            userAddress,
+            mintA,
+            tokenProgramIdA
+          )
+        );
+      }
+    }
+
+    if (!isTokenBSOL) {
+      const accB = await this.connection.getAccountInfo(tokenAccountB);
+      if (!accB) {
+        preInstructions.push(
+          createAssociatedTokenAccountIdempotentInstruction(
+            userAddress,
+            tokenAccountB,
+            userAddress,
+            mintB,
+            tokenProgramIdB
+          )
+        );
+      }
+    }
+
+    if (!isTokenASOL && !isTokenBSOL) {
+      return { tokenAccountA, tokenAccountB, preInstructions, endInstructions, tokenProgramIdA, tokenProgramIdB };
+    }
+    // Handle SOL -> WSOL packaging
+    if (isTokenASOL || isTokenBSOL) {
+      const newAccount = generatePubKey({
+        fromPublicKey: userAddress,
+        programId: TOKEN_PROGRAM_ID,
+      });
+      const wsolAccount = newAccount.publicKey;
+      const rentExemptLamports = params.rentExemptLamports || (await this.estimateRentFee(AccountLayout.span));
+      // Calculate how much SOL is needed
+      let amountNeeded = 0;
+      if (isTokenASOL && amountA) {
+        amountNeeded = amountA.toNumber();
+      } else if (isTokenBSOL && amountB) {
+        amountNeeded = amountB.toNumber();
+      }
+
+      // Create WSOL account
+      preInstructions.push(
+        SystemProgram.createAccountWithSeed({
+          fromPubkey: userAddress,
+          basePubkey: userAddress,
+          seed: newAccount.seed,
+          newAccountPubkey: wsolAccount,
+          space: AccountLayout.span,
+          lamports: rentExemptLamports + amountNeeded,
+          programId: TOKEN_PROGRAM_ID,
+        })
+      );
+      // Initialize WSOL account
+      preInstructions.push(createInitializeAccountInstruction(wsolAccount, NATIVE_MINT, userAddress));
+      // Add instructions to close WSOL account
+      endInstructions.push(createCloseAccountInstruction(wsolAccount, userAddress, userAddress, []));
+      // Update the corresponding token account
+      if (isTokenASOL) {
+        tokenAccountA = wsolAccount;
+      }
+      if (isTokenBSOL) {
+        tokenAccountB = wsolAccount;
+      }
+    }
+    return { tokenAccountA, tokenAccountB, preInstructions, endInstructions, tokenProgramIdA, tokenProgramIdB };
+  }
+
+  private async estimateRentFee(space: number, useCache = true): Promise<number> {
+    if (useCache && this.rentFeeCache[space] !== undefined) {
+      return this.rentFeeCache[space];
+    }
+    const lamports = await this.connection.getMinimumBalanceForRentExemption(space);
+    this.rentFeeCache[space] = lamports;
+    return lamports;
+  }
+
+  public calculateApr = calculateApr;
+  public calculateRewardApr = calculateRewardApr;
+  public calculateRangeAprs = calculateRangeAprs;
+  public alignPriceToTickPrice = alignPriceToTickPrice;
+  public getAmountBFromAmountA = getAmountBFromAmountA;
+  public getAmountAFromAmountB = getAmountAFromAmountB;
+}

+ 178 - 0
src/lib/clmm-sdk/src/client/chain/models.ts

@@ -0,0 +1,178 @@
+import { PublicKey, Signer, TransactionInstruction, VersionedTransaction } from '@solana/web3.js';
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import {
+  IPersonalPositionLayout,
+  IPoolLayoutWithId,
+  ITokenInfo,
+  TickArrayBitmapExtensionType,
+} from '../../instructions/index.js';
+import { IMakeTransactionOptions } from '../../utils/index.js';
+
+export type SignerCallback = (transaction: VersionedTransaction) => Promise<VersionedTransaction>;
+
+export type ParamsWithSignerCallback<T> = T & {
+  signerCallback: SignerCallback;
+};
+
+export interface IInstructionReturn {
+  instructions: TransactionInstruction[];
+  signers?: Signer[];
+  transaction: VersionedTransaction;
+  nftAddress?: string;
+}
+
+export interface ICreatePositionParams {
+  userAddress: PublicKey;
+  poolInfo: IPoolLayoutWithId;
+  tickLower: number;
+  tickUpper: number;
+  base: 'MintA' | 'MintB';
+  baseAmount: BN;
+  otherAmountMax: BN;
+  transactionOptions?: IMakeTransactionOptions;
+  refererPosition?: PublicKey;
+}
+
+export interface IClosePositionParams {
+  userAddress: PublicKey;
+  nftMint: PublicKey;
+}
+
+export interface IDecreaseLiquidityParams {
+  userAddress: PublicKey;
+  nftMint: PublicKey;
+  liquidity: BN;
+  slippage?: number;
+}
+
+export interface IDecreaseFullLiquidityParams {
+  userAddress: PublicKey;
+  nftMint: PublicKey;
+  closePosition?: boolean;
+  slippage?: number;
+}
+
+export interface IAddLiquidityParams {
+  userAddress: PublicKey;
+  nftMint: PublicKey;
+  base: 'MintA' | 'MintB';
+  baseAmount: BN;
+  otherAmountMax: BN;
+  computeBudgetOptions?: IComputeBudgetOptions;
+}
+
+export interface ICreatePoolParams {
+  userAddress: PublicKey;
+  poolManager: PublicKey;
+  mintA: ITokenInfo;
+  mintB: ITokenInfo;
+  ammConfigId: PublicKey;
+  initialPrice: number;
+  openTime?: BN;
+  computeBudgetOptions?: IComputeBudgetOptions;
+}
+
+export interface ICollectFeesParams {
+  userAddress: PublicKey;
+  nftMint: PublicKey;
+}
+
+export interface ICollectAllFeesParams {
+  userAddress: PublicKey;
+  nftMintList: PublicKey[];
+}
+
+export interface IGetPositionInfoByNftMintReturn {
+  priceLower: Decimal;
+  priceUpper: Decimal;
+  uiPriceLower: string;
+  uiPriceUpper: string;
+  tokenA: {
+    address: PublicKey;
+    decimals: number;
+    amount: BN;
+    feeAmount: BN;
+    uiAmount: string;
+    uiFeeAmount: string;
+  };
+  tokenB: {
+    address: PublicKey;
+    decimals: number;
+    amount: BN;
+    feeAmount: BN;
+    uiAmount: string;
+    uiFeeAmount: string;
+  };
+  rawPositionInfo: IPersonalPositionLayout;
+  rawPoolInfo: IPoolLayoutWithId;
+}
+
+export interface ICalculateCreatePositionFee {
+  transactionNetFee: number; // Transaction fee
+  refundableFees: number; // Refundable fees (NFT minting fee + NFT holder fee + personal position fee)
+  unRefundableFees: number; // Unrefundable fees (Newly created tick array account + transaction fee)
+
+  createTickFee: number; // Create tick array account fee
+}
+
+/**
+ * maxFee and exactFee are mutually exclusive
+ */
+export type IComputeBudgetOptions =
+  | { maxFee: number; exactFee?: never; computeUnitPrice?: number }
+  | { maxFee?: never; exactFee: number; computeUnitPrice?: never }
+  | { maxFee?: undefined; exactFee?: undefined; computeUnitPrice?: number };
+
+export interface IQouteSwapParams {
+  poolInfo: IPoolLayoutWithId;
+  inputTokenMint: PublicKey;
+  amountIn: BN;
+  slippage?: number;
+  priceLimit?: Decimal;
+  catchLiquidityInsufficient?: boolean;
+}
+
+export interface IQouteSwapReturn {
+  allTrade: boolean;
+  amountIn: BN;
+  isInputMintA: boolean;
+  expectedAmountOut: BN;
+  minAmountOut: BN;
+  remainingAccounts: PublicKey[];
+  executionPrice: BN;
+  feeAmount: BN;
+}
+
+export interface ISwapParams {
+  poolInfo: IPoolLayoutWithId;
+  userAddress: PublicKey;
+  quoteReturn: IQouteSwapReturn;
+}
+
+export interface ISwapExactOutParams {
+  poolInfo: IPoolLayoutWithId;
+  userAddress: PublicKey;
+  quoteReturn: IQuoteSwapExactOutReturn;
+}
+
+export interface IQuoteSwapExactOutParams {
+  poolInfo: IPoolLayoutWithId;
+  outputTokenMint: PublicKey;
+  amountOut: BN;
+  slippage?: number;
+  priceLimit?: Decimal;
+  catchLiquidityInsufficient?: boolean;
+}
+
+export interface IQuoteSwapExactOutReturn {
+  allTrade: boolean;
+  amountOut: BN;
+  isOutputMintA: boolean;
+  expectedAmountIn: BN;
+  maxAmountIn: BN;
+  remainingAccounts: PublicKey[];
+  executionPrice: BN;
+  feeAmount: BN;
+}

+ 408 - 0
src/lib/clmm-sdk/src/client/chain/utils.ts

@@ -0,0 +1,408 @@
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import {
+  IPoolLayout,
+  IPoolLayoutWithId,
+  LiquidityMath,
+  PoolUtils,
+  SqrtPriceMath,
+  TickMath,
+} from '../../instructions/index.js';
+import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token';
+import { Connection, PublicKey } from '@solana/web3.js';
+
+/**
+ * Used to round the price to the corresponding tick when creating a position
+ * @param price Input price
+ * @param poolInfo Pool information
+ * @returns Decimal rounded price
+ */
+export function alignPriceToTickPrice(price: Decimal, poolInfo: IPoolLayout): Decimal {
+  const { price: roundedPrice } = TickMath.getTickAlignedPriceDetails(
+    price,
+    poolInfo.tickSpacing,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+  return roundedPrice;
+}
+
+/**
+ * Calculate the amount of tokenB needed to be invested after the specified tokenA amount has been invested
+ * @param params.priceLower Lower price
+ * @param params.priceUpper Upper price
+ * @param params.amountA Amount of tokenA to be invested
+ * @param params.poolInfo Pool information
+ * @returns BN amount of tokenB to be invested
+ */
+export function getAmountBFromAmountA(params: {
+  priceLower: Decimal | number | string;
+  priceUpper: Decimal | number | string;
+  amountA: BN;
+  poolInfo: IPoolLayout;
+}): BN {
+  // console.log('[clmm sdk] getAmountBFromAmountA fn params:', JSON.stringify(params, null, 2));
+  const { priceLower, priceUpper, amountA, poolInfo } = params;
+
+  const priceLowerDecimal = alignPriceToTickPrice(new Decimal(priceLower), poolInfo);
+  const priceUpperDecimal = alignPriceToTickPrice(new Decimal(priceUpper), poolInfo);
+
+  const sqrtPriceX64A = SqrtPriceMath.priceToSqrtPriceX64(
+    priceLowerDecimal,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+  const sqrtPriceX64B = SqrtPriceMath.priceToSqrtPriceX64(
+    priceUpperDecimal,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+
+  const amountB = LiquidityMath.getAmountBFromAmountA(sqrtPriceX64A, sqrtPriceX64B, poolInfo.sqrtPriceX64, amountA);
+
+  return amountB;
+}
+
+/**
+ * Calculate the amount of tokenA needed to be invested after the specified tokenB amount has been invested
+ * @param params.priceLower Lower price
+ * @param params.priceUpper Upper price
+ * @param params.amountB Amount of tokenB to be invested
+ * @param params.poolInfo Pool information
+ * @returns BN amount of tokenA to be invested
+ */
+export function getAmountAFromAmountB(params: {
+  priceLower: Decimal | number | string;
+  priceUpper: Decimal | number | string;
+  amountB: BN;
+  poolInfo: IPoolLayout;
+}): BN {
+  const { priceLower, priceUpper, amountB, poolInfo } = params;
+
+  const priceLowerDecimal = alignPriceToTickPrice(new Decimal(priceLower), poolInfo);
+  const priceUpperDecimal = alignPriceToTickPrice(new Decimal(priceUpper), poolInfo);
+
+  const sqrtPriceX64A = SqrtPriceMath.priceToSqrtPriceX64(
+    priceLowerDecimal,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+  const sqrtPriceX64B = SqrtPriceMath.priceToSqrtPriceX64(
+    priceUpperDecimal,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+
+  return LiquidityMath.getAmountAFromAmountB(sqrtPriceX64A, sqrtPriceX64B, poolInfo.sqrtPriceX64, amountB);
+}
+
+/**
+ * Calculate the expected annualized return rate of adding liquidity (APR)
+ */
+export function calculateApr(params: {
+  volume24h: number; // 24h volume in USD
+  feeRate: number; // Fee rate, e.g. 0.003 means 0.3%
+  positionUsdValue: number; // USD value of the tokens invested by the user
+  amountA: BN; // Amount of tokenA invested
+  amountB: BN; // Amount of tokenB invested
+  tickLower: number; // Tick corresponding to the lower price
+  tickUpper: number; // Tick corresponding to the upper price
+  poolInfo: IPoolLayoutWithId; // Pool information
+  existLiquidity?: BN; // Existing liquidity (required when adding liquidity)
+  scene?: 'create' | 'add' | 'exist'; // Calculation scenario, create, add, exist
+}): number {
+  // console.log('[clmm sdk] calculateApr fn params:', JSON.stringify(params, null, 2));
+  const {
+    volume24h,
+    feeRate,
+    positionUsdValue,
+    amountA,
+    amountB,
+    tickLower,
+    tickUpper,
+    poolInfo,
+    scene = 'exist',
+    existLiquidity = new BN(0),
+  } = params;
+
+  return _calculateApr({
+    fee24hUsdValue: volume24h * feeRate,
+    positionUsdValue,
+    amountA,
+    amountB,
+    tickLower,
+    tickUpper,
+    poolInfo,
+    scene,
+    existLiquidity,
+  });
+}
+
+/**
+ * Calculate the expected annualized return rate of reward
+ */
+export function calculateRewardApr(params: {
+  reward24hUsdValue: number; // Reward received in 24h
+  positionUsdValue: number; // USD value of the tokens invested by the user
+  amountA: BN; // Amount of tokenA invested
+  amountB: BN; // Amount of tokenB invested
+  tickLower: number; // Tick corresponding to the lower price
+  tickUpper: number; // Tick corresponding to the upper price
+  poolInfo: IPoolLayoutWithId; // Pool information
+  existLiquidity?: BN; // Existing liquidity (required when adding liquidity)
+  scene?: 'create' | 'add' | 'exist'; // Calculation scenario, create, add, exist
+}): number {
+  const {
+    reward24hUsdValue,
+    positionUsdValue,
+    amountA,
+    amountB,
+    tickLower,
+    tickUpper,
+    poolInfo,
+    scene = 'exist',
+    existLiquidity = new BN(0),
+  } = params;
+
+  return _calculateApr({
+    fee24hUsdValue: reward24hUsdValue,
+    positionUsdValue,
+    amountA,
+    amountB,
+    tickLower,
+    tickUpper,
+    poolInfo,
+    scene,
+    existLiquidity,
+  });
+}
+
+/**
+ * Calculate the expected annualized return rate of adding liquidity (APR)
+ *
+ */
+export function _calculateApr(params: {
+  fee24hUsdValue: number; // 24h fee or reward received
+  positionUsdValue: number; // USD value of the tokens invested by the user
+  amountA: BN; // Amount of tokenA invested
+  amountB: BN; // Amount of tokenB invested
+  tickLower: number; // Tick corresponding to the lower price
+  tickUpper: number; // Tick corresponding to the upper price
+  poolInfo: IPoolLayoutWithId; // Pool information
+  existLiquidity?: BN; // Existing liquidity (required when adding liquidity)
+  scene?: 'create' | 'add' | 'exist'; // Calculation scenario, create, add, exist
+}): number {
+  const {
+    fee24hUsdValue,
+    positionUsdValue,
+    amountA,
+    amountB,
+    tickLower,
+    tickUpper,
+    poolInfo,
+    scene = 'exist',
+    existLiquidity = new BN(0),
+  } = params;
+
+  // Get the active liquidity of the pool
+  const poolActiveLiquidity = poolInfo.liquidity;
+
+  // Calculate the sqrtPrice of the price range
+  const sqrtPriceCurrentX64 = poolInfo.sqrtPriceX64;
+  const sqrtPriceLowerX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tickLower);
+  const sqrtPriceUpperX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tickUpper);
+
+  // Calculate the active liquidity of the user
+  const userActiveLiquidity = LiquidityMath.getLiquidityFromTokenAmounts(
+    sqrtPriceCurrentX64,
+    sqrtPriceLowerX64,
+    sqrtPriceUpperX64,
+    amountA,
+    amountB
+  );
+
+  // Check if the current price is within the user's range
+  const isInRange = poolInfo.tickCurrent >= tickLower && poolInfo.tickCurrent < tickUpper;
+
+  // If not in range, active liquidity is 0
+  if (!isInRange) {
+    return 0;
+  }
+
+  if (poolActiveLiquidity.isZero()) {
+    return 0;
+  }
+
+  // Calculate the annualized return rate
+  // Formula source: https://uponly.larksuite.com/wiki/GznTwz3kGi3D0ikaLp8uDlYcsKd?fromScene=spaceOverview
+  // For creating liquidity: (Volume * FeeRate / Position) * (userAL / (AL + userAL)) * 365 * 100
+  // For adding liquidity: (Volume * FeeRate / Position) * ((userAL + addAL) / (AL + addAL)) * 365 * 100
+  // For existing positions: (Volume * FeeRate / Position) * (userAL / AL) * 365 * 100
+  const fee24hDec = new Decimal(fee24hUsdValue);
+  const positionUsdValueDec = new Decimal(positionUsdValue);
+  const userActiveLiquidityDec = new Decimal(userActiveLiquidity.toString());
+  const poolActiveLiquidityDec = new Decimal(poolActiveLiquidity.toString());
+  const existLiquidityDec = new Decimal(existLiquidity.toString());
+
+  // Daily return rate
+  const dailyReturn = fee24hDec.div(positionUsdValueDec);
+
+  // Calculate the liquidity share using Decimal
+  let liquidityShare: Decimal;
+  if (scene === 'create') {
+    liquidityShare = userActiveLiquidityDec.div(poolActiveLiquidityDec.plus(userActiveLiquidityDec));
+  } else if (scene === 'add') {
+    liquidityShare = existLiquidityDec
+      .plus(userActiveLiquidityDec)
+      .div(poolActiveLiquidityDec.plus(userActiveLiquidityDec));
+  } else {
+    liquidityShare = userActiveLiquidityDec.div(poolActiveLiquidityDec);
+  }
+
+  // Calculate the annualized return rate
+  const apr = dailyReturn.mul(liquidityShare).mul(365).mul(100).toNumber();
+
+  return apr;
+}
+
+/**
+ * Calculate the annualized return rate for different price ranges
+ * Calculate APR for different price ranges based on the percentage offset from the current price
+ *
+ * @param params Calculation parameters
+ * @returns Mapping of annualized return rates for different price ranges, using -1 to represent the full range
+ */
+export function calculateRangeAprs(params: {
+  percentRanges: number[]; // Price range percentage list, e.g. [1, 5, 10, 20, 50]
+  volume24h: number; // 24h volume in USD
+  feeRate: number; // Fee rate, e.g. 0.003 means 0.3%
+  tokenAPriceUsd: number; // USD value of tokenA
+  tokenBPriceUsd: number; // USD value of tokenB
+  poolInfo: IPoolLayoutWithId; // Pool information
+}): Record<number, number> {
+  const { percentRanges, volume24h, feeRate, tokenAPriceUsd, tokenBPriceUsd, poolInfo } = params;
+
+  // Get the current price, liquidity and sample liquidity
+  const currentPrice = TickMath.getPriceFromTick({
+    tick: poolInfo.tickCurrent,
+    decimalsA: poolInfo.mintDecimalsA,
+    decimalsB: poolInfo.mintDecimalsB,
+  });
+
+  const poolLiquidity = poolInfo.liquidity;
+  const _sampleLiquidity = poolLiquidity.div(new BN(10000));
+  const _minLiquidity = new BN(1000);
+  const liquidityToUse = _sampleLiquidity.lt(_minLiquidity) ? _minLiquidity : _sampleLiquidity;
+
+  // Result mapping
+  const result: Record<number, number> = {};
+
+  const { minTickBoundary, maxTickBoundary } = PoolUtils.tickRange(poolInfo.tickSpacing);
+
+  // Calculate APR for each range
+  for (const range of percentRanges) {
+    let tickLower: number;
+    let tickUpper: number;
+
+    if (range === -1) {
+      tickLower = minTickBoundary;
+      tickUpper = maxTickBoundary;
+    } else {
+      const lowerPriceRatio = new Decimal(1).minus(new Decimal(range).div(100));
+      const upperPriceRatio = new Decimal(1).plus(new Decimal(range).div(100));
+      const lowerPrice = currentPrice.mul(lowerPriceRatio);
+      const upperPrice = currentPrice.mul(upperPriceRatio);
+
+      tickLower = TickMath.getTickWithPriceAndTickspacing(
+        lowerPrice,
+        poolInfo.tickSpacing,
+        poolInfo.mintDecimalsA,
+        poolInfo.mintDecimalsB
+      );
+
+      tickUpper = TickMath.getTickWithPriceAndTickspacing(
+        upperPrice,
+        poolInfo.tickSpacing,
+        poolInfo.mintDecimalsA,
+        poolInfo.mintDecimalsB
+      );
+    }
+
+    // Ensure that lower and upper are at least 1 tickSpacing apart
+    if (tickLower >= tickUpper) {
+      tickLower = Math.max(minTickBoundary, poolInfo.tickCurrent - poolInfo.tickSpacing);
+      tickUpper = Math.min(maxTickBoundary, poolInfo.tickCurrent + poolInfo.tickSpacing);
+    }
+
+    // Calculate the square root of the price (X64 format)
+    const sqrtPriceCurrentX64 = poolInfo.sqrtPriceX64;
+    const sqrtPriceLowerX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tickLower);
+    const sqrtPriceUpperX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tickUpper);
+
+    // Calculate the corresponding token amounts based on the liquidity
+    const { amountA, amountB } = LiquidityMath.getAmountsFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceLowerX64,
+      sqrtPriceUpperX64,
+      liquidityToUse,
+      false // Do not round up
+    );
+
+    // Calculate the USD value of the tokens
+    const tokenAUsdAmount = new Decimal(amountA.toString())
+      .div(new Decimal(10).pow(poolInfo.mintDecimalsA))
+      .mul(tokenAPriceUsd);
+
+    const tokenBUsdAmount = new Decimal(amountB.toString())
+      .div(new Decimal(10).pow(poolInfo.mintDecimalsB))
+      .mul(tokenBPriceUsd);
+
+    // Calculate the total investment value
+    const positionUsdValue = tokenAUsdAmount.plus(tokenBUsdAmount);
+
+    // If the USD value is too small, set a minimum value to avoid division issues
+    const effectivePositionUsdValue = positionUsdValue.lt(0.01) ? new Decimal(0.01) : positionUsdValue;
+
+    // Calculate the actual annualized return rate
+    const apr = calculateApr({
+      volume24h,
+      feeRate,
+      positionUsdValue: effectivePositionUsdValue.toNumber(),
+      amountA,
+      amountB,
+      tickLower,
+      tickUpper,
+      poolInfo,
+      scene: 'create',
+    });
+
+    result[range] = apr;
+  }
+
+  return result;
+}
+
+/**
+ * 获取 mint 对应的 token program ID
+ */
+export async function getTokenProgramId(connection: Connection, mintAddress: PublicKey): Promise<PublicKey> {
+  try {
+    const mintAccountInfo = await connection.getAccountInfo(mintAddress);
+    if (!mintAccountInfo) {
+      throw new Error(`Mint account not found: ${mintAddress.toBase58()}`);
+    }
+
+    // 检查 mint 账户的 owner 来确定使用的是哪个 token program
+    if (mintAccountInfo.owner.equals(TOKEN_2022_PROGRAM_ID)) {
+      return TOKEN_2022_PROGRAM_ID;
+    } else if (mintAccountInfo.owner.equals(TOKEN_PROGRAM_ID)) {
+      return TOKEN_PROGRAM_ID;
+    } else {
+      throw new Error(`Unknown token program for mint: ${mintAddress.toBase58()}`);
+    }
+  } catch (error) {
+    console.warn(`Failed to get token program for ${mintAddress.toBase58()}, defaulting to TOKEN_PROGRAM_ID`);
+    return TOKEN_PROGRAM_ID;
+  }
+}

+ 84 - 0
src/lib/clmm-sdk/src/client/index.ts

@@ -0,0 +1,84 @@
+import { Connection, PublicKey } from '@solana/web3.js';
+
+import { BYREAL_CLMM_PROGRAM_ID } from '../constants.js';
+import { IPoolLayoutWithId } from '../instructions/models.js';
+
+import { Api, IApiParams } from './apis/index.js';
+import { IPoolsReq, IPoolsByIdsReq } from './apis/poolsModels.js';
+import { IMyPositionsReq } from './apis/positionModels.js';
+import { ITickReq } from './apis/tickModels.js';
+import { Chain } from './chain/index.js';
+import { ICollectFeesParams } from './chain/models.js';
+import { ParamsWithSignerCallback } from './chain/models.js';
+import { CLMMClient } from './liquidity/clmm.js';
+import { Token } from './token.js';
+
+export class SdkClient {
+  public api: Api;
+  public chain: Chain;
+  public clmmClient: CLMMClient;
+  public connection: Connection;
+  public programId: PublicKey;
+  public token: Token;
+
+  constructor(params: { connection: Connection; programId?: PublicKey; apiConfig?: IApiParams }) {
+    const { connection, programId = BYREAL_CLMM_PROGRAM_ID, apiConfig } = params;
+    this.connection = connection;
+    this.programId = programId;
+    this.api = new Api(apiConfig);
+    this.chain = new Chain({ connection, programId });
+    this.clmmClient = new CLMMClient({ chain: this.chain });
+    this.token = new Token(connection);
+  }
+
+  // Get pool list
+  async getPools(req: IPoolsReq) {
+    return this.api.getPools(req);
+  }
+
+  // Get pool details by ids
+  async getPoolsByIds(req: IPoolsByIdsReq) {
+    const result = await this.api.getPoolsByIds(req);
+
+    return result;
+  }
+
+  // Get user positions
+  async getMyPositions(req: IMyPositionsReq) {
+    return this.api.getMyPositions(req);
+  }
+
+  // Collect single position fees
+  collectFees(params: ParamsWithSignerCallback<ICollectFeesParams>) {
+    return this.chain.collectFees(params);
+  }
+
+  // Get tick information
+  async getTicks(req: ITickReq) {
+    return this.api.getTicks(req);
+  }
+
+  /**
+   *
+   * @param id Pool address
+   * @returns
+   */
+  async getRawPoolInfoByPoolId(id: string): Promise<IPoolLayoutWithId> {
+    return this.chain.getRawPoolInfoByPoolId(id);
+  }
+
+  async getPositionNftMintListByUserAddress(userAddress: PublicKey): Promise<PublicKey[]> {
+    const positionList = await this.chain.getRawPositionInfoListByUserAddress(userAddress);
+    return positionList.map((position) => position.nftMint);
+  }
+}
+
+export * from './chain/index.js';
+export * from './chain/models.js';
+export * from './apis/index.js';
+export * from './apis/ky.js';
+export * from './liquidity/clmm.js';
+export * from './apis/poolsModels.js';
+export * from './apis/swapModels.js';
+export * from './apis/positionModels.js';
+export * from './apis/tickModels.js';

+ 42 - 0
src/lib/clmm-sdk/src/client/liquidity/abstract.ts

@@ -0,0 +1,42 @@
+import { PublicKey } from '@solana/web3.js';
+
+import { Chain } from '../chain/index.js';
+import { IInstructionReturn } from '../chain/models.js';
+
+export abstract class AbstractLiquidityClient {
+  protected _chain: Chain;
+
+  constructor(params: { chain: Chain }) {
+    this._chain = params.chain;
+  }
+  /**
+   * Create a position
+   * @param opts
+   * @returns
+   */
+  abstract createPosition(opts: any): Promise<IInstructionReturn>;
+
+  /**
+   * Add liquidity
+   * @param userAddress
+   * @param nftMint
+   * @returns
+   */
+  abstract addLiquidity(opts: any): Promise<IInstructionReturn>;
+
+  /**
+   * Decrease liquidity
+   * @param walletAddress
+   * @param nftMint
+   * @param decreasePercentage
+   * @returns
+   */
+  abstract decreaseLiquidity(walletAddress: PublicKey, nftMint: string, decreasePercentage: number): Promise<any>;
+
+  /**
+   * Remove all liquidity
+   * @param userAddress
+   * @param nftMint
+   */
+  abstract removeFullLiquidity(userAddress: string, nftMint: string): Promise<any>;
+}

+ 76 - 0
src/lib/clmm-sdk/src/client/liquidity/clmm.ts

@@ -0,0 +1,76 @@
+import { PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+
+import { Chain } from '../chain/index.js';
+import { IAddLiquidityParams } from '../chain/models.js';
+
+import { AbstractLiquidityClient } from './abstract.js';
+import { ICLMMCreatePositionOptions } from './types.js';
+
+export class CLMMClient extends AbstractLiquidityClient {
+  constructor(params: { chain: Chain }) {
+    super(params);
+  }
+
+  /**
+   * Create a position
+   * @param options
+   * @returns
+   */
+  async createPosition(options: ICLMMCreatePositionOptions) {
+    return this._chain.createPositionInstructions({
+      userAddress: options.walletAddress,
+      poolInfo: options.rpcPoolInfo,
+      tickLower: options.tickLower,
+      tickUpper: options.tickUpper,
+      base: options.baseType,
+      baseAmount: options.baseTokenAmount,
+      otherAmountMax: options.quoteTokenAmount,
+    });
+  }
+
+  async addLiquidity(options: IAddLiquidityParams) {
+    return this._chain.addLiquidityInstructions(options);
+  }
+
+  /**
+   * Decrease liquidity
+   * @param nftMint
+   * @returns
+   */
+  async decreaseLiquidity(walletAddress: PublicKey, nftMint: string, decreasePercentage: number) {
+    const _nftMint = new PublicKey(nftMint);
+    const positionInfo = await this._chain.getRawPositionInfoByNftMint(_nftMint);
+
+    if (!positionInfo) {
+      throw new Error('Position not found');
+    }
+
+    const { liquidity } = positionInfo;
+    const liquidityToDecrease = liquidity.mul(new BN(decreasePercentage)).div(new BN(100));
+
+    const params = {
+      userAddress: walletAddress,
+      nftMint: _nftMint,
+      liquidity: liquidityToDecrease,
+    };
+
+    const { transaction } = await this._chain.decreaseLiquidityInstructions(params);
+    return transaction;
+  }
+
+  /**
+   * Remove all liquidity
+   * @param userAddress
+   * @param nftMint
+   */
+  async removeFullLiquidity(userAddress: string, nftMint: string) {
+    const _nftMint = new PublicKey(nftMint);
+
+    const { transaction } = await this._chain.decreaseFullLiquidityInstructions({
+      userAddress: new PublicKey(userAddress),
+      nftMint: _nftMint,
+    });
+    return transaction;
+  }
+}

+ 14 - 0
src/lib/clmm-sdk/src/client/liquidity/types.ts

@@ -0,0 +1,14 @@
+import { PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+
+import { IPoolLayoutWithId } from '../../instructions/index.js';
+
+export interface ICLMMCreatePositionOptions {
+  walletAddress: PublicKey;
+  rpcPoolInfo: IPoolLayoutWithId;
+  baseTokenAmount: BN;
+  quoteTokenAmount: BN;
+  tickLower: number;
+  tickUpper: number;
+  baseType: 'MintA' | 'MintB';
+}

+ 105 - 0
src/lib/clmm-sdk/src/client/token.ts

@@ -0,0 +1,105 @@
+import {
+  TOKEN_PROGRAM_ID,
+  TOKEN_2022_PROGRAM_ID,
+  getAssociatedTokenAddress,
+  getMint,
+  AccountLayout,
+} from '@solana/spl-token';
+import { Connection, PublicKey } from '@solana/web3.js';
+
+import { isToken2022 } from '../utils/token.js';
+
+export class Token {
+  private readonly _connection: Connection;
+
+  constructor(connection: Connection) {
+    this._connection = connection;
+  }
+
+  async isToken2022(mintAddress: string) {
+    return isToken2022(mintAddress, this._connection);
+  }
+
+  /**
+   * Detect token type and get balance
+   * @param walletAddress Wallet address
+   * @param tokenMintAddress Token Mint address
+   * @returns Promise object containing balance and whether it is Token-2022
+   */
+  async detectTokenTypeAndGetBalance(
+    walletAddress: string,
+    tokenMintAddress: string
+  ): Promise<{ balance: number; isToken2022: boolean }> {
+    const walletPublicKey = new PublicKey(walletAddress);
+    const mintPublicKey = new PublicKey(tokenMintAddress);
+
+    // Compatible with native SOL
+    if (tokenMintAddress === 'So11111111111111111111111111111111111111112') {
+      const accountInfo = await this._connection.getAccountInfo(walletPublicKey);
+      if (accountInfo) {
+        const balance = Number(accountInfo.lamports) / Math.pow(10, 9); // SOL精度固定9位
+        return { balance, isToken2022: false };
+      }
+      return { balance: 0, isToken2022: false };
+    }
+
+    // First try to query as Token-2022
+    try {
+      const mint = await getMint(this._connection, mintPublicKey, 'confirmed', TOKEN_2022_PROGRAM_ID);
+      const tokenAccount = await getAssociatedTokenAddress(
+        mintPublicKey,
+        walletPublicKey,
+        false,
+        TOKEN_2022_PROGRAM_ID
+      );
+
+      const accountInfo = await this._connection.getAccountInfo(tokenAccount);
+      if (accountInfo) {
+        // Parse account data to get balance
+        const data = AccountLayout.decode(accountInfo.data);
+        const balance = Number(data.amount) / Math.pow(10, mint.decimals);
+        return { balance, isToken2022: true };
+      }
+    } catch (error) {
+      // If it is not Token-2022, try to query as a standard SPL Token
+      console.log('Not a Token-2022, trying standard SPL Token...');
+    }
+
+    // Try to query as a standard SPL Token
+    try {
+      const mint = await getMint(this._connection, mintPublicKey, 'confirmed', TOKEN_PROGRAM_ID);
+      const tokenAccount = await getAssociatedTokenAddress(mintPublicKey, walletPublicKey, false, TOKEN_PROGRAM_ID);
+
+      const accountInfo = await this._connection.getAccountInfo(tokenAccount);
+      if (accountInfo) {
+        const data = AccountLayout.decode(accountInfo.data);
+        const balance = Number(data.amount) / Math.pow(10, mint.decimals);
+        return { balance, isToken2022: false };
+      }
+    } catch (error) {
+      console.error('Error detecting token type:', error);
+    }
+
+    return { balance: 0, isToken2022: false };
+  }
+
+  /**
+   * Batch detect token type and get balance
+   * @param walletAddress Wallet address
+   * @param tokenMintAddresses Token Mint address array
+   * @returns Promise<{ tokenMintAddress: string; balance: number; isToken2022: boolean }[]>
+   */
+  async batchDetectTokenTypeAndGetBalance(
+    walletAddress: string,
+    tokenMintAddresses: string[]
+  ): Promise<{ tokenMintAddress: string; balance: number; isToken2022: boolean }[]> {
+    // Concurrent processing of all tokens
+    const results = await Promise.all(
+      tokenMintAddresses.map(async (tokenMintAddress) => {
+        const res = await this.detectTokenTypeAndGetBalance(walletAddress, tokenMintAddress);
+        return { tokenMintAddress, ...res };
+      })
+    );
+    return results;
+  }
+}

+ 30 - 0
src/lib/clmm-sdk/src/constants.ts

@@ -0,0 +1,30 @@
+import { PublicKey, SystemProgram } from '@solana/web3.js';
+import BN from 'bn.js';
+
+// Number of price points contained in each tick array
+export const TICK_ARRAY_SIZE = 60;
+// Size of tick array bitmap, used to track which tick arrays have been initialized
+export const TICK_ARRAY_BITMAP_SIZE = 512;
+// Size of extended tick array bitmap, used to handle additional liquidity ranges
+export const EXTENSION_TICKARRAY_BITMAP_SIZE = 14;
+
+// Dynamic Tick Array constants
+// Struct length: 208 bytes
+// Header length: 8 (discriminator) + 208 (struct) = 216 bytes
+export const DYN_TICK_ARRAY_STRUCT_LEN = 208;
+export const DYN_TICK_ARRAY_HEADER_LEN = 216;
+// TickState size: 168 bytes
+export const TICK_STATE_LEN = 168;
+
+export const U64_IGNORE_RANGE = new BN('18446744073700000000');
+
+export const BYREAL_CLMM_PROGRAM_ID = new PublicKey('REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2');
+
+export const MEMO_PROGRAM_ID = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr');
+export const RENT_PROGRAM_ID = new PublicKey('SysvarRent111111111111111111111111111111111');
+export const CLOCK_PROGRAM_ID = new PublicKey('SysvarC1ock11111111111111111111111111111111');
+export const METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
+export const INSTRUCTION_PROGRAM_ID = new PublicKey('Sysvar1nstructions1111111111111111111111111');
+export const WSOLMint = new PublicKey('So11111111111111111111111111111111111111112');
+export const SOLMint = PublicKey.default;
+export const SYSTEM_PROGRAM_ID = SystemProgram.programId;

+ 5 - 0
src/lib/clmm-sdk/src/index.ts

@@ -0,0 +1,5 @@
+export * from './constants.js';
+export * from './instructions/index.js';
+export * from './client/index.js';
+export * from './utils/index.js';
+export * from './calculate.js';

+ 1203 - 0
src/lib/clmm-sdk/src/instructions/baseInstruction.ts

@@ -0,0 +1,1203 @@
+import * as anchor from '@coral-xyz/anchor';
+import { Program } from '@coral-xyz/anchor';
+import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
+import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
+import { PublicKey, TransactionInstruction, Connection, clusterApiUrl } from '@solana/web3.js';
+import BN from 'bn.js';
+
+import { BYREAL_CLMM_PROGRAM_ID } from '../constants.js';
+
+import ByrealClmmIDL from './target/idl/byreal_amm_v3.json';
+import { ByrealClmm } from './target/types/byreal_amm_v3.js';
+
+export const getAmmV3Program = (programId: PublicKey): Program<ByrealClmm> => {
+  // Create a read-only provider, no wallet needed
+  const provider = new anchor.AnchorProvider(
+    new Connection(clusterApiUrl('mainnet-beta'), 'confirmed'),
+    {} as any, // Empty wallet object, since only instruction creation is needed
+    { commitment: 'confirmed' }
+  );
+
+  if (programId.toBase58() === BYREAL_CLMM_PROGRAM_ID.toBase58()) {
+    return new Program<ByrealClmm>(ByrealClmmIDL as any, provider);
+  }
+
+  throw new Error('[getAmmV3Program error]: Invalid program id');
+};
+
+export class BaseInstruction {
+  static async createPoolInstruction(
+    programId: PublicKey,
+    poolCreator: PublicKey,
+    poolManager: PublicKey,
+    ammConfigId: PublicKey,
+    mintA: PublicKey,
+    mintProgramIdA: PublicKey,
+    mintB: PublicKey,
+    mintProgramIdB: PublicKey,
+    sqrtPriceX64: BN,
+    openTime?: BN,
+    extendMintAccount?: PublicKey[]
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const _openTime = openTime || new BN(0);
+
+    const instruction = program.methods.createPool(sqrtPriceX64, _openTime).accounts({
+      poolCreator,
+      poolManager,
+      ammConfig: ammConfigId,
+      tokenMint0: mintA,
+      tokenMint1: mintB,
+      tokenProgram0: mintProgramIdA,
+      tokenProgram1: mintProgramIdB,
+    });
+
+    // If there are additional mint accounts, add them as remaining accounts
+    if (extendMintAccount && extendMintAccount.length > 0) {
+      instruction.remainingAccounts(
+        extendMintAccount.map((k) => ({
+          pubkey: k,
+          isSigner: false,
+          isWritable: false,
+        }))
+      );
+    }
+
+    return await instruction.instruction();
+  }
+
+  static async openPositionFromLiquidityInstruction(
+    programId: PublicKey,
+    payer: PublicKey,
+    poolId: PublicKey,
+    positionNftOwner: PublicKey,
+    positionNftMint: PublicKey,
+    positionNftAccount: PublicKey,
+    metadataAccount: PublicKey,
+    protocolPosition: PublicKey,
+    tickArrayLower: PublicKey,
+    tickArrayUpper: PublicKey,
+    personalPosition: PublicKey,
+    ownerTokenAccountA: PublicKey,
+    ownerTokenAccountB: PublicKey,
+    tokenVaultA: PublicKey,
+    tokenVaultB: PublicKey,
+    tokenMintA: PublicKey,
+    tokenMintB: PublicKey,
+
+    tickLowerIndex: number,
+    tickUpperIndex: number,
+    tickArrayLowerStartIndex: number,
+    tickArrayUpperStartIndex: number,
+    liquidity: BN,
+    amountMaxA: BN,
+    amountMaxB: BN,
+    withMetadata: 'create' | 'no-create',
+
+    exTickArrayBitmap?: PublicKey
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods
+      .openPositionV2(
+        tickLowerIndex,
+        tickUpperIndex,
+        tickArrayLowerStartIndex,
+        tickArrayUpperStartIndex,
+        liquidity,
+        amountMaxA,
+        amountMaxB,
+        withMetadata === 'create',
+        null // Set baseFlag to null, since this is based on liquidity
+      )
+      .accountsPartial({
+        payer,
+        positionNftOwner,
+        positionNftMint,
+        positionNftAccount,
+        metadataAccount,
+        poolState: poolId,
+        protocolPosition,
+        tickArrayLower,
+        tickArrayUpper,
+        personalPosition,
+        tokenAccount0: ownerTokenAccountA,
+        tokenAccount1: ownerTokenAccountB,
+        tokenVault0: tokenVaultA,
+        tokenVault1: tokenVaultB,
+        vault0Mint: tokenMintA,
+        vault1Mint: tokenMintB,
+      });
+
+    // If there are additional tick array bitmaps, add them as remaining accounts
+    if (exTickArrayBitmap) {
+      instruction.remainingAccounts([{ pubkey: exTickArrayBitmap, isSigner: false, isWritable: true }]);
+    }
+
+    return await instruction.instruction();
+  }
+
+  static async openPositionFromLiquidityInstruction22(
+    programId: PublicKey,
+    payer: PublicKey,
+    poolId: PublicKey,
+    positionNftOwner: PublicKey,
+    positionNftMint: PublicKey,
+    positionNftAccount: PublicKey,
+    protocolPosition: PublicKey,
+    tickArrayLower: PublicKey,
+    tickArrayUpper: PublicKey,
+    personalPosition: PublicKey,
+    ownerTokenAccountA: PublicKey,
+    ownerTokenAccountB: PublicKey,
+    tokenVaultA: PublicKey,
+    tokenVaultB: PublicKey,
+    tokenMintA: PublicKey,
+    tokenMintB: PublicKey,
+
+    tickLowerIndex: number,
+    tickUpperIndex: number,
+    tickArrayLowerStartIndex: number,
+    tickArrayUpperStartIndex: number,
+    liquidity: BN,
+    amountMaxA: BN,
+    amountMaxB: BN,
+    withMetadata: 'create' | 'no-create',
+
+    exTickArrayBitmap?: PublicKey
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods
+      .openPositionWithToken22Nft(
+        tickLowerIndex,
+        tickUpperIndex,
+        tickArrayLowerStartIndex,
+        tickArrayUpperStartIndex,
+        liquidity,
+        amountMaxA,
+        amountMaxB,
+        withMetadata === 'create',
+        null
+      )
+      .accountsPartial({
+        payer,
+        positionNftOwner,
+        positionNftMint,
+        positionNftAccount,
+        poolState: poolId,
+        // Here we need to manually pass in the generated PDA account, because anchor auto-generation uses little endian, while raydium's contract uses big endian (tick_lower_index.to_be_bytes()), using anchor auto-generation will cause errors;
+        protocolPosition,
+        tickArrayLower,
+        tickArrayUpper,
+        personalPosition,
+
+        tokenAccount0: ownerTokenAccountA,
+        tokenAccount1: ownerTokenAccountB,
+        tokenVault0: tokenVaultA,
+        tokenVault1: tokenVaultB,
+        vault0Mint: tokenMintA,
+        vault1Mint: tokenMintB,
+      });
+
+    // If there are additional tick array bitmaps, add them as remaining accounts
+    if (exTickArrayBitmap) {
+      instruction.remainingAccounts([{ pubkey: exTickArrayBitmap, isSigner: false, isWritable: true }]);
+    }
+
+    return await instruction.instruction();
+  }
+
+  static async openPositionFromBaseInstruction(
+    programId: PublicKey,
+    payer: PublicKey,
+    poolId: PublicKey,
+    positionNftOwner: PublicKey,
+    positionNftMint: PublicKey,
+    positionNftAccount: PublicKey,
+    metadataAccount: PublicKey,
+    protocolPosition: PublicKey,
+    tickArrayLower: PublicKey,
+    tickArrayUpper: PublicKey,
+    personalPosition: PublicKey,
+    ownerTokenAccountA: PublicKey,
+    ownerTokenAccountB: PublicKey,
+    tokenVaultA: PublicKey,
+    tokenVaultB: PublicKey,
+    tokenMintA: PublicKey,
+    tokenMintB: PublicKey,
+
+    tickLowerIndex: number,
+    tickUpperIndex: number,
+    tickArrayLowerStartIndex: number,
+    tickArrayUpperStartIndex: number,
+
+    withMetadata: 'create' | 'no-create',
+    base: 'MintA' | 'MintB',
+    baseAmount: BN,
+    otherAmountMax: BN,
+
+    exTickArrayBitmap?: PublicKey
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods
+      .openPositionV2(
+        tickLowerIndex,
+        tickUpperIndex,
+        tickArrayLowerStartIndex,
+        tickArrayUpperStartIndex,
+        new BN(0), // Set liquidity to 0, since this is based on base amount
+        base === 'MintA' ? baseAmount : otherAmountMax, // amount0Max
+        base === 'MintA' ? otherAmountMax : baseAmount, // amount1Max
+        withMetadata === 'create',
+        base === 'MintA' // baseFlag
+      )
+      .accountsPartial({
+        payer,
+        positionNftOwner,
+        positionNftMint,
+        positionNftAccount,
+        metadataAccount,
+        poolState: poolId,
+        protocolPosition,
+        tickArrayLower,
+        tickArrayUpper,
+        personalPosition,
+        tokenAccount0: ownerTokenAccountA,
+        tokenAccount1: ownerTokenAccountB,
+        tokenVault0: tokenVaultA,
+        tokenVault1: tokenVaultB,
+        vault0Mint: tokenMintA,
+        vault1Mint: tokenMintB,
+      });
+
+    // If there are additional tick array bitmaps, add them as remaining accounts
+    if (exTickArrayBitmap) {
+      instruction.remainingAccounts([{ pubkey: exTickArrayBitmap, isSigner: false, isWritable: true }]);
+    }
+
+    return await instruction.instruction();
+  }
+
+  static async openPositionFromBaseInstruction22(
+    programId: PublicKey,
+    payer: PublicKey,
+    poolId: PublicKey,
+    positionNftOwner: PublicKey,
+    positionNftMint: PublicKey,
+    positionNftAccount: PublicKey,
+    protocolPosition: PublicKey,
+    tickArrayLower: PublicKey,
+    tickArrayUpper: PublicKey,
+    personalPosition: PublicKey,
+    ownerTokenAccountA: PublicKey,
+    ownerTokenAccountB: PublicKey,
+    tokenVaultA: PublicKey,
+    tokenVaultB: PublicKey,
+    tokenMintA: PublicKey,
+    tokenMintB: PublicKey,
+
+    tickLowerIndex: number,
+    tickUpperIndex: number,
+    tickArrayLowerStartIndex: number,
+    tickArrayUpperStartIndex: number,
+
+    withMetadata: 'create' | 'no-create',
+    base: 'MintA' | 'MintB',
+    baseAmount: BN,
+    otherAmountMax: BN,
+
+    exTickArrayBitmap?: PublicKey
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods
+      .openPositionWithToken22Nft(
+        tickLowerIndex,
+        tickUpperIndex,
+        tickArrayLowerStartIndex,
+        tickArrayUpperStartIndex,
+        new BN(0), // Set liquidity to 0, since this is based on base amount
+        base === 'MintA' ? baseAmount : otherAmountMax, // amountMaxA
+        base === 'MintA' ? otherAmountMax : baseAmount, // amountMaxB
+        withMetadata === 'create',
+        base === 'MintA' // baseFlag
+      )
+      .accountsPartial({
+        payer,
+        positionNftOwner,
+        positionNftMint,
+        positionNftAccount,
+        poolState: poolId,
+        // Here we need to manually pass in the generated PDA account, because anchor auto-generation uses little endian; while raydium's contract uses big endian (tick_lower_index.to_be_bytes())
+        protocolPosition,
+        tickArrayLower,
+        tickArrayUpper,
+        personalPosition,
+
+        tokenAccount0: ownerTokenAccountA,
+        tokenAccount1: ownerTokenAccountB,
+        tokenVault0: tokenVaultA,
+        tokenVault1: tokenVaultB,
+        vault0Mint: tokenMintA,
+        vault1Mint: tokenMintB,
+      });
+
+    // If there are additional tick array bitmaps, add them as remaining accounts
+    if (exTickArrayBitmap) {
+      instruction.remainingAccounts([{ pubkey: exTickArrayBitmap, isSigner: false, isWritable: true }]);
+    }
+
+    return await instruction.instruction();
+  }
+
+  static async closePositionInstruction(
+    programId: PublicKey,
+    positionNftOwner: PublicKey,
+    positionNftMint: PublicKey,
+    positionNftAccount: PublicKey,
+    nft2022?: boolean
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.closePosition().accounts({
+      nftOwner: positionNftOwner,
+      positionNftMint,
+      positionNftAccount,
+      tokenProgram: nft2022 ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async increasePositionFromLiquidityInstruction(
+    programId: PublicKey,
+    positionNftOwner: PublicKey,
+    positionNftAccount: PublicKey,
+    personalPosition: PublicKey,
+
+    poolId: PublicKey,
+    protocolPosition: PublicKey,
+    tickArrayLower: PublicKey,
+    tickArrayUpper: PublicKey,
+    ownerTokenAccountA: PublicKey,
+    ownerTokenAccountB: PublicKey,
+    mintVaultA: PublicKey,
+    mintVaultB: PublicKey,
+    mintMintA: PublicKey,
+    mintMintB: PublicKey,
+
+    liquidity: BN,
+    amountMaxA: BN,
+    amountMaxB: BN,
+
+    exTickArrayBitmap?: PublicKey
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods
+      .increaseLiquidityV2(
+        liquidity,
+        amountMaxA,
+        amountMaxB,
+        null // Set baseFlag to null, since this is based on liquidity instruction
+      )
+      .accountsPartial({
+        nftOwner: positionNftOwner,
+        nftAccount: positionNftAccount,
+        poolState: poolId,
+        protocolPosition,
+        personalPosition,
+        tickArrayLower,
+        tickArrayUpper,
+        tokenAccount0: ownerTokenAccountA,
+        tokenAccount1: ownerTokenAccountB,
+        tokenVault0: mintVaultA,
+        tokenVault1: mintVaultB,
+        vault0Mint: mintMintA,
+        vault1Mint: mintMintB,
+      });
+
+    // If there are additional tick array bitmaps, add them as remaining accounts
+    if (exTickArrayBitmap) {
+      instruction.remainingAccounts([{ pubkey: exTickArrayBitmap, isSigner: false, isWritable: true }]);
+    }
+
+    return await instruction.instruction();
+  }
+
+  static async increasePositionFromBaseInstruction(
+    programId: PublicKey,
+    positionNftOwner: PublicKey,
+    positionNftAccount: PublicKey,
+    personalPosition: PublicKey,
+
+    poolId: PublicKey,
+    protocolPosition: PublicKey,
+    tickArrayLower: PublicKey,
+    tickArrayUpper: PublicKey,
+    ownerTokenAccountA: PublicKey,
+    ownerTokenAccountB: PublicKey,
+    mintVaultA: PublicKey,
+    mintVaultB: PublicKey,
+    mintMintA: PublicKey,
+    mintMintB: PublicKey,
+
+    base: 'MintA' | 'MintB',
+    baseAmount: BN,
+    otherAmountMax: BN,
+
+    exTickArrayBitmap?: PublicKey
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods
+      .increaseLiquidityV2(
+        new BN(0), // Set liquidity to 0, since this is based on base amount
+        base === 'MintA' ? baseAmount : otherAmountMax, // amount0Max
+        base === 'MintA' ? otherAmountMax : baseAmount, // amount1Max
+        base === 'MintA' // baseFlag
+      )
+      .accountsPartial({
+        nftOwner: positionNftOwner,
+        nftAccount: positionNftAccount,
+        poolState: poolId,
+        protocolPosition,
+        personalPosition,
+        tickArrayLower,
+        tickArrayUpper,
+        tokenAccount0: ownerTokenAccountA,
+        tokenAccount1: ownerTokenAccountB,
+        tokenVault0: mintVaultA,
+        tokenVault1: mintVaultB,
+        vault0Mint: mintMintA,
+        vault1Mint: mintMintB,
+      });
+
+    // If there are additional tick array bitmaps, add them as remaining accounts
+    if (exTickArrayBitmap) {
+      instruction.remainingAccounts([{ pubkey: exTickArrayBitmap, isSigner: false, isWritable: true }]);
+    }
+
+    return await instruction.instruction();
+  }
+
+  static async decreaseLiquidityInstruction(
+    programId: PublicKey,
+    positionNftOwner: PublicKey,
+    positionNftAccount: PublicKey,
+    personalPosition: PublicKey,
+
+    poolId: PublicKey,
+    protocolPosition: PublicKey,
+    tickArrayLower: PublicKey,
+    tickArrayUpper: PublicKey,
+    ownerTokenAccountA: PublicKey,
+    ownerTokenAccountB: PublicKey,
+    mintVaultA: PublicKey,
+    mintVaultB: PublicKey,
+    mintMintA: PublicKey,
+    mintMintB: PublicKey,
+    rewardAccounts: {
+      poolRewardVault: PublicKey;
+      ownerRewardVault: PublicKey;
+      rewardMint: PublicKey;
+    }[],
+
+    liquidity: BN,
+    amountMinA: BN,
+    amountMinB: BN,
+
+    exTickArrayBitmap?: PublicKey
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.decreaseLiquidityV2(liquidity, amountMinA, amountMinB).accountsPartial({
+      nftOwner: positionNftOwner,
+      nftAccount: positionNftAccount,
+      personalPosition,
+      poolState: poolId,
+      protocolPosition,
+      tokenVault0: mintVaultA,
+      tokenVault1: mintVaultB,
+      tickArrayLower,
+      tickArrayUpper,
+      recipientTokenAccount0: ownerTokenAccountA,
+      recipientTokenAccount1: ownerTokenAccountB,
+      vault0Mint: mintMintA,
+      vault1Mint: mintMintB,
+    });
+
+    // Build remaining accounts
+    const remainingAccounts = [
+      ...(exTickArrayBitmap ? [{ pubkey: exTickArrayBitmap, isSigner: false, isWritable: true }] : []),
+      ...rewardAccounts
+        .map((i) => [
+          { pubkey: i.poolRewardVault, isSigner: false, isWritable: true },
+          { pubkey: i.ownerRewardVault, isSigner: false, isWritable: true },
+          { pubkey: i.rewardMint, isSigner: false, isWritable: false },
+        ])
+        .flat(),
+    ];
+
+    if (remainingAccounts.length > 0) {
+      instruction.remainingAccounts(remainingAccounts);
+    }
+
+    return await instruction.instruction();
+  }
+
+  static async swapInstruction(
+    programId: PublicKey,
+    payer: PublicKey,
+    poolId: PublicKey,
+    ammConfigId: PublicKey,
+    inputTokenAccount: PublicKey,
+    outputTokenAccount: PublicKey,
+    inputVault: PublicKey,
+    outputVault: PublicKey,
+    inputMint: PublicKey,
+    outputMint: PublicKey,
+    tickArray: PublicKey[],
+    observationId: PublicKey,
+
+    amount: BN,
+    otherAmountThreshold: BN,
+    sqrtPriceLimitX64: BN,
+    isBaseInput: boolean,
+
+    exTickArrayBitmap?: PublicKey
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.swapV2(amount, otherAmountThreshold, sqrtPriceLimitX64, isBaseInput).accounts({
+      payer,
+      ammConfig: ammConfigId,
+      poolState: poolId,
+      inputTokenAccount,
+      outputTokenAccount,
+      inputVault,
+      outputVault,
+      observationState: observationId,
+      inputVaultMint: inputMint,
+      outputVaultMint: outputMint,
+    });
+
+    // Build remaining accounts
+    const remainingAccounts = [
+      ...(exTickArrayBitmap ? [{ pubkey: exTickArrayBitmap, isSigner: false, isWritable: true }] : []),
+      ...tickArray.map((i) => ({
+        pubkey: i,
+        isSigner: false,
+        isWritable: true,
+      })),
+    ];
+
+    if (remainingAccounts.length > 0) {
+      instruction.remainingAccounts(remainingAccounts);
+    }
+
+    return await instruction.instruction();
+  }
+
+  static async initRewardInstruction(
+    programId: PublicKey,
+    rewardFunder: PublicKey,
+    funderTokenAccount: PublicKey,
+    ammConfigId: PublicKey,
+    poolId: PublicKey,
+    rewardMint: PublicKey,
+    rewardProgramId: PublicKey,
+
+    openTime: number,
+    endTime: number,
+    emissionsPerSecondX64: BN
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods
+      .initializeReward({
+        openTime: new BN(openTime),
+        endTime: new BN(endTime),
+        emissionsPerSecondX64,
+      })
+      .accounts({
+        rewardFunder,
+        funderTokenAccount,
+        ammConfig: ammConfigId,
+        poolState: poolId,
+        rewardTokenMint: rewardMint,
+        rewardTokenProgram: rewardProgramId,
+      });
+
+    return await instruction.instruction();
+  }
+
+  static async setRewardInstruction(
+    programId: PublicKey,
+    authority: PublicKey,
+    ammConfigId: PublicKey,
+    poolId: PublicKey,
+
+    rewardIndex: number,
+    emissionsPerSecondX64: BN,
+    openTime: number,
+    endTime: number
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods
+      .setRewardParams(rewardIndex, emissionsPerSecondX64, new BN(openTime), new BN(endTime))
+      .accounts({
+        authority,
+        ammConfig: ammConfigId,
+        poolState: poolId,
+      });
+
+    return await instruction.instruction();
+  }
+
+  static async collectRewardInstruction(
+    programId: PublicKey,
+    rewardFunder: PublicKey,
+    funderTokenAccount: PublicKey,
+    poolId: PublicKey,
+    rewardVault: PublicKey,
+    rewardMint: PublicKey,
+
+    rewardIndex: number
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.collectRemainingRewards(rewardIndex).accounts({
+      rewardFunder,
+      funderTokenAccount,
+      poolState: poolId,
+      rewardTokenVault: rewardVault,
+      rewardVaultMint: rewardMint,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async createAmmConfigInstruction(
+    programId: PublicKey,
+    owner: PublicKey,
+    ammConfigId: PublicKey,
+    index: number,
+    tickSpacing: number,
+    tradeFeeRate: number,
+    protocolFeeRate: number,
+    fundFeeRate: number
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods
+      .createAmmConfig(index, tickSpacing, tradeFeeRate, protocolFeeRate, fundFeeRate)
+      .accountsPartial({
+        owner,
+        ammConfig: ammConfigId,
+      });
+
+    return await instruction.instruction();
+  }
+
+  static async updateAmmConfigInstruction(
+    programId: PublicKey,
+    ammConfigId: PublicKey,
+    owner: PublicKey,
+    param: number,
+    value: number
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.updateAmmConfig(param, value).accounts({
+      owner,
+      ammConfig: ammConfigId,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async updatePoolStatusInstruction(
+    programId: PublicKey,
+    authority: PublicKey,
+    poolState: PublicKey,
+    status: number
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.updatePoolStatus(status).accounts({
+      poolState,
+      authority,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async createSupportMintAssociatedInstruction(
+    programId: PublicKey,
+    owner: PublicKey,
+    tokenMint: PublicKey
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.createSupportMintAssociated().accounts({
+      owner,
+      tokenMint,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async createOperationAccountInstruction(
+    programId: PublicKey,
+    owner: PublicKey
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.createOperationAccount().accounts({
+      owner,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async updateOperationAccountInstruction(
+    programId: PublicKey,
+    owner: PublicKey,
+    param: number,
+    keys: PublicKey[]
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.updateOperationAccount(param, keys).accounts({
+      owner,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async transferRewardOwnerInstruction(
+    programId: PublicKey,
+    authority: PublicKey,
+    poolState: PublicKey,
+    newOwner: PublicKey
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.transferRewardOwner(newOwner).accounts({
+      poolState,
+      authority,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async updateRewardInfosInstruction(
+    programId: PublicKey,
+    poolState: PublicKey
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.updateRewardInfos().accounts({
+      poolState,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async collectProtocolFeeInstruction(
+    programId: PublicKey,
+    poolState: PublicKey,
+    tokenVault0: PublicKey,
+    tokenVault1: PublicKey,
+    vault0Mint: PublicKey,
+    vault1Mint: PublicKey,
+
+    amount0Requested: BN,
+    amount1Requested: BN
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.collectProtocolFee(amount0Requested, amount1Requested).accounts({
+      poolState,
+      tokenVault0,
+      tokenVault1,
+      vault0Mint,
+      vault1Mint,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async collectFundFeeInstruction(
+    programId: PublicKey,
+    poolState: PublicKey,
+    tokenVault0: PublicKey,
+    tokenVault1: PublicKey,
+    vault0Mint: PublicKey,
+    vault1Mint: PublicKey,
+
+    amount0Requested: BN,
+    amount1Requested: BN
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.collectFundFee(amount0Requested, amount1Requested).accounts({
+      poolState,
+      tokenVault0,
+      tokenVault1,
+      vault0Mint,
+      vault1Mint,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async _legacy_openPosition(
+    programId: PublicKey,
+    payer: PublicKey,
+    positionNftOwner: PublicKey,
+    positionNftMint: PublicKey,
+    positionNftAccount: PublicKey,
+    metadataAccount: PublicKey,
+    poolState: PublicKey,
+    protocolPosition: PublicKey,
+    tickArrayLower: PublicKey,
+    tickArrayUpper: PublicKey,
+    personalPosition: PublicKey,
+    tokenAccount0: PublicKey,
+    tokenAccount1: PublicKey,
+    tokenVault0: PublicKey,
+    tokenVault1: PublicKey,
+
+    tickLowerIndex: number,
+    tickUpperIndex: number,
+    tickArrayLowerStartIndex: number,
+    tickArrayUpperStartIndex: number,
+    liquidity: BN,
+    amount0Max: BN,
+    amount1Max: BN,
+
+    remainingAccounts?: {
+      pubkey: PublicKey;
+      isSigner: boolean;
+      isWritable: boolean;
+    }[]
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods
+      .openPosition(
+        tickLowerIndex,
+        tickUpperIndex,
+        tickArrayLowerStartIndex,
+        tickArrayUpperStartIndex,
+        liquidity,
+        amount0Max,
+        amount1Max
+      )
+      .accountsPartial({
+        payer,
+        positionNftOwner,
+        positionNftMint,
+        positionNftAccount,
+        metadataAccount,
+        poolState,
+        protocolPosition,
+        tickArrayLower,
+        tickArrayUpper,
+        personalPosition,
+        tokenAccount0,
+        tokenAccount1,
+        tokenVault0,
+        tokenVault1,
+      });
+
+    // Add remaining accounts
+    if (remainingAccounts && remainingAccounts.length > 0) {
+      instruction.remainingAccounts(remainingAccounts);
+    }
+
+    return await instruction.instruction();
+  }
+
+  static async _legacy_increaseLiquidity(
+    programId: PublicKey,
+    nftOwner: PublicKey,
+    nftAccount: PublicKey,
+    poolState: PublicKey,
+    protocolPosition: PublicKey,
+    personalPosition: PublicKey,
+    tickArrayLower: PublicKey,
+    tickArrayUpper: PublicKey,
+    tokenAccount0: PublicKey,
+    tokenAccount1: PublicKey,
+    tokenVault0: PublicKey,
+    tokenVault1: PublicKey,
+
+    liquidity: BN,
+    amount0Max: BN,
+    amount1Max: BN,
+
+    remainingAccounts?: {
+      pubkey: PublicKey;
+      isSigner: boolean;
+      isWritable: boolean;
+    }[]
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.increaseLiquidity(liquidity, amount0Max, amount1Max).accountsPartial({
+      nftOwner,
+      nftAccount,
+      poolState,
+      protocolPosition,
+      personalPosition,
+      tickArrayLower,
+      tickArrayUpper,
+      tokenAccount0,
+      tokenAccount1,
+      tokenVault0,
+      tokenVault1,
+    });
+
+    // Add remaining accounts
+    if (remainingAccounts && remainingAccounts.length > 0) {
+      instruction.remainingAccounts(remainingAccounts);
+    }
+
+    return await instruction.instruction();
+  }
+
+  static async _legacy_decreaseLiquidity(
+    programId: PublicKey,
+    nftOwner: PublicKey,
+    nftAccount: PublicKey,
+    personalPosition: PublicKey,
+    poolState: PublicKey,
+    protocolPosition: PublicKey,
+    tokenVaultA: PublicKey,
+    tokenVaultB: PublicKey,
+    tickArrayLower: PublicKey,
+    tickArrayUpper: PublicKey,
+    recipientTokenAccountA: PublicKey,
+    recipientTokenAccountB: PublicKey,
+
+    liquidity: BN,
+    amountAMin: BN,
+    amountBMin: BN,
+
+    remainingAccounts?: {
+      pubkey: PublicKey;
+      isSigner: boolean;
+      isWritable: boolean;
+    }[]
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.decreaseLiquidity(liquidity, amountAMin, amountBMin).accountsPartial({
+      nftOwner,
+      nftAccount,
+      personalPosition,
+      poolState,
+      protocolPosition,
+      tokenVault0: tokenVaultA,
+      tokenVault1: tokenVaultB,
+      tickArrayLower,
+      tickArrayUpper,
+      recipientTokenAccount0: recipientTokenAccountA,
+      recipientTokenAccount1: recipientTokenAccountB,
+    });
+
+    // Add remaining accounts
+    if (remainingAccounts && remainingAccounts.length > 0) {
+      instruction.remainingAccounts(remainingAccounts);
+    }
+
+    return await instruction.instruction();
+  }
+
+  static async _legacy_swap(
+    programId: PublicKey,
+    payer: PublicKey,
+    ammConfig: PublicKey,
+    poolState: PublicKey,
+    inputTokenAccount: PublicKey,
+    outputTokenAccount: PublicKey,
+    inputVault: PublicKey,
+    outputVault: PublicKey,
+    observationState: PublicKey,
+    tickArray: PublicKey[],
+
+    amount: BN,
+    otherAmountThreshold: BN,
+    sqrtPriceLimitX64: BN,
+    isBaseInput: boolean,
+
+    remainingAccounts?: {
+      pubkey: PublicKey;
+      isSigner: boolean;
+      isWritable: boolean;
+    }[]
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    // First tick array as required account, the rest as remaining accounts
+    const [firstTickArray, ...restTickArrays] = tickArray;
+
+    const instruction = program.methods.swap(amount, otherAmountThreshold, sqrtPriceLimitX64, isBaseInput).accounts({
+      payer,
+      ammConfig,
+      poolState,
+      inputTokenAccount,
+      outputTokenAccount,
+      inputVault,
+      outputVault,
+      observationState,
+      tickArray: firstTickArray,
+    });
+
+    // Build remaining accounts, including remaining tick arrays and additional accounts
+    const allRemainingAccounts = [
+      ...restTickArrays.map((pubkey) => ({
+        pubkey,
+        isSigner: false,
+        isWritable: true,
+      })),
+      ...(remainingAccounts || []),
+    ];
+
+    if (allRemainingAccounts.length > 0) {
+      instruction.remainingAccounts(allRemainingAccounts);
+    }
+
+    return await instruction.instruction();
+  }
+
+  // Initialize admin group
+  static async initAmmAdminGroupInstruction(
+    programId: PublicKey,
+    params: {
+      feeKeeper: PublicKey;
+      rewardConfigManager: PublicKey;
+      rewardClaimManager: PublicKey;
+      poolManager: PublicKey;
+      emergencyManager: PublicKey;
+      normalManager: PublicKey;
+    }
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.initAmmAdminGroup({
+      feeKeeper: params.feeKeeper,
+      rewardConfigManager: params.rewardConfigManager,
+      rewardClaimManager: params.rewardClaimManager,
+      poolManager: params.poolManager,
+      emergencyManager: params.emergencyManager,
+      normalManager: params.normalManager,
+    });
+
+    return await instruction.instruction();
+  }
+
+  // Update admin group
+  static async updateAmmAdminGroupInstruction(
+    programId: PublicKey,
+    params: {
+      feeKeeper?: PublicKey;
+      rewardConfigManager?: PublicKey;
+      rewardClaimManager?: PublicKey;
+      poolManager?: PublicKey;
+      emergencyManager?: PublicKey;
+      normalManager?: PublicKey;
+    }
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.updateAmmAdminGroup({
+      feeKeeper: params.feeKeeper ?? null,
+      rewardConfigManager: params.rewardConfigManager ?? null,
+      rewardClaimManager: params.rewardClaimManager ?? null,
+      poolManager: params.poolManager ?? null,
+      emergencyManager: params.emergencyManager ?? null,
+      normalManager: params.normalManager ?? null,
+    });
+
+    return await instruction.instruction();
+  }
+
+  // New chain-off reward related instructions
+  static async depositOffchainRewardInstruction(
+    programId: PublicKey,
+    poolId: PublicKey,
+    payer: PublicKey,
+    authority: PublicKey,
+    tokenMint: PublicKey,
+    payerTokenAccount: PublicKey,
+    tokenProgram: PublicKey,
+    amount: BN
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.depositOffchainReward(amount).accountsPartial({
+      payer,
+      poolId,
+      authority,
+      tokenMint,
+      payerTokenAccount,
+      tokenProgram,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async claimOffchainRewardInstruction(
+    programId: PublicKey,
+    poolId: PublicKey,
+    claimer: PublicKey,
+    authority: PublicKey,
+    tokenMint: PublicKey,
+    claimerTokenAccount: PublicKey,
+    tokenProgram: PublicKey,
+    amount: BN
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.claimOffchainReward(amount).accountsPartial({
+      claimer,
+      authority,
+      poolId,
+      tokenMint,
+      claimerTokenAccount,
+      tokenProgram,
+    });
+
+    return await instruction.instruction();
+  }
+
+  static async withdrawOffchainRewardInstruction(
+    programId: PublicKey,
+    poolId: PublicKey,
+    authority: PublicKey,
+    tokenMint: PublicKey,
+    receiverTokenAccount: PublicKey,
+    tokenProgram: PublicKey,
+    amount: BN
+  ): Promise<TransactionInstruction> {
+    const program = getAmmV3Program(programId);
+
+    const instruction = program.methods.withdrawOffchainReward(amount).accountsPartial({
+      authority,
+      tokenMint,
+      poolId,
+      receiverTokenAccount,
+      tokenProgram,
+    });
+
+    return await instruction.instruction();
+  }
+}

+ 41 - 0
src/lib/clmm-sdk/src/instructions/constants.ts

@@ -0,0 +1,41 @@
+import BN from 'bn.js';
+
+export const ZERO = new BN(0);
+export const ONE = new BN(1);
+export const NEGATIVE_ONE = new BN(-1);
+
+export const Q64 = new BN(1).shln(64);
+export const Q128 = new BN(1).shln(128);
+
+export const MaxU64 = Q64.sub(ONE);
+
+export const U64Resolution = 64;
+
+export const MaxUint128 = Q128.subn(1);
+
+export const MIN_TICK = -443636;
+export const MAX_TICK = -MIN_TICK;
+
+export const MIN_SQRT_PRICE_X64: BN = new BN('4295048016');
+export const MAX_SQRT_PRICE_X64: BN = new BN('79226673521066979257578248091');
+
+export const MIN_SQRT_PRICE_X64_ADD_ONE: BN = new BN('4295048017');
+export const MAX_SQRT_PRICE_X64_SUB_ONE: BN = new BN('79226673521066979257578248090');
+
+export const BIT_PRECISION = 16;
+export const LOG_B_2_X32 = '59543866431248';
+export const LOG_B_P_ERR_MARGIN_LOWER_X64 = '184467440737095516';
+export const LOG_B_P_ERR_MARGIN_UPPER_X64 = '15793534762490258745';
+
+export const FEE_RATE_DENOMINATOR = new BN(10).pow(new BN(6));
+
+export enum Fee {
+  rate_500 = 500, //  500 / 10e6 = 0.0005
+  rate_3000 = 3000, // 3000/ 10e6 = 0.003
+  rate_10000 = 10000, // 10000 /10e6 = 0.01
+}
+export const TICK_SPACINGS: { [amount in Fee]: number } = {
+  [Fee.rate_500]: 10,
+  [Fee.rate_3000]: 60,
+  [Fee.rate_10000]: 200,
+};

+ 155 - 0
src/lib/clmm-sdk/src/instructions/getRawData.ts

@@ -0,0 +1,155 @@
+import { MintLayout } from '@solana/spl-token';
+import { Connection, PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+
+import {
+  AmmConfigLayout,
+  IAmmConfigLayout,
+  IObservationLayout,
+  IPersonalPositionLayout,
+  ObservationLayout,
+  PersonalPositionLayout,
+  PoolLayout,
+} from './layout.js';
+import { IPoolLayoutWithId } from './models.js';
+import { getPdaPersonalPositionAddress } from './pda.js';
+import { fetchWalletTokenAccounts } from './utils/fetchWalletTokenAccounts.js';
+import { SqrtPriceMath } from './utils/index.js';
+
+export class RawDataUtils {
+  /**
+   * Get position list for specified account
+   */
+  static async getRawPositionInfoListByUserAddress(params: {
+    connection: Connection;
+    programId: PublicKey;
+    userAddress: PublicKey;
+  }): Promise<IPersonalPositionLayout[]> {
+    const { connection, programId, userAddress } = params;
+    const { rawTokenAccountInfos } = await fetchWalletTokenAccounts(connection, userAddress);
+    const balanceMints = rawTokenAccountInfos.filter((acc) => acc.accountInfo.amount.eq(new BN(1)));
+    const allPositionKey = balanceMints.map(
+      (acc) => getPdaPersonalPositionAddress(new PublicKey(programId), acc.accountInfo.mint).publicKey
+    );
+
+    const accountInfo = await connection.getMultipleAccountsInfo(allPositionKey);
+    const allPosition: IPersonalPositionLayout[] = [];
+    accountInfo.forEach((positionRes) => {
+      if (!positionRes) return;
+      const position = PersonalPositionLayout.decode(positionRes.data);
+      allPosition.push(position);
+    });
+
+    return allPosition;
+  }
+
+  /**
+   * Get position information from specified position list
+   */
+  static async getRawPositionInfoByNftMint(params: {
+    connection: Connection;
+    programId: PublicKey;
+    nftMint: PublicKey;
+  }): Promise<IPersonalPositionLayout | null> {
+    const { connection, programId, nftMint } = params;
+    const positionKey = getPdaPersonalPositionAddress(new PublicKey(programId), nftMint).publicKey;
+    const positionRes = await connection.getAccountInfo(positionKey);
+    if (!positionRes) return null;
+    const position = PersonalPositionLayout.decode(positionRes.data);
+    return position;
+  }
+
+  /**
+   * Get on-chain information for a single CLMM pool
+   */
+  static async getRawPoolInfoByPoolId(params: {
+    connection: Connection;
+    poolId: string | PublicKey;
+  }): Promise<IPoolLayoutWithId | null> {
+    const { connection, poolId } = params;
+    const poolPublicKey = new PublicKey(poolId);
+
+    const accountInfo = await connection.getAccountInfo(poolPublicKey);
+
+    if (!accountInfo || !accountInfo.data) return null;
+
+    const poolRawInfo = PoolLayout.decode(accountInfo.data);
+
+    const currentPrice = SqrtPriceMath.sqrtPriceX64ToPrice(
+      poolRawInfo.sqrtPriceX64,
+      poolRawInfo.mintDecimalsA,
+      poolRawInfo.mintDecimalsB
+    ).toNumber();
+
+    return {
+      ...poolRawInfo,
+      currentPrice,
+      programId: accountInfo.owner,
+      poolId: poolPublicKey,
+    };
+  }
+
+  /**
+   * Get on-chain information for specified token
+   */
+  static async getRawTokenInfoByMint(params: {
+    connection: Connection;
+    mintAddress: PublicKey;
+  }): Promise<(ReturnType<typeof MintLayout.decode> & { owner: PublicKey }) | null> {
+    const { connection, mintAddress } = params;
+    const accountInfo = await connection.getAccountInfo(mintAddress);
+    if (!accountInfo || !accountInfo.data) return null;
+    const tokenInfo = MintLayout.decode(accountInfo.data);
+    return {
+      ...tokenInfo,
+      owner: accountInfo.owner,
+    };
+  }
+
+  /**
+   * Get AMM configuration on-chain information
+   */
+  static async getRawAmmConfigByConfigId(params: {
+    connection: Connection;
+    configId: string | PublicKey;
+  }): Promise<(IAmmConfigLayout & { configId: PublicKey; owner: PublicKey }) | null> {
+    const { connection, configId } = params;
+    const configPublicKey = new PublicKey(configId);
+
+    const accountInfo = await connection.getAccountInfo(configPublicKey);
+
+    if (!accountInfo || !accountInfo.data) return null;
+
+    const configRawInfo = AmmConfigLayout.decode(accountInfo.data);
+
+    return {
+      ...configRawInfo,
+      configId: configPublicKey,
+      owner: accountInfo.owner,
+    };
+  }
+
+  /**
+   * Get observation account on-chain information
+   * Observation account is used to record historical data of price changes in the pool
+   */
+  static async getRawObservationByObservationId(params: {
+    connection: Connection;
+    observationId: string | PublicKey;
+  }): Promise<(IObservationLayout & { observationId: PublicKey; owner: PublicKey }) | null> {
+    const { connection, observationId } = params;
+    const observationPublicKey = new PublicKey(observationId);
+
+    const accountInfo = await connection.getAccountInfo(observationPublicKey);
+
+    if (!accountInfo || !accountInfo.data) return null;
+
+    const observationRawInfo = ObservationLayout.decode(accountInfo.data);
+
+    return {
+      ...observationRawInfo,
+      observationId: observationPublicKey,
+      owner: accountInfo.owner,
+    };
+  }
+}

+ 8 - 0
src/lib/clmm-sdk/src/instructions/index.ts

@@ -0,0 +1,8 @@
+export * from './utils/index.js';
+export * from './constants.js';
+export * from './baseInstruction.js';
+export * from './instruction.js';
+export * from './models.js';
+export * from './layout.js';
+export * from './pda.js';
+export * from './getRawData.js';

+ 547 - 0
src/lib/clmm-sdk/src/instructions/instruction.ts

@@ -0,0 +1,547 @@
+import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
+import { Keypair, PublicKey, Signer, TransactionInstruction } from '@solana/web3.js';
+import BN from 'bn.js';
+
+import { BaseInstruction } from './baseInstruction.js';
+import { IPersonalPositionLayout } from './layout.js';
+import { IPoolLayoutWithId, ITokenInfo } from './models.js';
+import {
+  getATAAddress,
+  getPdaExBitmapAccount,
+  getPdaPersonalPositionAddress,
+  getPdaProtocolPositionAddress,
+  getPdaTickArrayAddress,
+} from './pda.js';
+import { PoolUtils } from './utils/poolUtils.js';
+import { TickUtils } from './utils/tick.js';
+
+export class Instruction {
+  static async createPoolInstruction(props: {
+    programId: PublicKey;
+    owner: PublicKey;
+    poolManager: PublicKey;
+    mintA: ITokenInfo;
+    mintB: ITokenInfo;
+    ammConfigId: PublicKey;
+    initialPriceX64: BN;
+    openTime?: BN;
+    extendMintAccount?: PublicKey[];
+  }): Promise<{
+    instructions: TransactionInstruction[];
+  }> {
+    // console.log('[clmm sdk] createPoolInstruction fn params:', JSON.stringify(props, null, 2));
+
+    const { programId, owner, poolManager, mintA, mintB, ammConfigId, initialPriceX64, extendMintAccount, openTime } =
+      props;
+
+    const mintAAddress = new PublicKey(mintA.address);
+    const mintBAddress = new PublicKey(mintB.address);
+
+    const instruction = await BaseInstruction.createPoolInstruction(
+      programId,
+      owner,
+      poolManager,
+      ammConfigId,
+      mintAAddress,
+      new PublicKey(mintA.programId),
+      mintBAddress,
+      new PublicKey(mintB.programId),
+      initialPriceX64,
+      openTime,
+      extendMintAccount
+    );
+
+    return {
+      instructions: [instruction],
+    };
+  }
+
+  static async openPositionFromLiquidityInstruction(params: {
+    poolInfo: IPoolLayoutWithId;
+    ownerInfo: {
+      feePayer: PublicKey;
+      wallet: PublicKey;
+      tokenAccountA: PublicKey;
+      tokenAccountB: PublicKey;
+    };
+    tickLower: number;
+    tickUpper: number;
+    liquidity: BN;
+    amountMaxA: BN;
+    amountMaxB: BN;
+    withMetadata: 'create' | 'no-create';
+  }): Promise<{
+    instructions: TransactionInstruction[];
+    signers: Signer[];
+  }> {
+    // console.log('[clmm sdk] openPositionFromLiquidityInstruction fn params:', JSON.stringify(params, null, 2));
+
+    const { poolInfo, ownerInfo, tickLower, tickUpper, liquidity, amountMaxA, amountMaxB, withMetadata } = params;
+    const signers: Signer[] = [];
+
+    const { programId, poolId, tickSpacing } = poolInfo;
+
+    const keypair = Keypair.generate();
+    signers.push(keypair);
+    const nftMintAccount = keypair.publicKey;
+
+    const tickArrayLowerStartIndex = TickUtils.getTickArrayStartIndexByTick(tickLower, tickSpacing);
+    const tickArrayUpperStartIndex = TickUtils.getTickArrayStartIndexByTick(tickUpper, tickSpacing);
+
+    const { publicKey: tickArrayLower } = getPdaTickArrayAddress(programId, poolId, tickArrayLowerStartIndex);
+    const { publicKey: tickArrayUpper } = getPdaTickArrayAddress(programId, poolId, tickArrayUpperStartIndex);
+
+    const { publicKey: positionNftAccount } = getATAAddress(ownerInfo.wallet, nftMintAccount, TOKEN_2022_PROGRAM_ID);
+
+    const { publicKey: personalPosition } = getPdaPersonalPositionAddress(programId, nftMintAccount);
+    const { publicKey: protocolPosition } = getPdaProtocolPositionAddress(programId, poolId, tickLower, tickUpper);
+
+    const instruction = await BaseInstruction.openPositionFromLiquidityInstruction22(
+      programId,
+      ownerInfo.feePayer,
+      poolId,
+      ownerInfo.wallet,
+      nftMintAccount,
+      positionNftAccount,
+      protocolPosition,
+      tickArrayLower,
+      tickArrayUpper,
+      personalPosition,
+      ownerInfo.tokenAccountA,
+      ownerInfo.tokenAccountB,
+      poolInfo.vaultA,
+      poolInfo.vaultB,
+      poolInfo.mintA,
+      poolInfo.mintB,
+      tickLower,
+      tickUpper,
+      tickArrayLowerStartIndex,
+      tickArrayUpperStartIndex,
+      liquidity,
+      amountMaxA,
+      amountMaxB,
+      withMetadata,
+      PoolUtils.isOverflowDefaultTickarrayBitmap(tickSpacing, [tickArrayLowerStartIndex, tickArrayUpperStartIndex])
+        ? getPdaExBitmapAccount(programId, poolId).publicKey
+        : undefined
+    );
+
+    return {
+      signers,
+      instructions: [instruction],
+    };
+  }
+
+  static async openPositionFromBaseInstruction(params: {
+    poolInfo: IPoolLayoutWithId;
+    ownerInfo: {
+      feePayer: PublicKey;
+      wallet: PublicKey;
+      tokenAccountA: PublicKey;
+      tokenAccountB: PublicKey;
+    };
+    tickLower: number;
+    tickUpper: number;
+    base: 'MintA' | 'MintB';
+    baseAmount: BN;
+    otherAmountMax: BN;
+    withMetadata: 'create' | 'no-create';
+  }): Promise<{
+    instructions: TransactionInstruction[];
+    signers: Signer[];
+  }> {
+    // console.log('[clmm sdk] openPositionFromBaseInstruction fn params:', JSON.stringify(params, null, 2));
+
+    const { poolInfo, ownerInfo, tickLower, tickUpper, base, baseAmount, otherAmountMax, withMetadata } = params;
+    const signers: Signer[] = [];
+
+    const { programId, poolId, tickSpacing } = poolInfo;
+
+    const keypair = Keypair.generate();
+    signers.push(keypair);
+    const nftMintAccount = keypair.publicKey;
+
+    const tickArrayLowerStartIndex = TickUtils.getTickArrayStartIndexByTick(tickLower, tickSpacing);
+    const tickArrayUpperStartIndex = TickUtils.getTickArrayStartIndexByTick(tickUpper, tickSpacing);
+
+    const { publicKey: tickArrayLower } = getPdaTickArrayAddress(programId, poolId, tickArrayLowerStartIndex);
+    const { publicKey: tickArrayUpper } = getPdaTickArrayAddress(programId, poolId, tickArrayUpperStartIndex);
+
+    const { publicKey: positionNftAccount } = getATAAddress(ownerInfo.wallet, nftMintAccount, TOKEN_2022_PROGRAM_ID);
+    const { publicKey: personalPosition } = getPdaPersonalPositionAddress(programId, nftMintAccount);
+    const { publicKey: protocolPosition } = getPdaProtocolPositionAddress(programId, poolId, tickLower, tickUpper);
+
+    const instruction = await BaseInstruction.openPositionFromBaseInstruction22(
+      programId,
+      ownerInfo.feePayer,
+      poolId,
+      ownerInfo.wallet,
+      nftMintAccount,
+      positionNftAccount,
+      protocolPosition,
+      tickArrayLower,
+      tickArrayUpper,
+      personalPosition,
+      ownerInfo.tokenAccountA,
+      ownerInfo.tokenAccountB,
+      poolInfo.vaultA,
+      poolInfo.vaultB,
+      poolInfo.mintA,
+      poolInfo.mintB,
+      tickLower,
+      tickUpper,
+      tickArrayLowerStartIndex,
+      tickArrayUpperStartIndex,
+      withMetadata,
+      base,
+      baseAmount,
+      otherAmountMax,
+      PoolUtils.isOverflowDefaultTickarrayBitmap(tickSpacing, [tickArrayLowerStartIndex, tickArrayUpperStartIndex])
+        ? getPdaExBitmapAccount(programId, poolId).publicKey
+        : undefined
+    );
+
+    return {
+      instructions: [instruction],
+      signers,
+    };
+  }
+
+  static async closePositionInstruction(params: {
+    programId: PublicKey;
+    nftMint: PublicKey;
+    ownerWallet: PublicKey;
+  }): Promise<{
+    instructions: TransactionInstruction[];
+  }> {
+    // console.log('[clmm sdk] closePositionInstruction fn params:', JSON.stringify(params, null, 2));
+
+    const { programId, nftMint, ownerWallet } = params;
+    const positionNftAccount = getATAAddress(ownerWallet, nftMint, TOKEN_2022_PROGRAM_ID).publicKey;
+
+    const instruction = await BaseInstruction.closePositionInstruction(
+      programId,
+      ownerWallet,
+      nftMint,
+      positionNftAccount,
+      true
+    );
+
+    return {
+      instructions: [instruction],
+    };
+  }
+
+  static async increasePositionFromLiquidityInstructions(params: {
+    poolInfo: IPoolLayoutWithId;
+    ownerPosition: IPersonalPositionLayout;
+    ownerInfo: {
+      wallet: PublicKey;
+      tokenAccountA: PublicKey;
+      tokenAccountB: PublicKey;
+    };
+
+    liquidity: BN;
+    amountMaxA: BN;
+    amountMaxB: BN;
+  }): Promise<{
+    instructions: TransactionInstruction[];
+  }> {
+    // console.log('[clmm sdk] increasePositionFromLiquidityInstructions fn params:', JSON.stringify(params, null, 2));
+
+    const { poolInfo, ownerPosition, ownerInfo, liquidity, amountMaxA, amountMaxB } = params;
+    const { programId, poolId, tickSpacing } = poolInfo;
+
+    const tickArrayLowerStartIndex = TickUtils.getTickArrayStartIndexByTick(ownerPosition.tickLower, tickSpacing);
+    const tickArrayUpperStartIndex = TickUtils.getTickArrayStartIndexByTick(ownerPosition.tickUpper, tickSpacing);
+
+    const { publicKey: tickArrayLower } = getPdaTickArrayAddress(programId, poolId, tickArrayLowerStartIndex);
+    const { publicKey: tickArrayUpper } = getPdaTickArrayAddress(programId, poolId, tickArrayUpperStartIndex);
+
+    const { publicKey: positionNftAccount } = getATAAddress(
+      ownerInfo.wallet,
+      ownerPosition.nftMint,
+      TOKEN_2022_PROGRAM_ID
+    );
+
+    const { publicKey: personalPosition } = getPdaPersonalPositionAddress(programId, ownerPosition.nftMint);
+    const { publicKey: protocolPosition } = getPdaProtocolPositionAddress(
+      programId,
+      poolId,
+      ownerPosition.tickLower,
+      ownerPosition.tickUpper
+    );
+
+    const instruction = await BaseInstruction.increasePositionFromLiquidityInstruction(
+      programId,
+      ownerInfo.wallet,
+      positionNftAccount,
+      personalPosition,
+      poolId,
+      protocolPosition,
+      tickArrayLower,
+      tickArrayUpper,
+      ownerInfo.tokenAccountA,
+      ownerInfo.tokenAccountB,
+      poolInfo.vaultA,
+      poolInfo.vaultB,
+      poolInfo.mintA,
+      poolInfo.mintB,
+      liquidity,
+      amountMaxA,
+      amountMaxB,
+      PoolUtils.isOverflowDefaultTickarrayBitmap(tickSpacing, [tickArrayLowerStartIndex, tickArrayUpperStartIndex])
+        ? getPdaExBitmapAccount(programId, poolId).publicKey
+        : undefined
+    );
+
+    return {
+      instructions: [instruction],
+    };
+  }
+
+  static async increasePositionFromBaseInstructions(params: {
+    poolInfo: IPoolLayoutWithId;
+    ownerPosition: IPersonalPositionLayout;
+    ownerInfo: {
+      wallet: PublicKey;
+      tokenAccountA: PublicKey;
+      tokenAccountB: PublicKey;
+    };
+    base: 'MintA' | 'MintB';
+    baseAmount: BN;
+    otherAmountMax: BN;
+  }): Promise<{
+    instructions: TransactionInstruction[];
+  }> {
+    // console.log('[clmm sdk] increasePositionFromBaseInstructions fn params:', JSON.stringify(params, null, 2));
+
+    const { poolInfo, ownerPosition, ownerInfo, base, baseAmount, otherAmountMax } = params;
+    const { programId, poolId, tickSpacing } = poolInfo;
+
+    const tickArrayLowerStartIndex = TickUtils.getTickArrayStartIndexByTick(ownerPosition.tickLower, tickSpacing);
+    const tickArrayUpperStartIndex = TickUtils.getTickArrayStartIndexByTick(ownerPosition.tickUpper, tickSpacing);
+
+    const { publicKey: tickArrayLower } = getPdaTickArrayAddress(programId, poolId, tickArrayLowerStartIndex);
+    const { publicKey: tickArrayUpper } = getPdaTickArrayAddress(programId, poolId, tickArrayUpperStartIndex);
+
+    const { publicKey: positionNftAccount } = getATAAddress(
+      ownerInfo.wallet,
+      ownerPosition.nftMint,
+      TOKEN_2022_PROGRAM_ID
+    );
+
+    const { publicKey: personalPosition } = getPdaPersonalPositionAddress(programId, ownerPosition.nftMint);
+    const { publicKey: protocolPosition } = getPdaProtocolPositionAddress(
+      programId,
+      poolId,
+      ownerPosition.tickLower,
+      ownerPosition.tickUpper
+    );
+
+    const instruction = await BaseInstruction.increasePositionFromBaseInstruction(
+      programId,
+      ownerInfo.wallet,
+      positionNftAccount,
+      personalPosition,
+      poolId,
+      protocolPosition,
+      tickArrayLower,
+      tickArrayUpper,
+      ownerInfo.tokenAccountA,
+      ownerInfo.tokenAccountB,
+      poolInfo.vaultA,
+      poolInfo.vaultB,
+      poolInfo.mintA,
+      poolInfo.mintB,
+      base,
+      baseAmount,
+      otherAmountMax,
+      PoolUtils.isOverflowDefaultTickarrayBitmap(tickSpacing, [tickArrayLowerStartIndex, tickArrayUpperStartIndex])
+        ? getPdaExBitmapAccount(programId, poolId).publicKey
+        : undefined
+    );
+
+    return {
+      instructions: [instruction],
+    };
+  }
+
+  static async decreaseLiquidityInstructions(params: {
+    poolInfo: IPoolLayoutWithId;
+    ownerPosition: IPersonalPositionLayout;
+    ownerInfo: {
+      wallet: PublicKey;
+      tokenAccountA: PublicKey;
+      tokenAccountB: PublicKey;
+      // rewardAccounts: PublicKey[];
+    };
+    liquidity: BN;
+    amountMinA: BN;
+    amountMinB: BN;
+  }): Promise<{
+    instructions: TransactionInstruction[];
+  }> {
+    // console.log('[clmm sdk] decreaseLiquidityInstructions fn params:', JSON.stringify(params, null, 2));
+
+    const { poolInfo, ownerPosition, ownerInfo, liquidity, amountMinA, amountMinB } = params;
+    const { programId, poolId, tickSpacing } = poolInfo;
+
+    const tickArrayLowerStartIndex = TickUtils.getTickArrayStartIndexByTick(ownerPosition.tickLower, tickSpacing);
+    const tickArrayUpperStartIndex = TickUtils.getTickArrayStartIndexByTick(ownerPosition.tickUpper, tickSpacing);
+
+    const { publicKey: tickArrayLower } = getPdaTickArrayAddress(programId, poolId, tickArrayLowerStartIndex);
+    const { publicKey: tickArrayUpper } = getPdaTickArrayAddress(programId, poolId, tickArrayUpperStartIndex);
+    const { publicKey: positionNftAccount } = getATAAddress(
+      ownerInfo.wallet,
+      ownerPosition.nftMint,
+      TOKEN_2022_PROGRAM_ID
+    );
+    const { publicKey: personalPosition } = getPdaPersonalPositionAddress(programId, ownerPosition.nftMint);
+    const { publicKey: protocolPosition } = getPdaProtocolPositionAddress(
+      programId,
+      poolId,
+      ownerPosition.tickLower,
+      ownerPosition.tickUpper
+    );
+
+    const rewardAccounts = poolInfo.rewardInfos
+      .filter((info) => !info.openTime.isZero() && !info.tokenMint.equals(PublicKey.default))
+      .map((rewardInfo) => ({
+        poolRewardVault: rewardInfo.tokenVault,
+        ownerRewardVault: getATAAddress(ownerInfo.wallet, rewardInfo.tokenMint).publicKey,
+        rewardMint: rewardInfo.tokenMint,
+      }));
+
+    const instruction = await BaseInstruction.decreaseLiquidityInstruction(
+      programId,
+      ownerInfo.wallet,
+      positionNftAccount,
+      personalPosition,
+      poolId,
+      protocolPosition,
+      tickArrayLower,
+      tickArrayUpper,
+      ownerInfo.tokenAccountA,
+      ownerInfo.tokenAccountB,
+      poolInfo.vaultA,
+      poolInfo.vaultB,
+      poolInfo.mintA,
+      poolInfo.mintB,
+      rewardAccounts,
+
+      liquidity,
+      amountMinA,
+      amountMinB,
+      PoolUtils.isOverflowDefaultTickarrayBitmap(tickSpacing, [tickArrayLowerStartIndex, tickArrayUpperStartIndex])
+        ? getPdaExBitmapAccount(programId, poolId).publicKey
+        : undefined
+    );
+
+    return {
+      instructions: [instruction],
+    };
+  }
+
+  static async swapBaseInInstruction(params: {
+    poolInfo: IPoolLayoutWithId;
+    ownerInfo: {
+      wallet: PublicKey;
+      tokenAccountA: PublicKey;
+      tokenAccountB: PublicKey;
+    };
+    amount: BN;
+    otherAmountThreshold: BN;
+    sqrtPriceLimitX64: BN;
+    isInputMintA: boolean;
+    exTickArrayBitmap?: PublicKey;
+    tickArray: PublicKey[];
+  }) {
+    const {
+      poolInfo,
+      ownerInfo,
+      amount,
+      otherAmountThreshold,
+      sqrtPriceLimitX64,
+      exTickArrayBitmap,
+      tickArray,
+      isInputMintA,
+    } = params;
+
+    const instruction = await BaseInstruction.swapInstruction(
+      poolInfo.programId,
+      ownerInfo.wallet,
+      poolInfo.poolId,
+      poolInfo.ammConfig,
+      isInputMintA ? ownerInfo.tokenAccountA : ownerInfo.tokenAccountB,
+      isInputMintA ? ownerInfo.tokenAccountB : ownerInfo.tokenAccountA,
+      isInputMintA ? poolInfo.vaultA : poolInfo.vaultB,
+      isInputMintA ? poolInfo.vaultB : poolInfo.vaultA,
+      isInputMintA ? poolInfo.mintA : poolInfo.mintB,
+      isInputMintA ? poolInfo.mintB : poolInfo.mintA,
+      tickArray,
+      poolInfo.observationId,
+      amount,
+      otherAmountThreshold,
+      sqrtPriceLimitX64,
+      true,
+      exTickArrayBitmap
+    );
+
+    return {
+      instructions: [instruction],
+    };
+  }
+
+  static async swapBaseOutInstruction(params: {
+    poolInfo: IPoolLayoutWithId;
+    ownerInfo: {
+      wallet: PublicKey;
+      tokenAccountA: PublicKey;
+      tokenAccountB: PublicKey;
+    };
+    amount: BN;
+    otherAmountThreshold: BN;
+    sqrtPriceLimitX64: BN;
+    isOutputMintA: boolean;
+    exTickArrayBitmap?: PublicKey;
+    tickArray: PublicKey[];
+  }) {
+    const {
+      poolInfo,
+      ownerInfo,
+      amount,
+      otherAmountThreshold,
+      sqrtPriceLimitX64,
+      exTickArrayBitmap,
+      tickArray,
+      isOutputMintA,
+    } = params;
+
+    // For exact output, input and output are reversed
+    // If output is mintA, then input is mintB
+    const instruction = await BaseInstruction.swapInstruction(
+      poolInfo.programId,
+      ownerInfo.wallet,
+      poolInfo.poolId,
+      poolInfo.ammConfig,
+      isOutputMintA ? ownerInfo.tokenAccountB : ownerInfo.tokenAccountA,
+      isOutputMintA ? ownerInfo.tokenAccountA : ownerInfo.tokenAccountB,
+      isOutputMintA ? poolInfo.vaultB : poolInfo.vaultA,
+      isOutputMintA ? poolInfo.vaultA : poolInfo.vaultB,
+      isOutputMintA ? poolInfo.mintB : poolInfo.mintA,
+      isOutputMintA ? poolInfo.mintA : poolInfo.mintB,
+      tickArray,
+      poolInfo.observationId,
+      amount,
+      otherAmountThreshold,
+      sqrtPriceLimitX64,
+      false, // isBaseInput = false for exact output
+      exTickArrayBitmap
+    );
+
+    return {
+      instructions: [instruction],
+    };
+  }
+}

+ 255 - 0
src/lib/clmm-sdk/src/instructions/layout.ts

@@ -0,0 +1,255 @@
+import { TICK_ARRAY_SIZE, EXTENSION_TICKARRAY_BITMAP_SIZE } from '../constants.js';
+
+import {
+  blob,
+  bool,
+  i128,
+  i64,
+  publicKey,
+  s32,
+  seq,
+  struct,
+  u128,
+  u16,
+  u32,
+  u64,
+  u8,
+} from './libs/marshmallow/index.js';
+
+/**
+ * @description Pool configuration layout
+ *
+ * @example https://solscan.io/account/EdPxg8QaeFSrTYqdWJn6Kezwy9McWncTYueD9eMGCuzR#anchorData
+ */
+export const AmmConfigLayout = struct([
+  blob(8),
+  u8('bump'),
+  u16('index'),
+  publicKey('owner'),
+  u32('protocolFeeRate'),
+  u32('tradeFeeRate'),
+  u16('tickSpacing'),
+  u32('fundFeeRate'),
+  u32('padding'),
+  publicKey('fundOwner'),
+]);
+
+export type IAmmConfigLayout = ReturnType<typeof AmmConfigLayout.decode>;
+
+/**
+ * @description Observation account layout
+ *
+ * @example https://solscan.io/account/AA5RaVvyGyZgtmAsJJHT5ZVBxVPtAXuYaMwfgeFJW4Mk#anchorData
+ */
+export const ObservationLayout = struct([
+  blob(8),
+  bool('initialized'),
+  u64('recentEpoch'),
+  u16('observationIndex'),
+  publicKey('poolId'),
+  seq(struct([u32('blockTimestamp'), i64('tickCumulative'), seq(u64(), 4)]), 100, 'observations'),
+]);
+
+export type IObservationLayout = ReturnType<typeof ObservationLayout.decode>;
+
+/**
+ * @description Pool information layout
+ *
+ * @example https://solscan.io/account/CYbD9RaToYMtWKA7QZyoLahnHdWq553Vm62Lh6qWtuxq#anchorData
+ */
+export const PoolLayout = struct([
+  blob(8),
+  u8('bump'),
+  publicKey('ammConfig'),
+  publicKey('creator'),
+  publicKey('mintA'),
+  publicKey('mintB'),
+  publicKey('vaultA'),
+  publicKey('vaultB'),
+  publicKey('observationId'),
+  u8('mintDecimalsA'),
+  u8('mintDecimalsB'),
+  u16('tickSpacing'),
+  u128('liquidity'),
+  u128('sqrtPriceX64'),
+  s32('tickCurrent'),
+  u32(),
+  u128('feeGrowthGlobalX64A'),
+  u128('feeGrowthGlobalX64B'),
+  u64('protocolFeesTokenA'),
+  u64('protocolFeesTokenB'),
+
+  u128('swapInAmountTokenA'),
+  u128('swapOutAmountTokenB'),
+  u128('swapInAmountTokenB'),
+  u128('swapOutAmountTokenA'),
+
+  u8('status'),
+
+  seq(u8(), 7, ''),
+
+  seq(
+    struct([
+      u8('rewardState'),
+      u64('openTime'),
+      u64('endTime'),
+      u64('lastUpdateTime'),
+      u128('emissionsPerSecondX64'),
+      u64('rewardTotalEmissioned'),
+      u64('rewardClaimed'),
+      publicKey('tokenMint'),
+      publicKey('tokenVault'),
+      publicKey('creator'),
+      u128('rewardGrowthGlobalX64'),
+    ]),
+    3,
+    'rewardInfos'
+  ),
+  seq(u64(), 16, 'tickArrayBitmap'),
+
+  u64('totalFeesTokenA'),
+  u64('totalFeesClaimedTokenA'),
+  u64('totalFeesTokenB'),
+  u64('totalFeesClaimedTokenB'),
+
+  u64('fundFeesTokenA'),
+  u64('fundFeesTokenB'),
+
+  u64('openTime'),
+  u64('recentEpoch'),
+
+  u8('decayFeeFlag'),
+  u8('decayFeeInitFeeRate'),
+  u8('decayFeeDecreaseRate'),
+  u8('decayFeeDecreaseInterval'),
+  seq(u8(), 4, 'padding1_1'),
+  seq(u64(), 23, 'padding1'),
+  seq(u64(), 32, 'padding2'),
+]);
+
+export type IPoolLayout = ReturnType<typeof PoolLayout.decode>;
+
+/**
+ * @description Personal position information layout
+ *
+ * @example https://solscan.io/account/CLYRosA3oGsx6WjuebDYmEL3kukTCSsncmYU6at8nDsn#anchorData
+ */
+export const PersonalPositionLayout = struct([
+  blob(8),
+  u8('bump'),
+  publicKey('nftMint'),
+  publicKey('poolId'),
+
+  s32('tickLower'),
+  s32('tickUpper'),
+  u128('liquidity'),
+  u128('feeGrowthInsideLastX64A'),
+  u128('feeGrowthInsideLastX64B'),
+  u64('tokenFeesOwedA'),
+  u64('tokenFeesOwedB'),
+
+  seq(struct([u128('growthInsideLastX64'), u64('rewardAmountOwed')]), 3, 'rewardInfos'),
+]);
+
+export type IPersonalPositionLayout = ReturnType<typeof PersonalPositionLayout.decode>;
+
+/**
+ * @description Protocol position information layout, used to track liquidity within specific price ranges
+ *
+ * @example https://solscan.io/account/38GUhmh7vPyWStAV3YKEEYPHrLk2Mnw5jvaUQkMGS1hb#anchorData
+ */
+export const ProtocolPositionLayout = struct([
+  blob(8),
+  u8('bump'),
+  publicKey('poolId'),
+  s32('tickLowerIndex'),
+  s32('tickUpperIndex'),
+  u128('liquidity'),
+  u128('feeGrowthInsideLastX64A'),
+  u128('feeGrowthInsideLastX64B'),
+  u64('tokenFeesOwedA'),
+  u64('tokenFeesOwedB'),
+  seq(u128(), 3, 'rewardGrowthInside'),
+]);
+
+/**
+ * @description Price tick array layout
+ *
+ * @example https://solscan.io/account/4vGLPwfohNUd2o4NwZPMx7q8AH98DQ9Eth5tS1p8dew1#anchorData
+ */
+/**
+ * @description TickState layout (168 bytes)
+ */
+export const TickStateLayout = struct([
+  s32('tick'),
+  i128('liquidityNet'),
+  u128('liquidityGross'),
+  u128('feeGrowthOutsideX64A'),
+  u128('feeGrowthOutsideX64B'),
+  seq(u128(), 3, 'rewardGrowthsOutsideX64'),
+  seq(u32(), 13, ''),
+]);
+
+export type ITickStateLayout = ReturnType<typeof TickStateLayout.decode>;
+
+export const TickArrayLayout = struct([
+  blob(8),
+  publicKey('poolId'),
+  s32('startTickIndex'),
+  seq(TickStateLayout, TICK_ARRAY_SIZE, 'ticks'),
+  u8('initializedTickCount'),
+]);
+
+export type ITickArrayLayout = ReturnType<typeof TickArrayLayout.decode>;
+
+/**
+ * @description Dynamic Tick Array layout
+ *
+ * Dynamic tick arrays use a sparse storage model with a mapping table (tick_offset_index)
+ * to track which logical tick positions have allocated TickStates.
+ * This allows for more efficient memory usage compared to fixed tick arrays.
+ *
+ * Struct size: 208 bytes (32+4+4+60+1+1+2+8+96)
+ * Header size: 216 bytes (8 discriminator + 208 struct)
+ * Followed by dynamic number of TickStates (max 60, each 168 bytes)
+ */
+export const DynTickArrayLayout = struct([
+  blob(8), // discriminator (8 bytes)
+  publicKey('poolId'), // 32 bytes
+  s32('startTickIndex'), // 4 bytes
+  blob(4, 'padding0'), // 4 bytes
+  seq(u8(), TICK_ARRAY_SIZE, 'tickOffsetIndex'), // 60 bytes - Mapping table: offset -> physical position + 1
+  u8('allocTickCount'), // 1 byte - Number of allocated ticks
+  u8('initializedTickCount'), // 1 byte - Number of initialized ticks
+  blob(2, 'padding1'), // 2 bytes
+  u64('recentEpoch'), // 8 bytes
+  blob(96, 'padding2'), // 96 bytes
+]);
+
+export type IDynTickArrayLayout = ReturnType<typeof DynTickArrayLayout.decode>;
+
+/**
+ * @description Price tick array bitmap extension layout
+ *
+ * @example https://solscan.io/account/DoPuiZfJu7sypqwR4eiU7C5TMcmmiFoU4HaF5SoD8mRy#anchorData
+ */
+export const TickArrayBitmapExtensionLayout = struct([
+  blob(8),
+  publicKey('poolId'),
+  seq(seq(u64(), 8), EXTENSION_TICKARRAY_BITMAP_SIZE, 'positiveTickArrayBitmap'),
+  seq(seq(u64(), 8), EXTENSION_TICKARRAY_BITMAP_SIZE, 'negativeTickArrayBitmap'),
+]);
+
+export const SPLTokenAccountLayout = struct([
+  publicKey('mint'),
+  publicKey('owner'),
+  u64('amount'),
+  u32('delegateOption'),
+  publicKey('delegate'),
+  u8('state'),
+  u32('isNativeOption'),
+  u64('isNative'),
+  u64('delegatedAmount'),
+  u32('closeAuthorityOption'),
+  publicKey('closeAuthority'),
+]);

+ 211 - 0
src/lib/clmm-sdk/src/instructions/libs/marshmallow/bufferLayout.ts

@@ -0,0 +1,211 @@
+import {
+  bits as _bits,
+  BitStructure as _BitStructure,
+  blob as _blob,
+  Blob as _Blob,
+  cstr as _cstr,
+  f32 as _f32,
+  f32be as _f32be,
+  f64 as _f64,
+  f64be as _f64be,
+  greedy as _greedy,
+  Layout as _Layout,
+  ns64 as _ns64,
+  ns64be as _ns64be,
+  nu64 as _nu64,
+  nu64be as _nu64be,
+  offset as _offset,
+  s16 as _s16,
+  s16be as _s16be,
+  s24 as _s24,
+  s24be as _s24be,
+  s32 as _s32,
+  s32be as _s32be,
+  s40 as _s40,
+  s40be as _s40be,
+  s48 as _s48,
+  s48be as _s48be,
+  s8 as _s8,
+  seq as _seq,
+  struct as _struct,
+  Structure as _Structure,
+  u16 as _u16,
+  u16be as _u16be,
+  u24 as _u24,
+  u24be as _u24be,
+  u32 as _u32,
+  u32be as _u32be,
+  u40 as _u40,
+  u40be as _u40be,
+  u48 as _u48,
+  u48be as _u48be,
+  u8 as _u8,
+  UInt as _UInt,
+  union as _union,
+  Union as _Union,
+  unionLayoutDiscriminator as _unionLayoutDiscriminator,
+  utf8 as _utf8,
+} from '@solana/buffer-layout';
+
+//#region ------------------- Layout -------------------
+export interface Layout<T = any, P = ''> {
+  span: number;
+  property?: P;
+  decode(b: Buffer, offset?: number): T;
+  encode(src: T, b: Buffer, offset?: number): number;
+  getSpan(b: Buffer, offset?: number): number;
+  replicate<AP extends string>(name: AP): Layout<T, AP>;
+}
+export interface LayoutConstructor {
+  new <T, P>(): Layout<T, P>; // for class extends syntex
+  new <T, P>(span?: T, property?: P): Layout<T, P>;
+  readonly prototype: Layout;
+}
+export const Layout = _Layout as unknown as LayoutConstructor;
+//#endregion
+
+//#region ------------------- Structure -------------------
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export interface Structure<T = any, P = '', DecodeSchema extends { [key: string]: any } = any>
+  extends Layout<DecodeSchema, P> {
+  span: number;
+  decode(b: Buffer, offset?: number): DecodeSchema;
+  layoutFor<AP extends string>(property: AP): Layout<DecodeSchema[AP]>;
+  offsetOf<AP extends string>(property: AP): number;
+}
+interface StructureConstructor {
+  new <T = any, P = '', DecodeSchema extends { [key: string]: any } = any>(): Structure<T, P, DecodeSchema>;
+  new <T = any, P = '', DecodeSchema extends { [key: string]: any } = any>(
+    fields: T,
+    property?: P,
+    decodePrefixes?: boolean
+  ): Structure<T, P, DecodeSchema>;
+}
+export const Structure = _Structure as unknown as StructureConstructor;
+//#endregion
+
+//#region ------------------- Union -------------------
+export interface Union<UnionSchema extends { [key: string]: any } = any> extends Layout {
+  registry: object;
+  decode(b: Buffer, offset?: number): Partial<UnionSchema>;
+  addVariant(
+    variant: number,
+    layout: Structure<any, any, Partial<UnionSchema>> | Layout<any, keyof UnionSchema>,
+    property?: string
+  ): any /* TEMP: code in Layout.js 1809 */;
+}
+interface UnionConstructor {
+  new <UnionSchema extends { [key: string]: any } = any>(): Union<UnionSchema>;
+  new <UnionSchema extends { [key: string]: any } = any>(
+    discr: Layout<any, any>,
+    defaultLayout: Layout<any, any>,
+    property?: string
+  ): Union<UnionSchema>;
+}
+export const Union = _Union as unknown as UnionConstructor;
+//#endregion
+
+//#region ------------------- BitStructure -------------------
+export type BitStructure<T = unknown /* TEMP */, P = ''> = Layout<T, P>;
+interface BitStructureConstructor {
+  new (...params: any[]): BitStructure;
+}
+export const BitStructure = _BitStructure as BitStructureConstructor;
+//#endregion
+
+//#region ------------------- UInt -------------------
+export type UInt<T = any, P = ''> = Layout<T, P>;
+interface UIntConstructor {
+  new <T, P>(span?: T, property?: P): UInt<T, P>;
+}
+export const UInt = _UInt as UIntConstructor;
+//#endregion
+
+//#region ------------------- Blob -------------------
+export type Blob<P extends string = ''> = Layout<Buffer, P>;
+interface BlobConstructor {
+  new (...params: ConstructorParameters<LayoutConstructor>): Blob;
+}
+export const Blob = _Blob as unknown as BlobConstructor;
+//#endregion
+
+export const greedy = _greedy as <P extends string = ''>(elementSpan?: number, property?: P) => Layout<number, P>;
+export const u8 = _u8 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const u16 = _u16 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const u24 = _u24 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const u32 = _u32 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const u40 = _u40 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const u48 = _u48 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const nu64 = _nu64 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const u16be = _u16be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const u24be = _u24be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const u32be = _u32be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const u40be = _u40be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const u48be = _u48be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const nu64be = _nu64be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const s8 = _s8 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const s16 = _s16 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const s24 = _s24 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const s32 = _s32 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const s40 = _s40 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const s48 = _s48 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const ns64 = _ns64 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const s16be = _s16be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const s24be = _s24be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const s32be = _s32be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const s40be = _s40be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const s48be = _s48be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const ns64be = _ns64be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const f32 = _f32 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const f32be = _f32be as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const f64 = _f64 as <P extends string = ''>(property?: P) => Layout<number, P>;
+export const f64be = _f64be as <P extends string = ''>(property?: P) => Layout<number, P>;
+
+export const struct = _struct as <T, P extends string = ''>(
+  fields: T,
+  property?: P,
+  decodePrefixes?: boolean
+) => T extends Layout<infer Value, infer Property>[]
+  ? Structure<
+      Value,
+      P,
+      {
+        [K in Exclude<Extract<Property, string>, ''>]: Extract<T[number], Layout<any, K>> extends Layout<infer V, any>
+          ? V
+          : any;
+      }
+    >
+  : any;
+
+export const seq = _seq as unknown as <T, P>(
+  elementLayout: Layout<T, string>,
+  count: number | Layout<number, string>,
+  property?: P
+) => Layout<T[]>;
+export const union = _union as <UnionSchema extends { [key: string]: any } = any>(
+  discr: Layout<any, any>,
+  defaultLayout?: any,
+  property?: string
+) => Union<UnionSchema>;
+export const unionLayoutDiscriminator = _unionLayoutDiscriminator as <P extends string = ''>(
+  layout: Layout<any, P>,
+  property?: P
+) => any;
+export const blob = _blob as unknown as <P extends string = ''>(
+  length: number | Layout<number, P>,
+  property?: P
+) => Blob<P>;
+export const cstr = _cstr as <P extends string = ''>(property?: P) => Layout<string, P>;
+export const utf8 = _utf8 as <P extends string = ''>(maxSpan: number, property?: P) => Layout<string, P>;
+export const bits = _bits as unknown as <T, P extends string = ''>(
+  word: Layout<T>,
+  msb?: boolean,
+  property?: P
+) => BitStructure<T, P>;
+export const offset = _offset as unknown as <T, P extends string = ''>(
+  layout: Layout<T, P>,
+  offset?: number,
+  property?: P
+) => Layout<T, P>;
+
+export type GetStructureSchema<T extends Structure> = T extends Structure<any, any, infer S> ? S : unknown;

+ 374 - 0
src/lib/clmm-sdk/src/instructions/libs/marshmallow/index.ts

@@ -0,0 +1,374 @@
+import { PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+
+import {
+  bits,
+  blob,
+  Blob,
+  Layout,
+  offset as _offset,
+  seq as _seq,
+  Structure as _Structure,
+  u32 as _u32,
+  u8 as _u8,
+  UInt,
+  union as _union,
+  Union as _Union,
+} from './bufferLayout.js';
+
+export * from './bufferLayout.js';
+export { blob };
+
+export class BNLayout<P extends string = ''> extends Layout<BN, P> {
+  blob: Layout<Buffer>;
+  signed: boolean;
+
+  constructor(span: number, signed: boolean, property?: P) {
+    super(span, property);
+    this.blob = blob(span);
+    this.signed = signed;
+  }
+
+  /** @override */
+  override decode(b: Buffer, offset = 0): BN {
+    const num = new BN(this.blob.decode(b, offset), 10, 'le');
+    if (this.signed) {
+      return num.fromTwos(this.span * 8).clone();
+    }
+    return num;
+  }
+
+  /** @override */
+  override encode(src: BN, b: Buffer, offset = 0): number {
+    if (typeof src === 'number') src = new BN(src); // src will pass a number accidently in union
+    if (this.signed) {
+      src = src.toTwos(this.span * 8);
+    }
+    return this.blob.encode(src.toArrayLike(Buffer, 'le', this.span), b, offset);
+  }
+}
+
+export class WideBits<P extends string = ''> extends Layout<Record<string, boolean>, P> {
+  _lower: any;
+  _upper: any;
+  constructor(property?: P) {
+    //@ts-expect-error type wrong for super()'s type different from extends , but it desn't matter
+    super(8, property);
+    this._lower = bits(_u32(), false);
+    this._upper = bits(_u32(), false);
+  }
+
+  addBoolean(property: string): void {
+    if (this._lower.fields.length < 32) {
+      this._lower.addBoolean(property);
+    } else {
+      this._upper.addBoolean(property);
+    }
+  }
+
+  override decode(b: Buffer, offset = 0): Record<string, boolean> {
+    const lowerDecoded = this._lower.decode(b, offset);
+    const upperDecoded = this._upper.decode(b, offset + this._lower.span);
+    return { ...lowerDecoded, ...upperDecoded };
+  }
+
+  override encode(src: any /* TEMP */, b: Buffer, offset = 0): any {
+    return this._lower.encode(src, b, offset) + this._upper.encode(src, b, offset + this._lower.span);
+  }
+}
+
+export function u8<P extends string = ''>(property?: P): UInt<number, P> {
+  return new UInt(1, property);
+}
+
+export function u32<P extends string = ''>(property?: P): UInt<number, P> {
+  return new UInt(4, property);
+}
+
+export function u64<P extends string = ''>(property?: P): BNLayout<P> {
+  return new BNLayout(8, false, property);
+}
+
+export function u128<P extends string = ''>(property?: P): BNLayout<P> {
+  return new BNLayout(16, false, property);
+}
+
+export function i8<P extends string = ''>(property?: P): BNLayout<P> {
+  return new BNLayout(1, true, property);
+}
+
+export function i64<P extends string = ''>(property?: P): BNLayout<P> {
+  return new BNLayout(8, true, property);
+}
+
+export function i128<P extends string = ''>(property?: P): BNLayout<P> {
+  return new BNLayout(16, true, property);
+}
+
+export class WrappedLayout<T, U, P extends string = ''> extends Layout<U, P> {
+  layout: Layout<T>;
+  decoder: (data: T) => U;
+  encoder: (src: U) => T;
+
+  constructor(layout: Layout<T>, decoder: (data: T) => U, encoder: (src: U) => T, property?: P) {
+    //@ts-expect-error type wrong for super()'s type different from extends , but it desn't matter
+    super(layout.span, property);
+    this.layout = layout;
+    this.decoder = decoder;
+    this.encoder = encoder;
+  }
+
+  override decode(b: Buffer, offset?: number): U {
+    return this.decoder(this.layout.decode(b, offset));
+  }
+
+  override encode(src: U, b: Buffer, offset?: number): number {
+    return this.layout.encode(this.encoder(src), b, offset);
+  }
+
+  override getSpan(b: Buffer, offset?: number): number {
+    return this.layout.getSpan(b, offset);
+  }
+}
+
+export function publicKey<P extends string = ''>(property?: P): Layout<PublicKey, P> {
+  return new WrappedLayout(
+    blob(32),
+    (b: Buffer) => new PublicKey(b),
+    (key: PublicKey) => key.toBuffer(),
+    property
+  );
+}
+
+export class OptionLayout<T, P> extends Layout<T | null, P> {
+  layout: Layout<T>;
+  discriminator: Layout<number>;
+
+  constructor(layout: Layout<T>, property?: P) {
+    //@ts-expect-error type wrong for super()'s type different from extends , but it desn't matter
+    super(-1, property);
+    this.layout = layout;
+    this.discriminator = _u8();
+  }
+
+  override encode(src: T | null, b: Buffer, offset = 0): number {
+    if (src === null || src === undefined) {
+      return this.discriminator.encode(0, b, offset);
+    }
+    this.discriminator.encode(1, b, offset);
+    return this.layout.encode(src, b, offset + 1) + 1;
+  }
+
+  override decode(b: Buffer, offset = 0): T | null {
+    const discriminator = this.discriminator.decode(b, offset);
+    if (discriminator === 0) {
+      return null;
+    } else if (discriminator === 1) {
+      return this.layout.decode(b, offset + 1);
+    }
+    throw new Error('Invalid option ' + this.property);
+  }
+
+  override getSpan(b: Buffer, offset = 0): number {
+    const discriminator = this.discriminator.decode(b, offset);
+    if (discriminator === 0) {
+      return 1;
+    } else if (discriminator === 1) {
+      return this.layout.getSpan(b, offset + 1) + 1;
+    }
+    throw new Error('Invalid option ' + this.property);
+  }
+}
+
+export function option<T, P extends string = ''>(layout: Layout<T>, property?: P): Layout<T | null, P> {
+  return new OptionLayout<T, P>(layout, property);
+}
+
+export function bool<P extends string = ''>(property?: P): Layout<boolean, P> {
+  return new WrappedLayout(_u8(), decodeBool, encodeBool, property);
+}
+
+export function decodeBool(value: number): boolean {
+  if (value === 0) {
+    return false;
+  } else if (value === 1) {
+    return true;
+  }
+  throw new Error('Invalid bool: ' + value);
+}
+
+export function encodeBool(value: boolean): number {
+  return value ? 1 : 0;
+}
+
+export function vec<T, P extends string = ''>(elementLayout: Layout<T>, property?: P): Layout<T[], P> {
+  const length = _u32('length');
+  const layout: Layout<{ values: T[] }> = struct([
+    length,
+    seq(elementLayout, _offset(length, -length.span), 'values'),
+  ]) as any; // Something I don't know
+  return new WrappedLayout(
+    layout,
+    ({ values }) => values,
+    (values) => ({ values }),
+    property
+  );
+}
+
+export function tagged<T, P extends string = ''>(tag: BN, layout: Layout<T>, property?: P): Layout<T, P> {
+  const wrappedLayout: Layout<{ tag: BN; data: T }> = struct([u64('tag'), layout.replicate('data')]) as any; // Something I don't know
+
+  function decodeTag({ tag: receivedTag, data }: { tag: BN; data: T }): T {
+    if (!receivedTag.eq(tag)) {
+      throw new Error('Invalid tag, expected: ' + tag.toString('hex') + ', got: ' + receivedTag.toString('hex'));
+    }
+    return data;
+  }
+
+  return new WrappedLayout(wrappedLayout, decodeTag, (data) => ({ tag, data }), property);
+}
+
+export function vecU8<P extends string = ''>(property?: P): Layout<Buffer, P> {
+  const length = _u32('length');
+  const layout: Layout<{ data: Buffer }> = struct([length, blob(_offset(length, -length.span), 'data')]) as any; // Something I don't know
+  return new WrappedLayout(
+    layout,
+    ({ data }) => data,
+    (data) => ({ data }),
+    property
+  );
+}
+
+export function str<P extends string = ''>(property?: P): Layout<string, P> {
+  return new WrappedLayout(
+    vecU8(),
+    (data) => data.toString('utf-8'),
+    (s) => Buffer.from(s, 'utf-8'),
+    property
+  );
+}
+
+export interface EnumLayout<T, P extends string = ''> extends Layout<T, P> {
+  registry: Record<string, Layout<any>>;
+}
+
+export function rustEnum<T, P extends string = ''>(variants: Layout<any>[], property?: P): EnumLayout<T, P> {
+  const unionLayout = _union(_u8(), property);
+  variants.forEach((variant, index) => unionLayout.addVariant(index, variant, variant.property));
+  return unionLayout as any; // ?why use UnionLayout? This must be a fault
+}
+
+export function array<T, P extends string = ''>(
+  elementLayout: Layout<T>,
+  length: number,
+  property?: P
+): Layout<T[], P> {
+  const layout = struct([seq(elementLayout, length, 'values')]) as any as Layout<{ values: T[] }>; // Something I don't know
+  return new WrappedLayout(
+    layout,
+    ({ values }) => values,
+    (values) => ({ values }),
+    property
+  );
+}
+
+export class Structure<T, P, D extends { [key: string]: any }> extends _Structure<T, P, D> {
+  /** @override */
+  override decode(b: Buffer, offset?: number): D {
+    return super.decode(b, offset);
+  }
+}
+
+export function struct<T, P extends string = ''>(
+  fields: T,
+  property?: P,
+  decodePrefixes?: boolean
+): T extends Layout<infer Value, infer Property>[]
+  ? Structure<
+      Value,
+      P,
+      {
+        [K in Exclude<Extract<Property, string>, ''>]: Extract<T[number], Layout<any, K>> extends Layout<infer V, any>
+          ? V
+          : any;
+      }
+    >
+  : any {
+  //@ts-expect-error this type is not quite satisfied the define, but, never no need to worry about.
+  return new Structure(fields, property, decodePrefixes);
+}
+
+export type GetLayoutSchemaFromStructure<T extends Structure<any, any, any>> = T extends Structure<any, any, infer S>
+  ? S
+  : any;
+export type GetStructureFromLayoutSchema<S extends { [key: string]: any }> = Structure<any, any, S>;
+
+export class Union<Schema extends { [key: string]: any }> extends _Union<Schema> {
+  encodeInstruction(instruction: any): Buffer {
+    const instructionMaxSpan = Math.max(...Object.values(this.registry).map((r) => r.span));
+    const b = Buffer.alloc(instructionMaxSpan);
+    return b.slice(0, this.encode(instruction, b));
+  }
+
+  decodeInstruction(instruction: any): Partial<Schema> {
+    return this.decode(instruction);
+  }
+}
+export function union<UnionSchema extends { [key: string]: any } = any>(
+  discr: any,
+  defaultLayout?: any,
+  property?: string
+): Union<UnionSchema> {
+  return new Union(discr, defaultLayout, property);
+}
+
+class Zeros extends Blob {
+  override decode(b: Buffer, offset: number): Buffer {
+    const slice = super.decode(b, offset);
+    if (!slice.every((v) => v === 0)) {
+      throw new Error('nonzero padding bytes');
+    }
+    return slice;
+  }
+}
+
+export function zeros(length: number): Zeros {
+  return new Zeros(length);
+}
+
+export function seq<T, P extends string = '', AnotherP extends string = ''>(
+  elementLayout: Layout<T, P>,
+  count: number | BN | Layout<BN | number, P>,
+  property?: AnotherP
+): Layout<T[], AnotherP> {
+  let parsedCount: number;
+  const superCount =
+    typeof count === 'number'
+      ? count
+      : BN.isBN(count)
+      ? count.toNumber()
+      : new Proxy(count as unknown as Layout<number> /* pretend to be Layout<number> */, {
+          get(target, property): any {
+            if (!parsedCount) {
+              // get count in targetLayout. note that count may be BN
+              const countProperty = Reflect.get(target, 'count');
+
+              // let targetLayout's  property:count be a number
+              parsedCount = BN.isBN(countProperty) ? countProperty.toNumber() : countProperty;
+
+              // record the count
+              Reflect.set(target, 'count', parsedCount);
+            }
+            return Reflect.get(target, property);
+          },
+          set(target, property, value): any {
+            if (property === 'count') {
+              parsedCount = value;
+            }
+            return Reflect.set(target, property, value);
+          },
+        });
+
+  // @ts-expect-error force type
+  return _seq(elementLayout, superCount, property);
+}

+ 63 - 0
src/lib/clmm-sdk/src/instructions/models.ts

@@ -0,0 +1,63 @@
+import { PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+
+import { IPoolLayout } from './layout.js';
+
+// Get transfer amount and fee
+export interface IGetTransferAmountFee {
+  amount: BN;
+  fee: BN | undefined;
+  expirationTime: number | undefined;
+}
+
+// Transfer fee configuration
+export interface TransferFeeDataBaseType {
+  transferFeeConfigAuthority: string;
+  withdrawWithheldAuthority: string;
+  withheldAmount: string;
+  olderTransferFee: {
+    epoch: string;
+    maximumFee: string;
+    transferFeeBasisPoints: number;
+  };
+  newerTransferFee: {
+    epoch: string;
+    maximumFee: string;
+    transferFeeBasisPoints: number;
+  };
+}
+
+// Token information
+export interface ITokenInfo {
+  decimals: number;
+  address: string;
+  programId: string;
+  // extensions: {
+  //   feeConfig: TransferFeeDataBaseType;
+  // };
+}
+
+export type IPoolLayoutWithId = IPoolLayout & {
+  currentPrice: number;
+  programId: PublicKey;
+  poolId: PublicKey;
+};
+
+export interface IAccountInfo {
+  pubkey: PublicKey;
+  isSigner: boolean;
+  isWritable: boolean;
+}
+
+// Pool information (to be completed and improved)
+// export interface IPoolInfo {
+//   id: string;
+//   programId: string;
+//   tickSpacing: number;
+//   tickCurrent: number;
+//   sqrtPriceX64: BN;
+//   mintA: ITokenInfo;
+//   mintB: ITokenInfo;
+//   vaultA: PublicKey;
+//   vaultB: PublicKey;
+// }

+ 131 - 0
src/lib/clmm-sdk/src/instructions/pda.ts

@@ -0,0 +1,131 @@
+import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
+import { PublicKey } from '@solana/web3.js';
+
+import { i32ToBytes, u16ToBytes } from './utils/binaryUtils.js';
+import { findProgramAddress } from './utils/index.js';
+
+export const AMM_CONFIG_SEED = Buffer.from('amm_config', 'utf8');
+export const POOL_SEED = Buffer.from('pool', 'utf8');
+export const POOL_VAULT_SEED = Buffer.from('pool_vault', 'utf8');
+export const POOL_REWARD_VAULT_SEED = Buffer.from('pool_reward_vault', 'utf8');
+export const POSITION_SEED = Buffer.from('position', 'utf8');
+export const TICK_ARRAY_SEED = Buffer.from('tick_array', 'utf8');
+export const OPERATION_SEED = Buffer.from('operation', 'utf8');
+export const POOL_TICK_ARRAY_BITMAP_SEED = Buffer.from('pool_tick_array_bitmap_extension', 'utf8');
+export const OBSERVATION_SEED = Buffer.from('observation', 'utf8');
+export const SUPPORT_MINT_SEED = Buffer.from('support_mint', 'utf8');
+
+export function getPdaAmmConfigId(
+  programId: PublicKey,
+  index: number
+): {
+  publicKey: PublicKey;
+  nonce: number;
+} {
+  return findProgramAddress([AMM_CONFIG_SEED, u16ToBytes(index)], programId);
+}
+
+export function getPdaPoolId(
+  programId: PublicKey,
+  ammConfigId: PublicKey,
+  mintA: PublicKey,
+  mintB: PublicKey
+): {
+  publicKey: PublicKey;
+  nonce: number;
+} {
+  return findProgramAddress([POOL_SEED, ammConfigId.toBuffer(), mintA.toBuffer(), mintB.toBuffer()], programId);
+}
+
+export function getPdaPoolVaultId(
+  programId: PublicKey,
+  poolId: PublicKey,
+  vaultMint: PublicKey
+): {
+  publicKey: PublicKey;
+  nonce: number;
+} {
+  return findProgramAddress([POOL_VAULT_SEED, poolId.toBuffer(), vaultMint.toBuffer()], programId);
+}
+
+export function getPdaTickArrayAddress(
+  programId: PublicKey,
+  poolId: PublicKey,
+  startIndex: number
+): {
+  publicKey: PublicKey;
+  nonce: number;
+} {
+  return findProgramAddress([TICK_ARRAY_SEED, poolId.toBuffer(), i32ToBytes(startIndex)], programId);
+}
+
+export function getPdaProtocolPositionAddress(
+  programId: PublicKey,
+  poolId: PublicKey,
+  tickLower: number,
+  tickUpper: number
+): {
+  publicKey: PublicKey;
+  nonce: number;
+} {
+  return findProgramAddress(
+    [POSITION_SEED, poolId.toBuffer(), i32ToBytes(tickLower), i32ToBytes(tickUpper)],
+    programId
+  );
+}
+
+export function getPdaPersonalPositionAddress(
+  programId: PublicKey,
+  nftMint: PublicKey
+): {
+  publicKey: PublicKey;
+  nonce: number;
+} {
+  return findProgramAddress([POSITION_SEED, nftMint.toBuffer()], programId);
+}
+
+export function getPdaExBitmapAccount(
+  programId: PublicKey,
+  poolId: PublicKey
+): {
+  publicKey: PublicKey;
+  nonce: number;
+} {
+  return findProgramAddress([POOL_TICK_ARRAY_BITMAP_SEED, poolId.toBuffer()], programId);
+}
+
+export function getPdaObservationAccount(
+  programId: PublicKey,
+  poolId: PublicKey
+): {
+  publicKey: PublicKey;
+  nonce: number;
+} {
+  return findProgramAddress([OBSERVATION_SEED, poolId.toBuffer()], programId);
+}
+
+// New PDA seed, used to support mint extensions (e.g. token 2022)
+export function getPdaMintExAccount(
+  programId: PublicKey,
+  mintAddress: PublicKey
+): {
+  publicKey: PublicKey;
+  nonce: number;
+} {
+  return findProgramAddress([SUPPORT_MINT_SEED, mintAddress.toBuffer()], programId);
+}
+
+// Get ATA address
+export function getATAAddress(
+  owner: PublicKey,
+  mint: PublicKey,
+  programId?: PublicKey
+): {
+  publicKey: PublicKey;
+  nonce: number;
+} {
+  return findProgramAddress(
+    [owner.toBuffer(), (programId ?? TOKEN_PROGRAM_ID).toBuffer(), mint.toBuffer()],
+    new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL')
+  );
+}

+ 7242 - 0
src/lib/clmm-sdk/src/instructions/target/idl/byreal_amm_v3.json

@@ -0,0 +1,7242 @@
+{
+  "address": "REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2",
+  "metadata": {
+    "name": "byreal_clmm",
+    "version": "0.1.0",
+    "spec": "0.1.0",
+    "description": "Anchor client and source for Byreal concentrated liquidity AMM"
+  },
+  "instructions": [
+    {
+      "name": "claim_offchain_reward",
+      "docs": [
+        "claim offchain reward from the pool"
+      ],
+      "discriminator": [
+        195,
+        87,
+        221,
+        149,
+        141,
+        195,
+        146,
+        19
+      ],
+      "accounts": [
+        {
+          "name": "claimer",
+          "docs": [
+            "the address who claim the offchain reward."
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "authority",
+          "docs": [
+            "The authority make decision that who can claim the offchain reward."
+          ],
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "Initialize amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "pool_id",
+          "docs": [
+            "the pool id, which is the pool state account."
+          ],
+          "relations": [
+            "reward_config"
+          ]
+        },
+        {
+          "name": "token_mint"
+        },
+        {
+          "name": "claimer_token_account",
+          "docs": [
+            ""
+          ],
+          "writable": true
+        },
+        {
+          "name": "reward_vault_token_account",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "account",
+                "path": "reward_config"
+              },
+              {
+                "kind": "account",
+                "path": "token_program"
+              },
+              {
+                "kind": "account",
+                "path": "token_mint"
+              }
+            ],
+            "program": {
+              "kind": "const",
+              "value": [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ]
+            }
+          }
+        },
+        {
+          "name": "reward_config",
+          "docs": [
+            "The offchain reward config account, it also is the reward vault account."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  111,
+                  102,
+                  102,
+                  99,
+                  104,
+                  97,
+                  105,
+                  110,
+                  95,
+                  114,
+                  101,
+                  119,
+                  97,
+                  114,
+                  100
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_id"
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "Spl token program or token program 2022"
+          ]
+        },
+        {
+          "name": "associated_token_program",
+          "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+        }
+      ],
+      "args": [
+        {
+          "name": "amount",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "close_position",
+      "docs": [
+        "Close the user's position and NFT account. If the NFT mint belongs to token2022, it will also be closed and the funds returned to the NFT owner.",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` - The context of accounts",
+        ""
+      ],
+      "discriminator": [
+        123,
+        134,
+        81,
+        0,
+        49,
+        68,
+        98,
+        98
+      ],
+      "accounts": [
+        {
+          "name": "nft_owner",
+          "docs": [
+            "The position nft owner"
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "position_nft_mint",
+          "docs": [
+            "Mint address bound to the personal position."
+          ],
+          "writable": true
+        },
+        {
+          "name": "position_nft_account",
+          "docs": [
+            "User token account where position NFT be minted to"
+          ],
+          "writable": true
+        },
+        {
+          "name": "personal_position",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  115,
+                  105,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "position_nft_mint"
+              }
+            ]
+          }
+        },
+        {
+          "name": "system_program",
+          "docs": [
+            "System program to close the position state account"
+          ],
+          "address": "11111111111111111111111111111111"
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "Token/Token2022 program to close token/mint account"
+          ]
+        }
+      ],
+      "args": []
+    },
+    {
+      "name": "collect_fund_fee",
+      "docs": [
+        "Collect the fund fee accrued to the pool",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` - The context of accounts",
+        "* `amount_0_requested` - The maximum amount of token_0 to send, can be 0 to collect fees in only token_1",
+        "* `amount_1_requested` - The maximum amount of token_1 to send, can be 0 to collect fees in only token_0",
+        ""
+      ],
+      "discriminator": [
+        167,
+        138,
+        78,
+        149,
+        223,
+        194,
+        6,
+        126
+      ],
+      "accounts": [
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions.",
+            "anyone can collect fee, but only fee-manager in admin group can receive fee"
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "pool_state",
+          "docs": [
+            "Pool state stores accumulated protocol fee amount"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_0",
+          "docs": [
+            "The address that holds pool tokens for token_0"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_1",
+          "docs": [
+            "The address that holds pool tokens for token_1"
+          ],
+          "writable": true
+        },
+        {
+          "name": "vault_0_mint",
+          "docs": [
+            "The mint of token vault 0"
+          ]
+        },
+        {
+          "name": "vault_1_mint",
+          "docs": [
+            "The mint of token vault 1"
+          ]
+        },
+        {
+          "name": "recipient_token_account_0",
+          "docs": [
+            "The address that receives the collected token_0 protocol fees"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "account",
+                "path": "admin_group.fee_keeper",
+                "account": "AmmAdminGroup"
+              },
+              {
+                "kind": "const",
+                "value": [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "vault_0_mint"
+              }
+            ],
+            "program": {
+              "kind": "const",
+              "value": [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ]
+            }
+          }
+        },
+        {
+          "name": "recipient_token_account_1",
+          "docs": [
+            "The address that receives the collected token_1 protocol fees"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "account",
+                "path": "admin_group.fee_keeper",
+                "account": "AmmAdminGroup"
+              },
+              {
+                "kind": "const",
+                "value": [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "vault_1_mint"
+              }
+            ],
+            "program": {
+              "kind": "const",
+              "value": [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ]
+            }
+          }
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "The SPL program to perform token transfers"
+          ],
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        },
+        {
+          "name": "token_program_2022",
+          "docs": [
+            "The SPL program 2022 to perform token transfers"
+          ],
+          "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
+        },
+        {
+          "name": "associated_token_program",
+          "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+        }
+      ],
+      "args": [
+        {
+          "name": "amount_0_requested",
+          "type": "u64"
+        },
+        {
+          "name": "amount_1_requested",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "collect_protocol_fee",
+      "docs": [
+        "Collect the protocol fee accrued to the pool",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` - The context of accounts",
+        "* `amount_0_requested` - The maximum amount of token_0 to send, can be 0 to collect fees in only token_1",
+        "* `amount_1_requested` - The maximum amount of token_1 to send, can be 0 to collect fees in only token_0",
+        ""
+      ],
+      "discriminator": [
+        136,
+        136,
+        252,
+        221,
+        194,
+        66,
+        126,
+        89
+      ],
+      "accounts": [
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions.",
+            "anyone can collect fee, but only fee-manager in admin group can receive fee"
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "pool_state",
+          "docs": [
+            "Pool state stores accumulated protocol fee amount"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_0",
+          "docs": [
+            "The address that holds pool tokens for token_0"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_1",
+          "docs": [
+            "The address that holds pool tokens for token_1"
+          ],
+          "writable": true
+        },
+        {
+          "name": "vault_0_mint",
+          "docs": [
+            "The mint of token vault 0"
+          ]
+        },
+        {
+          "name": "vault_1_mint",
+          "docs": [
+            "The mint of token vault 1"
+          ]
+        },
+        {
+          "name": "recipient_token_account_0",
+          "docs": [
+            "The address that receives the collected token_0 protocol fees"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "account",
+                "path": "admin_group.fee_keeper",
+                "account": "AmmAdminGroup"
+              },
+              {
+                "kind": "const",
+                "value": [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "vault_0_mint"
+              }
+            ],
+            "program": {
+              "kind": "const",
+              "value": [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ]
+            }
+          }
+        },
+        {
+          "name": "recipient_token_account_1",
+          "docs": [
+            "The address that receives the collected token_1 protocol fees"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "account",
+                "path": "admin_group.fee_keeper",
+                "account": "AmmAdminGroup"
+              },
+              {
+                "kind": "const",
+                "value": [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "vault_1_mint"
+              }
+            ],
+            "program": {
+              "kind": "const",
+              "value": [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ]
+            }
+          }
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "The SPL program to perform token transfers"
+          ],
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        },
+        {
+          "name": "token_program_2022",
+          "docs": [
+            "The SPL program 2022 to perform token transfers"
+          ],
+          "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
+        },
+        {
+          "name": "associated_token_program",
+          "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+        }
+      ],
+      "args": [
+        {
+          "name": "amount_0_requested",
+          "type": "u64"
+        },
+        {
+          "name": "amount_1_requested",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "collect_remaining_rewards",
+      "docs": [
+        "Collect remaining reward token for reward founder",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx`- The context of accounts",
+        "* `reward_index` - the index to reward info",
+        ""
+      ],
+      "discriminator": [
+        18,
+        237,
+        166,
+        197,
+        34,
+        16,
+        213,
+        144
+      ],
+      "accounts": [
+        {
+          "name": "reward_funder",
+          "docs": [
+            "The founder who init reward info previously"
+          ],
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "funder_token_account",
+          "docs": [
+            "The funder's reward token account"
+          ],
+          "writable": true
+        },
+        {
+          "name": "pool_state",
+          "docs": [
+            "Set reward for this pool"
+          ],
+          "writable": true
+        },
+        {
+          "name": "reward_token_vault",
+          "docs": [
+            "Reward vault transfer remaining token to founder token account"
+          ]
+        },
+        {
+          "name": "reward_vault_mint",
+          "docs": [
+            "The mint of reward token vault"
+          ]
+        },
+        {
+          "name": "token_program",
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        },
+        {
+          "name": "token_program_2022",
+          "docs": [
+            "Token program 2022"
+          ],
+          "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
+        },
+        {
+          "name": "memo_program",
+          "docs": [
+            "memo program"
+          ],
+          "address": "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
+        }
+      ],
+      "args": [
+        {
+          "name": "reward_index",
+          "type": "u8"
+        }
+      ]
+    },
+    {
+      "name": "create_amm_config",
+      "docs": [
+        "# Arguments",
+        "",
+        "* `ctx`- The accounts needed by instruction.",
+        "* `index` - The index of amm config, there may be multiple config.",
+        "* `tick_spacing` - The tickspacing binding with config, cannot be changed.",
+        "* `trade_fee_rate` - Trade fee rate, can be changed.",
+        "* `protocol_fee_rate` - The rate of protocol fee within trade fee.",
+        "* `fund_fee_rate` - The rate of fund fee within trade fee.",
+        ""
+      ],
+      "discriminator": [
+        137,
+        52,
+        237,
+        212,
+        215,
+        117,
+        108,
+        104
+      ],
+      "accounts": [
+        {
+          "name": "owner",
+          "docs": [
+            "Address to be set as normal manager in admin group."
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "amm_config",
+          "docs": [
+            "Initialize config state account to store protocol owner address and fee rates."
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  109,
+                  109,
+                  95,
+                  99,
+                  111,
+                  110,
+                  102,
+                  105,
+                  103
+                ]
+              },
+              {
+                "kind": "arg",
+                "path": "index"
+              }
+            ]
+          }
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": [
+        {
+          "name": "index",
+          "type": "u16"
+        },
+        {
+          "name": "tick_spacing",
+          "type": "u16"
+        },
+        {
+          "name": "trade_fee_rate",
+          "type": "u32"
+        },
+        {
+          "name": "protocol_fee_rate",
+          "type": "u32"
+        },
+        {
+          "name": "fund_fee_rate",
+          "type": "u32"
+        }
+      ]
+    },
+    {
+      "name": "create_operation_account",
+      "docs": [
+        "Creates an operation account for the program",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx`- The context of accounts",
+        ""
+      ],
+      "discriminator": [
+        63,
+        87,
+        148,
+        33,
+        109,
+        35,
+        8,
+        104
+      ],
+      "accounts": [
+        {
+          "name": "owner",
+          "docs": [
+            "Address to be set as operation account owner."
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "operation_state",
+          "docs": [
+            "Initialize operation state account to store operation owner address and white list mint."
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  111,
+                  112,
+                  101,
+                  114,
+                  97,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": []
+    },
+    {
+      "name": "create_pool",
+      "docs": [
+        "Creates a pool for the given token pair and the initial price",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx`- The context of accounts",
+        "* `sqrt_price_x64` - the initial sqrt price (amount_token_1 / amount_token_0) of the pool as a Q64.64",
+        "Note: The open_time must be smaller than the current block_timestamp on chain."
+      ],
+      "discriminator": [
+        233,
+        146,
+        209,
+        142,
+        207,
+        104,
+        64,
+        188
+      ],
+      "accounts": [
+        {
+          "name": "pool_creator",
+          "docs": [
+            "Address paying to create the pool. Can be anyone"
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "pool_manager",
+          "docs": [
+            "with pool_manager permission, the pool creator can create a pool."
+          ],
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "amm_config",
+          "docs": [
+            "Which config the pool belongs to."
+          ]
+        },
+        {
+          "name": "pool_state",
+          "docs": [
+            "Initialize an account to store the pool state"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  111,
+                  108
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "amm_config"
+              },
+              {
+                "kind": "account",
+                "path": "token_mint_0"
+              },
+              {
+                "kind": "account",
+                "path": "token_mint_1"
+              }
+            ]
+          }
+        },
+        {
+          "name": "offchain_reward_config",
+          "docs": [
+            "Initialize an account to store the off-chain reward config"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  111,
+                  102,
+                  102,
+                  99,
+                  104,
+                  97,
+                  105,
+                  110,
+                  95,
+                  114,
+                  101,
+                  119,
+                  97,
+                  114,
+                  100
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_mint_0",
+          "docs": [
+            "Token_0 mint, the key must be smaller then token_1 mint."
+          ]
+        },
+        {
+          "name": "token_mint_1",
+          "docs": [
+            "Token_1 mint"
+          ]
+        },
+        {
+          "name": "token_vault_0",
+          "docs": [
+            "Token_0 vault for the pool"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  111,
+                  108,
+                  95,
+                  118,
+                  97,
+                  117,
+                  108,
+                  116
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "account",
+                "path": "token_mint_0"
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_vault_1",
+          "docs": [
+            "Token_1 vault for the pool"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  111,
+                  108,
+                  95,
+                  118,
+                  97,
+                  117,
+                  108,
+                  116
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "account",
+                "path": "token_mint_1"
+              }
+            ]
+          }
+        },
+        {
+          "name": "observation_state",
+          "docs": [
+            "Initialize an account to store oracle observations"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  111,
+                  98,
+                  115,
+                  101,
+                  114,
+                  118,
+                  97,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              }
+            ]
+          }
+        },
+        {
+          "name": "tick_array_bitmap",
+          "docs": [
+            "Initialize an account to store if a tick array is initialized."
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  111,
+                  108,
+                  95,
+                  116,
+                  105,
+                  99,
+                  107,
+                  95,
+                  97,
+                  114,
+                  114,
+                  97,
+                  121,
+                  95,
+                  98,
+                  105,
+                  116,
+                  109,
+                  97,
+                  112,
+                  95,
+                  101,
+                  120,
+                  116,
+                  101,
+                  110,
+                  115,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_program_0",
+          "docs": [
+            "Spl token program or token program 2022"
+          ]
+        },
+        {
+          "name": "token_program_1",
+          "docs": [
+            "Spl token program or token program 2022"
+          ]
+        },
+        {
+          "name": "system_program",
+          "docs": [
+            "To create a new program account"
+          ],
+          "address": "11111111111111111111111111111111"
+        },
+        {
+          "name": "rent",
+          "docs": [
+            "Sysvar for program account"
+          ],
+          "address": "SysvarRent111111111111111111111111111111111"
+        }
+      ],
+      "args": [
+        {
+          "name": "sqrt_price_x64",
+          "type": "u128"
+        },
+        {
+          "name": "open_time",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "create_support_mint_associated",
+      "docs": [
+        "Create support token22 mint account which can create pool and send rewards with ignoring the not support extensions."
+      ],
+      "discriminator": [
+        17,
+        251,
+        65,
+        92,
+        136,
+        242,
+        14,
+        169
+      ],
+      "accounts": [
+        {
+          "name": "owner",
+          "docs": [
+            "Address to be set as protocol owner."
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_mint",
+          "docs": [
+            "Support token mint"
+          ]
+        },
+        {
+          "name": "support_mint_associated",
+          "docs": [
+            "Initialize support mint state account to store support mint address and bump."
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  115,
+                  117,
+                  112,
+                  112,
+                  111,
+                  114,
+                  116,
+                  95,
+                  109,
+                  105,
+                  110,
+                  116
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "token_mint"
+              }
+            ]
+          }
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": []
+    },
+    {
+      "name": "decrease_liquidity",
+      "docs": [
+        "#[deprecated(note = \"Use `decrease_liquidity_v2` instead.\")]",
+        "Decreases liquidity for an existing position",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` -  The context of accounts",
+        "* `liquidity` - The amount by which liquidity will be decreased",
+        "* `amount_0_min` - The minimum amount of token_0 that should be accounted for the burned liquidity",
+        "* `amount_1_min` - The minimum amount of token_1 that should be accounted for the burned liquidity",
+        ""
+      ],
+      "discriminator": [
+        160,
+        38,
+        208,
+        111,
+        104,
+        91,
+        44,
+        1
+      ],
+      "accounts": [
+        {
+          "name": "nft_owner",
+          "docs": [
+            "The position owner or delegated authority"
+          ],
+          "signer": true
+        },
+        {
+          "name": "nft_account",
+          "docs": [
+            "The token account for the tokenized position"
+          ]
+        },
+        {
+          "name": "personal_position",
+          "docs": [
+            "Decrease liquidity for this position"
+          ],
+          "writable": true
+        },
+        {
+          "name": "pool_state",
+          "writable": true
+        },
+        {
+          "name": "protocol_position",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  115,
+                  105,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "account",
+                "path": "personal_position.tick_lower_index",
+                "account": "PersonalPositionState"
+              },
+              {
+                "kind": "account",
+                "path": "personal_position.tick_upper_index",
+                "account": "PersonalPositionState"
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_vault_0",
+          "docs": [
+            "Token_0 vault"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_1",
+          "docs": [
+            "Token_1 vault"
+          ],
+          "writable": true
+        },
+        {
+          "name": "tick_array_lower",
+          "docs": [
+            "Stores init state for the lower tick"
+          ],
+          "writable": true
+        },
+        {
+          "name": "tick_array_upper",
+          "docs": [
+            "Stores init state for the upper tick"
+          ],
+          "writable": true
+        },
+        {
+          "name": "recipient_token_account_0",
+          "docs": [
+            "The destination token account for receive amount_0"
+          ],
+          "writable": true
+        },
+        {
+          "name": "recipient_token_account_1",
+          "docs": [
+            "The destination token account for receive amount_1"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "SPL program to transfer out tokens"
+          ],
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        }
+      ],
+      "args": [
+        {
+          "name": "liquidity",
+          "type": "u128"
+        },
+        {
+          "name": "amount_0_min",
+          "type": "u64"
+        },
+        {
+          "name": "amount_1_min",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "decrease_liquidity_v2",
+      "docs": [
+        "Decreases liquidity for an existing position, support Token2022",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` -  The context of accounts",
+        "* `liquidity` - The amount by which liquidity will be decreased",
+        "* `amount_0_min` - The minimum amount of token_0 that should be accounted for the burned liquidity",
+        "* `amount_1_min` - The minimum amount of token_1 that should be accounted for the burned liquidity",
+        ""
+      ],
+      "discriminator": [
+        58,
+        127,
+        188,
+        62,
+        79,
+        82,
+        196,
+        96
+      ],
+      "accounts": [
+        {
+          "name": "nft_owner",
+          "docs": [
+            "The position owner or delegated authority"
+          ],
+          "signer": true
+        },
+        {
+          "name": "nft_account",
+          "docs": [
+            "The token account for the tokenized position"
+          ]
+        },
+        {
+          "name": "personal_position",
+          "docs": [
+            "Decrease liquidity for this position"
+          ],
+          "writable": true
+        },
+        {
+          "name": "pool_state",
+          "writable": true
+        },
+        {
+          "name": "protocol_position",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  115,
+                  105,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "account",
+                "path": "personal_position.tick_lower_index",
+                "account": "PersonalPositionState"
+              },
+              {
+                "kind": "account",
+                "path": "personal_position.tick_upper_index",
+                "account": "PersonalPositionState"
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_vault_0",
+          "docs": [
+            "Token_0 vault"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_1",
+          "docs": [
+            "Token_1 vault"
+          ],
+          "writable": true
+        },
+        {
+          "name": "tick_array_lower",
+          "docs": [
+            "Stores init state for the lower tick"
+          ],
+          "writable": true
+        },
+        {
+          "name": "tick_array_upper",
+          "docs": [
+            "Stores init state for the upper tick"
+          ],
+          "writable": true
+        },
+        {
+          "name": "recipient_token_account_0",
+          "docs": [
+            "The destination token account for receive amount_0"
+          ],
+          "writable": true
+        },
+        {
+          "name": "recipient_token_account_1",
+          "docs": [
+            "The destination token account for receive amount_1"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "SPL program to transfer out tokens"
+          ],
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        },
+        {
+          "name": "token_program_2022",
+          "docs": [
+            "Token program 2022"
+          ],
+          "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
+        },
+        {
+          "name": "memo_program",
+          "docs": [
+            "memo program"
+          ],
+          "address": "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
+        },
+        {
+          "name": "vault_0_mint",
+          "docs": [
+            "The mint of token vault 0"
+          ]
+        },
+        {
+          "name": "vault_1_mint",
+          "docs": [
+            "The mint of token vault 1"
+          ]
+        }
+      ],
+      "args": [
+        {
+          "name": "liquidity",
+          "type": "u128"
+        },
+        {
+          "name": "amount_0_min",
+          "type": "u64"
+        },
+        {
+          "name": "amount_1_min",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "deposit_offchain_reward",
+      "docs": [
+        "deposit offchain reward into the pool"
+      ],
+      "discriminator": [
+        97,
+        125,
+        48,
+        169,
+        92,
+        241,
+        44,
+        142
+      ],
+      "accounts": [
+        {
+          "name": "payer",
+          "docs": [
+            "the address paying to deposit the offchain reward."
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "authority",
+          "docs": [
+            "The authority make decision that who can deposit the offchain reward."
+          ],
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "Initialize amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "pool_id",
+          "docs": [
+            "the pool id, which is the pool state account."
+          ],
+          "relations": [
+            "reward_config"
+          ]
+        },
+        {
+          "name": "token_mint"
+        },
+        {
+          "name": "payer_token_account",
+          "docs": [
+            ""
+          ],
+          "writable": true
+        },
+        {
+          "name": "reward_vault_token_account",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "account",
+                "path": "reward_config"
+              },
+              {
+                "kind": "account",
+                "path": "token_program"
+              },
+              {
+                "kind": "account",
+                "path": "token_mint"
+              }
+            ],
+            "program": {
+              "kind": "const",
+              "value": [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ]
+            }
+          }
+        },
+        {
+          "name": "reward_config",
+          "docs": [
+            "The offchain reward config account, it also is the reward vault account."
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  111,
+                  102,
+                  102,
+                  99,
+                  104,
+                  97,
+                  105,
+                  110,
+                  95,
+                  114,
+                  101,
+                  119,
+                  97,
+                  114,
+                  100
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_id"
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "Spl token program or token program 2022"
+          ]
+        },
+        {
+          "name": "associated_token_program",
+          "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": [
+        {
+          "name": "amount",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "increase_liquidity",
+      "docs": [
+        "#[deprecated(note = \"Use `increase_liquidity_v2` instead.\")]",
+        "Increases liquidity for an existing position, with amount paid by `payer`",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` - The context of accounts",
+        "* `liquidity` - The desired liquidity to be added, can't be zero",
+        "* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check",
+        "* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check",
+        ""
+      ],
+      "discriminator": [
+        46,
+        156,
+        243,
+        118,
+        13,
+        205,
+        251,
+        178
+      ],
+      "accounts": [
+        {
+          "name": "nft_owner",
+          "docs": [
+            "Pays to mint the position"
+          ],
+          "signer": true
+        },
+        {
+          "name": "nft_account",
+          "docs": [
+            "The token account for nft"
+          ]
+        },
+        {
+          "name": "pool_state",
+          "writable": true
+        },
+        {
+          "name": "protocol_position",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  115,
+                  105,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "account",
+                "path": "personal_position.tick_lower_index",
+                "account": "PersonalPositionState"
+              },
+              {
+                "kind": "account",
+                "path": "personal_position.tick_upper_index",
+                "account": "PersonalPositionState"
+              }
+            ]
+          }
+        },
+        {
+          "name": "personal_position",
+          "docs": [
+            "Increase liquidity for this position"
+          ],
+          "writable": true
+        },
+        {
+          "name": "tick_array_lower",
+          "docs": [
+            "Stores init state for the lower tick"
+          ],
+          "writable": true
+        },
+        {
+          "name": "tick_array_upper",
+          "docs": [
+            "Stores init state for the upper tick"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_account_0",
+          "docs": [
+            "The payer's token account for token_0"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_account_1",
+          "docs": [
+            "The token account spending token_1 to mint the position"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_0",
+          "docs": [
+            "The address that holds pool tokens for token_0"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_1",
+          "docs": [
+            "The address that holds pool tokens for token_1"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "Program to create mint account and mint tokens"
+          ],
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        }
+      ],
+      "args": [
+        {
+          "name": "liquidity",
+          "type": "u128"
+        },
+        {
+          "name": "amount_0_max",
+          "type": "u64"
+        },
+        {
+          "name": "amount_1_max",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "increase_liquidity_v2",
+      "docs": [
+        "Increases liquidity for an existing position, with amount paid by `payer`, support Token2022",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` - The context of accounts",
+        "* `liquidity` - The desired liquidity to be added, if zero, calculate liquidity base amount_0 or amount_1 according base_flag",
+        "* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check",
+        "* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check",
+        "* `base_flag` - must be specified if liquidity is zero, true: calculate liquidity base amount_0_max otherwise base amount_1_max",
+        ""
+      ],
+      "discriminator": [
+        133,
+        29,
+        89,
+        223,
+        69,
+        238,
+        176,
+        10
+      ],
+      "accounts": [
+        {
+          "name": "nft_owner",
+          "docs": [
+            "Pays to mint the position"
+          ],
+          "signer": true
+        },
+        {
+          "name": "nft_account",
+          "docs": [
+            "The token account for nft"
+          ]
+        },
+        {
+          "name": "pool_state",
+          "writable": true
+        },
+        {
+          "name": "protocol_position",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  115,
+                  105,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "account",
+                "path": "personal_position.tick_lower_index",
+                "account": "PersonalPositionState"
+              },
+              {
+                "kind": "account",
+                "path": "personal_position.tick_upper_index",
+                "account": "PersonalPositionState"
+              }
+            ]
+          }
+        },
+        {
+          "name": "personal_position",
+          "docs": [
+            "Increase liquidity for this position"
+          ],
+          "writable": true
+        },
+        {
+          "name": "tick_array_lower",
+          "docs": [
+            "Stores init state for the lower tick"
+          ],
+          "writable": true
+        },
+        {
+          "name": "tick_array_upper",
+          "docs": [
+            "Stores init state for the upper tick"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_account_0",
+          "docs": [
+            "The payer's token account for token_0"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_account_1",
+          "docs": [
+            "The token account spending token_1 to mint the position"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_0",
+          "docs": [
+            "The address that holds pool tokens for token_0"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_1",
+          "docs": [
+            "The address that holds pool tokens for token_1"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "Program to create mint account and mint tokens"
+          ],
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        },
+        {
+          "name": "token_program_2022",
+          "docs": [
+            "Token program 2022"
+          ],
+          "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
+        },
+        {
+          "name": "vault_0_mint",
+          "docs": [
+            "The mint of token vault 0"
+          ]
+        },
+        {
+          "name": "vault_1_mint",
+          "docs": [
+            "The mint of token vault 1"
+          ]
+        }
+      ],
+      "args": [
+        {
+          "name": "liquidity",
+          "type": "u128"
+        },
+        {
+          "name": "amount_0_max",
+          "type": "u64"
+        },
+        {
+          "name": "amount_1_max",
+          "type": "u64"
+        },
+        {
+          "name": "base_flag",
+          "type": {
+            "option": "bool"
+          }
+        }
+      ]
+    },
+    {
+      "name": "init_amm_admin_group",
+      "docs": [
+        "Initialize the AMM admin group account, which is used to manage the AMM protocol."
+      ],
+      "discriminator": [
+        209,
+        108,
+        32,
+        246,
+        157,
+        214,
+        237,
+        86
+      ],
+      "accounts": [
+        {
+          "name": "payer",
+          "docs": [
+            "only super admin can create admin group"
+          ],
+          "writable": true,
+          "signer": true,
+          "address": "AY196f8U5EvM999PVnvLmyvaUnzL4GLiFaGKUgnJXN6o"
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "Initialize amm admin group account to store admin permissions."
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": [
+        {
+          "name": "params",
+          "type": {
+            "defined": {
+              "name": "InitAdminGroupParams"
+            }
+          }
+        }
+      ]
+    },
+    {
+      "name": "initialize_reward",
+      "docs": [
+        "Initialize a reward info for a given pool and reward index",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx`- The context of accounts",
+        "* `reward_index` - the index to reward info",
+        "* `open_time` - reward open timestamp",
+        "* `end_time` - reward end timestamp",
+        "* `emissions_per_second_x64` - Token reward per second are earned per unit of liquidity.",
+        ""
+      ],
+      "discriminator": [
+        95,
+        135,
+        192,
+        196,
+        242,
+        129,
+        230,
+        68
+      ],
+      "accounts": [
+        {
+          "name": "reward_funder",
+          "docs": [
+            "The founder deposit reward token to vault"
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "funder_token_account",
+          "writable": true
+        },
+        {
+          "name": "amm_config",
+          "docs": [
+            "For check the reward_funder authority"
+          ]
+        },
+        {
+          "name": "pool_state",
+          "docs": [
+            "Set reward for this pool"
+          ],
+          "writable": true
+        },
+        {
+          "name": "operation_state",
+          "docs": [
+            "load info from the account to judge reward permission"
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  111,
+                  112,
+                  101,
+                  114,
+                  97,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "reward_token_mint",
+          "docs": [
+            "Reward mint"
+          ]
+        },
+        {
+          "name": "reward_token_vault",
+          "docs": [
+            "A pda, reward vault"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  111,
+                  108,
+                  95,
+                  114,
+                  101,
+                  119,
+                  97,
+                  114,
+                  100,
+                  95,
+                  118,
+                  97,
+                  117,
+                  108,
+                  116
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "account",
+                "path": "reward_token_mint"
+              }
+            ]
+          }
+        },
+        {
+          "name": "reward_token_program"
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        },
+        {
+          "name": "rent",
+          "address": "SysvarRent111111111111111111111111111111111"
+        }
+      ],
+      "args": [
+        {
+          "name": "param",
+          "type": {
+            "defined": {
+              "name": "InitializeRewardParam"
+            }
+          }
+        }
+      ]
+    },
+    {
+      "name": "open_position",
+      "docs": [
+        "#[deprecated(note = \"Use `open_position_with_token22_nft` instead.\")]",
+        "Creates a new position wrapped in a NFT",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` - The context of accounts",
+        "* `tick_lower_index` - The low boundary of market",
+        "* `tick_upper_index` - The upper boundary of market",
+        "* `tick_array_lower_start_index` - The start index of tick array which include tick low",
+        "* `tick_array_upper_start_index` - The start index of tick array which include tick upper",
+        "* `liquidity` - The liquidity to be added",
+        "* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check",
+        "* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check",
+        ""
+      ],
+      "discriminator": [
+        135,
+        128,
+        47,
+        77,
+        15,
+        152,
+        240,
+        49
+      ],
+      "accounts": [
+        {
+          "name": "payer",
+          "docs": [
+            "Pays to mint the position"
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "position_nft_owner"
+        },
+        {
+          "name": "position_nft_mint",
+          "docs": [
+            "Unique token mint address"
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "position_nft_account",
+          "docs": [
+            "Token account where position NFT will be minted",
+            "This account created in the contract by cpi to avoid large stack variables"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "account",
+                "path": "position_nft_owner"
+              },
+              {
+                "kind": "const",
+                "value": [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "position_nft_mint"
+              }
+            ],
+            "program": {
+              "kind": "const",
+              "value": [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ]
+            }
+          }
+        },
+        {
+          "name": "metadata_account",
+          "docs": [
+            "To store metaplex metadata"
+          ],
+          "writable": true
+        },
+        {
+          "name": "pool_state",
+          "docs": [
+            "Add liquidity for this pool"
+          ],
+          "writable": true
+        },
+        {
+          "name": "protocol_position",
+          "docs": [
+            "Store the information of market marking in range"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  115,
+                  105,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "arg",
+                "path": "tick_lower_index"
+              },
+              {
+                "kind": "arg",
+                "path": "tick_upper_index"
+              }
+            ]
+          }
+        },
+        {
+          "name": "tick_array_lower",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  116,
+                  105,
+                  99,
+                  107,
+                  95,
+                  97,
+                  114,
+                  114,
+                  97,
+                  121
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "arg",
+                "path": "tick_array_lower_start_index"
+              }
+            ]
+          }
+        },
+        {
+          "name": "tick_array_upper",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  116,
+                  105,
+                  99,
+                  107,
+                  95,
+                  97,
+                  114,
+                  114,
+                  97,
+                  121
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "arg",
+                "path": "tick_array_upper_start_index"
+              }
+            ]
+          }
+        },
+        {
+          "name": "personal_position",
+          "docs": [
+            "personal position state"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  115,
+                  105,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "position_nft_mint"
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_account_0",
+          "docs": [
+            "The token_0 account deposit token to the pool"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_account_1",
+          "docs": [
+            "The token_1 account deposit token to the pool"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_0",
+          "docs": [
+            "The address that holds pool tokens for token_0"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_1",
+          "docs": [
+            "The address that holds pool tokens for token_1"
+          ],
+          "writable": true
+        },
+        {
+          "name": "rent",
+          "docs": [
+            "Sysvar for token mint and ATA creation"
+          ],
+          "address": "SysvarRent111111111111111111111111111111111"
+        },
+        {
+          "name": "system_program",
+          "docs": [
+            "Program to create the position manager state account"
+          ],
+          "address": "11111111111111111111111111111111"
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "Program to create mint account and mint tokens"
+          ],
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        },
+        {
+          "name": "associated_token_program",
+          "docs": [
+            "Program to create an ATA for receiving position NFT"
+          ],
+          "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+        },
+        {
+          "name": "metadata_program",
+          "docs": [
+            "Program to create NFT metadata"
+          ],
+          "address": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
+        }
+      ],
+      "args": [
+        {
+          "name": "tick_lower_index",
+          "type": "i32"
+        },
+        {
+          "name": "tick_upper_index",
+          "type": "i32"
+        },
+        {
+          "name": "tick_array_lower_start_index",
+          "type": "i32"
+        },
+        {
+          "name": "tick_array_upper_start_index",
+          "type": "i32"
+        },
+        {
+          "name": "liquidity",
+          "type": "u128"
+        },
+        {
+          "name": "amount_0_max",
+          "type": "u64"
+        },
+        {
+          "name": "amount_1_max",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "open_position_v2",
+      "docs": [
+        "#[deprecated(note = \"Use `open_position_with_token22_nft` instead.\")]",
+        "Creates a new position wrapped in a NFT, support Token2022",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` - The context of accounts",
+        "* `tick_lower_index` - The low boundary of market",
+        "* `tick_upper_index` - The upper boundary of market",
+        "* `tick_array_lower_start_index` - The start index of tick array which include tick low",
+        "* `tick_array_upper_start_index` - The start index of tick array which include tick upper",
+        "* `liquidity` - The liquidity to be added, if zero, and the base_flag is specified, calculate liquidity base amount_0_max or amount_1_max according base_flag, otherwise open position with zero liquidity",
+        "* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check",
+        "* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check",
+        "* `with_metadata` - The flag indicating whether to create NFT mint metadata",
+        "* `base_flag` - if the liquidity specified as zero, true: calculate liquidity base amount_0_max otherwise base amount_1_max",
+        ""
+      ],
+      "discriminator": [
+        77,
+        184,
+        74,
+        214,
+        112,
+        86,
+        241,
+        199
+      ],
+      "accounts": [
+        {
+          "name": "payer",
+          "docs": [
+            "Pays to mint the position"
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "position_nft_owner"
+        },
+        {
+          "name": "position_nft_mint",
+          "docs": [
+            "Unique token mint address"
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "position_nft_account",
+          "docs": [
+            "Token account where position NFT will be minted"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "account",
+                "path": "position_nft_owner"
+              },
+              {
+                "kind": "const",
+                "value": [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "position_nft_mint"
+              }
+            ],
+            "program": {
+              "kind": "const",
+              "value": [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ]
+            }
+          }
+        },
+        {
+          "name": "metadata_account",
+          "docs": [
+            "To store metaplex metadata"
+          ],
+          "writable": true
+        },
+        {
+          "name": "pool_state",
+          "docs": [
+            "Add liquidity for this pool"
+          ],
+          "writable": true
+        },
+        {
+          "name": "protocol_position",
+          "docs": [
+            "Store the information of market marking in range"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  115,
+                  105,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "arg",
+                "path": "tick_lower_index"
+              },
+              {
+                "kind": "arg",
+                "path": "tick_upper_index"
+              }
+            ]
+          }
+        },
+        {
+          "name": "tick_array_lower",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  116,
+                  105,
+                  99,
+                  107,
+                  95,
+                  97,
+                  114,
+                  114,
+                  97,
+                  121
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "arg",
+                "path": "tick_array_lower_start_index"
+              }
+            ]
+          }
+        },
+        {
+          "name": "tick_array_upper",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  116,
+                  105,
+                  99,
+                  107,
+                  95,
+                  97,
+                  114,
+                  114,
+                  97,
+                  121
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "arg",
+                "path": "tick_array_upper_start_index"
+              }
+            ]
+          }
+        },
+        {
+          "name": "personal_position",
+          "docs": [
+            "personal position state"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  115,
+                  105,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "position_nft_mint"
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_account_0",
+          "docs": [
+            "The token_0 account deposit token to the pool"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_account_1",
+          "docs": [
+            "The token_1 account deposit token to the pool"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_0",
+          "docs": [
+            "The address that holds pool tokens for token_0"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_1",
+          "docs": [
+            "The address that holds pool tokens for token_1"
+          ],
+          "writable": true
+        },
+        {
+          "name": "rent",
+          "docs": [
+            "Sysvar for token mint and ATA creation"
+          ],
+          "address": "SysvarRent111111111111111111111111111111111"
+        },
+        {
+          "name": "system_program",
+          "docs": [
+            "Program to create the position manager state account"
+          ],
+          "address": "11111111111111111111111111111111"
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "Program to create mint account and mint tokens"
+          ],
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        },
+        {
+          "name": "associated_token_program",
+          "docs": [
+            "Program to create an ATA for receiving position NFT"
+          ],
+          "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+        },
+        {
+          "name": "metadata_program",
+          "docs": [
+            "Program to create NFT metadata"
+          ],
+          "address": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
+        },
+        {
+          "name": "token_program_2022",
+          "docs": [
+            "Program to create mint account and mint tokens"
+          ],
+          "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
+        },
+        {
+          "name": "vault_0_mint",
+          "docs": [
+            "The mint of token vault 0"
+          ]
+        },
+        {
+          "name": "vault_1_mint",
+          "docs": [
+            "The mint of token vault 1"
+          ]
+        }
+      ],
+      "args": [
+        {
+          "name": "tick_lower_index",
+          "type": "i32"
+        },
+        {
+          "name": "tick_upper_index",
+          "type": "i32"
+        },
+        {
+          "name": "tick_array_lower_start_index",
+          "type": "i32"
+        },
+        {
+          "name": "tick_array_upper_start_index",
+          "type": "i32"
+        },
+        {
+          "name": "liquidity",
+          "type": "u128"
+        },
+        {
+          "name": "amount_0_max",
+          "type": "u64"
+        },
+        {
+          "name": "amount_1_max",
+          "type": "u64"
+        },
+        {
+          "name": "with_metadata",
+          "type": "bool"
+        },
+        {
+          "name": "base_flag",
+          "type": {
+            "option": "bool"
+          }
+        }
+      ]
+    },
+    {
+      "name": "open_position_with_token22_nft",
+      "docs": [
+        "Creates a new position wrapped in a Token2022 NFT without relying on metadata_program and metadata_account, reduce the cost for user to create a personal position.",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` - The context of accounts",
+        "* `tick_lower_index` - The low boundary of market",
+        "* `tick_upper_index` - The upper boundary of market",
+        "* `tick_array_lower_start_index` - The start index of tick array which include tick low",
+        "* `tick_array_upper_start_index` - The start index of tick array which include tick upper",
+        "* `liquidity` - The liquidity to be added, if zero, and the base_flag is specified, calculate liquidity base amount_0_max or amount_1_max according base_flag, otherwise open position with zero liquidity",
+        "* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check",
+        "* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check",
+        "* `with_metadata` - The flag indicating whether to create NFT mint metadata",
+        "* `base_flag` - if the liquidity specified as zero, true: calculate liquidity base amount_0_max otherwise base amount_1_max",
+        ""
+      ],
+      "discriminator": [
+        77,
+        255,
+        174,
+        82,
+        125,
+        29,
+        201,
+        46
+      ],
+      "accounts": [
+        {
+          "name": "payer",
+          "docs": [
+            "Pays to mint the position"
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "position_nft_owner"
+        },
+        {
+          "name": "position_nft_mint",
+          "docs": [
+            "Unique token mint address, initialize in contract"
+          ],
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "position_nft_account",
+          "writable": true
+        },
+        {
+          "name": "pool_state",
+          "docs": [
+            "Add liquidity for this pool"
+          ],
+          "writable": true
+        },
+        {
+          "name": "protocol_position",
+          "docs": [
+            "Store the information of market marking in range"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  115,
+                  105,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "arg",
+                "path": "tick_lower_index"
+              },
+              {
+                "kind": "arg",
+                "path": "tick_upper_index"
+              }
+            ]
+          }
+        },
+        {
+          "name": "tick_array_lower",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  116,
+                  105,
+                  99,
+                  107,
+                  95,
+                  97,
+                  114,
+                  114,
+                  97,
+                  121
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "arg",
+                "path": "tick_array_lower_start_index"
+              }
+            ]
+          }
+        },
+        {
+          "name": "tick_array_upper",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  116,
+                  105,
+                  99,
+                  107,
+                  95,
+                  97,
+                  114,
+                  114,
+                  97,
+                  121
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_state"
+              },
+              {
+                "kind": "arg",
+                "path": "tick_array_upper_start_index"
+              }
+            ]
+          }
+        },
+        {
+          "name": "personal_position",
+          "docs": [
+            "personal position state"
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  112,
+                  111,
+                  115,
+                  105,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "position_nft_mint"
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_account_0",
+          "docs": [
+            "The token_0 account deposit token to the pool"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_account_1",
+          "docs": [
+            "The token_1 account deposit token to the pool"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_0",
+          "docs": [
+            "The address that holds pool tokens for token_0"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_vault_1",
+          "docs": [
+            "The address that holds pool tokens for token_1"
+          ],
+          "writable": true
+        },
+        {
+          "name": "rent",
+          "docs": [
+            "Sysvar for token mint and ATA creation"
+          ],
+          "address": "SysvarRent111111111111111111111111111111111"
+        },
+        {
+          "name": "system_program",
+          "docs": [
+            "Program to create the position manager state account"
+          ],
+          "address": "11111111111111111111111111111111"
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "Program to transfer for token account"
+          ],
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        },
+        {
+          "name": "associated_token_program",
+          "docs": [
+            "Program to create an ATA for receiving position NFT"
+          ],
+          "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+        },
+        {
+          "name": "token_program_2022",
+          "docs": [
+            "Program to create NFT mint/token account and transfer for token22 account"
+          ],
+          "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
+        },
+        {
+          "name": "vault_0_mint",
+          "docs": [
+            "The mint of token vault 0"
+          ]
+        },
+        {
+          "name": "vault_1_mint",
+          "docs": [
+            "The mint of token vault 1"
+          ]
+        }
+      ],
+      "args": [
+        {
+          "name": "tick_lower_index",
+          "type": "i32"
+        },
+        {
+          "name": "tick_upper_index",
+          "type": "i32"
+        },
+        {
+          "name": "tick_array_lower_start_index",
+          "type": "i32"
+        },
+        {
+          "name": "tick_array_upper_start_index",
+          "type": "i32"
+        },
+        {
+          "name": "liquidity",
+          "type": "u128"
+        },
+        {
+          "name": "amount_0_max",
+          "type": "u64"
+        },
+        {
+          "name": "amount_1_max",
+          "type": "u64"
+        },
+        {
+          "name": "with_metadata",
+          "type": "bool"
+        },
+        {
+          "name": "base_flag",
+          "type": {
+            "option": "bool"
+          }
+        }
+      ]
+    },
+    {
+      "name": "set_reward_params",
+      "docs": [
+        "Reset reward param, start a new reward cycle or extend the current cycle.",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` - The context of accounts",
+        "* `reward_index` - The index of reward token in the pool.",
+        "* `emissions_per_second_x64` - The per second emission reward, when extend the current cycle,",
+        "new value can't be less than old value",
+        "* `open_time` - reward open timestamp, must be set when starting a new cycle",
+        "* `end_time` - reward end timestamp",
+        ""
+      ],
+      "discriminator": [
+        112,
+        52,
+        167,
+        75,
+        32,
+        201,
+        211,
+        137
+      ],
+      "accounts": [
+        {
+          "name": "authority",
+          "docs": [
+            "Address to be set as protocol owner. It pays to create factory state account."
+          ],
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "amm_config"
+        },
+        {
+          "name": "pool_state",
+          "writable": true
+        },
+        {
+          "name": "operation_state",
+          "docs": [
+            "load info from the account to judge reward permission"
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  111,
+                  112,
+                  101,
+                  114,
+                  97,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "Token program"
+          ],
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        },
+        {
+          "name": "token_program_2022",
+          "docs": [
+            "Token program 2022"
+          ],
+          "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
+        }
+      ],
+      "args": [
+        {
+          "name": "reward_index",
+          "type": "u8"
+        },
+        {
+          "name": "emissions_per_second_x64",
+          "type": "u128"
+        },
+        {
+          "name": "open_time",
+          "type": "u64"
+        },
+        {
+          "name": "end_time",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "swap",
+      "docs": [
+        "#[deprecated(note = \"Use `swap_v2` instead.\")]",
+        "Swaps one token for as much as possible of another token across a single pool",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` - The context of accounts",
+        "* `amount` - Arranged in pairs with other_amount_threshold. (amount_in, amount_out_minimum) or (amount_out, amount_in_maximum)",
+        "* `other_amount_threshold` - For slippage check",
+        "* `sqrt_price_limit` - The Q64.64 sqrt price √P limit. If zero for one, the price cannot",
+        "* `is_base_input` - swap base input or swap base output",
+        ""
+      ],
+      "discriminator": [
+        248,
+        198,
+        158,
+        145,
+        225,
+        117,
+        135,
+        200
+      ],
+      "accounts": [
+        {
+          "name": "payer",
+          "docs": [
+            "The user performing the swap"
+          ],
+          "signer": true
+        },
+        {
+          "name": "amm_config",
+          "docs": [
+            "The factory state to read protocol fees"
+          ]
+        },
+        {
+          "name": "pool_state",
+          "docs": [
+            "The program account of the pool in which the swap will be performed"
+          ],
+          "writable": true
+        },
+        {
+          "name": "input_token_account",
+          "docs": [
+            "The user token account for input token"
+          ],
+          "writable": true
+        },
+        {
+          "name": "output_token_account",
+          "docs": [
+            "The user token account for output token"
+          ],
+          "writable": true
+        },
+        {
+          "name": "input_vault",
+          "docs": [
+            "The vault token account for input token"
+          ],
+          "writable": true
+        },
+        {
+          "name": "output_vault",
+          "docs": [
+            "The vault token account for output token"
+          ],
+          "writable": true
+        },
+        {
+          "name": "observation_state",
+          "docs": [
+            "The program account for the most recent oracle observation"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "SPL program for token transfers"
+          ],
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        },
+        {
+          "name": "tick_array",
+          "writable": true
+        }
+      ],
+      "args": [
+        {
+          "name": "amount",
+          "type": "u64"
+        },
+        {
+          "name": "other_amount_threshold",
+          "type": "u64"
+        },
+        {
+          "name": "sqrt_price_limit_x64",
+          "type": "u128"
+        },
+        {
+          "name": "is_base_input",
+          "type": "bool"
+        }
+      ]
+    },
+    {
+      "name": "swap_v2",
+      "docs": [
+        "Swaps one token for as much as possible of another token across a single pool, support token program 2022",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx` - The context of accounts",
+        "* `amount` - Arranged in pairs with other_amount_threshold. (amount_in, amount_out_minimum) or (amount_out, amount_in_maximum)",
+        "* `other_amount_threshold` - For slippage check",
+        "* `sqrt_price_limit` - The Q64.64 sqrt price √P limit. If zero for one, the price cannot",
+        "* `is_base_input` - swap base input or swap base output",
+        ""
+      ],
+      "discriminator": [
+        43,
+        4,
+        237,
+        11,
+        26,
+        201,
+        30,
+        98
+      ],
+      "accounts": [
+        {
+          "name": "payer",
+          "docs": [
+            "The user performing the swap"
+          ],
+          "signer": true
+        },
+        {
+          "name": "amm_config",
+          "docs": [
+            "The factory state to read protocol fees"
+          ]
+        },
+        {
+          "name": "pool_state",
+          "docs": [
+            "The program account of the pool in which the swap will be performed"
+          ],
+          "writable": true
+        },
+        {
+          "name": "input_token_account",
+          "docs": [
+            "The user token account for input token"
+          ],
+          "writable": true
+        },
+        {
+          "name": "output_token_account",
+          "docs": [
+            "The user token account for output token"
+          ],
+          "writable": true
+        },
+        {
+          "name": "input_vault",
+          "docs": [
+            "The vault token account for input token"
+          ],
+          "writable": true
+        },
+        {
+          "name": "output_vault",
+          "docs": [
+            "The vault token account for output token"
+          ],
+          "writable": true
+        },
+        {
+          "name": "observation_state",
+          "docs": [
+            "The program account for the most recent oracle observation"
+          ],
+          "writable": true
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "SPL program for token transfers"
+          ],
+          "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+        },
+        {
+          "name": "token_program_2022",
+          "docs": [
+            "SPL program 2022 for token transfers"
+          ],
+          "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
+        },
+        {
+          "name": "memo_program",
+          "docs": [
+            "Memo program"
+          ],
+          "address": "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
+        },
+        {
+          "name": "input_vault_mint",
+          "docs": [
+            "The mint of token vault 0"
+          ]
+        },
+        {
+          "name": "output_vault_mint",
+          "docs": [
+            "The mint of token vault 1"
+          ]
+        }
+      ],
+      "args": [
+        {
+          "name": "amount",
+          "type": "u64"
+        },
+        {
+          "name": "other_amount_threshold",
+          "type": "u64"
+        },
+        {
+          "name": "sqrt_price_limit_x64",
+          "type": "u128"
+        },
+        {
+          "name": "is_base_input",
+          "type": "bool"
+        }
+      ]
+    },
+    {
+      "name": "transfer_reward_owner",
+      "docs": [
+        "Transfer reward owner",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx`- The context of accounts",
+        "* `new_owner`- new owner pubkey",
+        ""
+      ],
+      "discriminator": [
+        7,
+        22,
+        12,
+        83,
+        242,
+        43,
+        48,
+        121
+      ],
+      "accounts": [
+        {
+          "name": "authority",
+          "docs": [
+            "Address to be set as operation account owner."
+          ],
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "pool_state",
+          "writable": true
+        }
+      ],
+      "args": [
+        {
+          "name": "new_owner",
+          "type": "pubkey"
+        }
+      ]
+    },
+    {
+      "name": "update_amm_admin_group",
+      "docs": [
+        "Update the AMM admin group account, which is used to manage the AMM protocol."
+      ],
+      "discriminator": [
+        61,
+        183,
+        185,
+        188,
+        82,
+        81,
+        141,
+        197
+      ],
+      "accounts": [
+        {
+          "name": "payer",
+          "docs": [
+            "only super admin can create admin group"
+          ],
+          "writable": true,
+          "signer": true,
+          "address": "AY196f8U5EvM999PVnvLmyvaUnzL4GLiFaGKUgnJXN6o"
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "update amm admin group account to store admin permissions."
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        }
+      ],
+      "args": [
+        {
+          "name": "params",
+          "type": {
+            "defined": {
+              "name": "UpdateAdminGroupParams"
+            }
+          }
+        }
+      ]
+    },
+    {
+      "name": "update_amm_config",
+      "docs": [
+        "Updates the owner of the amm config",
+        "Must be called by the current owner or admin",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx`- The context of accounts",
+        "* `trade_fee_rate`- The new trade fee rate of amm config, be set when `param` is 0",
+        "* `protocol_fee_rate`- The new protocol fee rate of amm config, be set when `param` is 1",
+        "* `fund_fee_rate`- The new fund fee rate of amm config, be set when `param` is 2",
+        "* `new_owner`- The config's new owner, be set when `param` is 3",
+        "* `new_fund_owner`- The config's new fund owner, be set when `param` is 4",
+        "* `param`- The value can be 0 | 1 | 2 | 3 | 4, otherwise will report a error",
+        ""
+      ],
+      "discriminator": [
+        49,
+        60,
+        174,
+        136,
+        154,
+        28,
+        116,
+        200
+      ],
+      "accounts": [
+        {
+          "name": "owner",
+          "docs": [
+            "The amm config owner or admin"
+          ],
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "amm_config",
+          "docs": [
+            "Amm config account to be changed"
+          ],
+          "writable": true
+        }
+      ],
+      "args": [
+        {
+          "name": "param",
+          "type": "u8"
+        },
+        {
+          "name": "value",
+          "type": "u32"
+        }
+      ]
+    },
+    {
+      "name": "update_operation_account",
+      "docs": [
+        "Update the operation account",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx`- The context of accounts",
+        "* `param`- The value can be 0 | 1 | 2 | 3, otherwise will report a error",
+        "* `keys`- update operation owner when the `param` is 0",
+        "remove operation owner when the `param` is 1",
+        "update whitelist mint when the `param` is 2",
+        "remove whitelist mint when the `param` is 3",
+        ""
+      ],
+      "discriminator": [
+        127,
+        70,
+        119,
+        40,
+        188,
+        227,
+        61,
+        7
+      ],
+      "accounts": [
+        {
+          "name": "owner",
+          "docs": [
+            "Address to be set as operation account owner."
+          ],
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "operation_state",
+          "docs": [
+            "Initialize operation state account to store operation owner address and white list mint."
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  111,
+                  112,
+                  101,
+                  114,
+                  97,
+                  116,
+                  105,
+                  111,
+                  110
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": [
+        {
+          "name": "param",
+          "type": "u8"
+        },
+        {
+          "name": "keys",
+          "type": {
+            "vec": "pubkey"
+          }
+        }
+      ]
+    },
+    {
+      "name": "update_pool_status",
+      "docs": [
+        "Update pool status for given value",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx`- The context of accounts",
+        "* `status` - The value of status",
+        ""
+      ],
+      "discriminator": [
+        130,
+        87,
+        108,
+        6,
+        46,
+        224,
+        117,
+        123
+      ],
+      "accounts": [
+        {
+          "name": "authority",
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "pool_state",
+          "writable": true
+        }
+      ],
+      "args": [
+        {
+          "name": "status",
+          "type": "u8"
+        }
+      ]
+    },
+    {
+      "name": "update_reward_infos",
+      "docs": [
+        "Update rewards info of the given pool, can be called for everyone",
+        "",
+        "# Arguments",
+        "",
+        "* `ctx`- The context of accounts",
+        ""
+      ],
+      "discriminator": [
+        163,
+        172,
+        224,
+        52,
+        11,
+        154,
+        106,
+        223
+      ],
+      "accounts": [
+        {
+          "name": "pool_state",
+          "docs": [
+            "The liquidity pool for which reward info to update"
+          ],
+          "writable": true
+        }
+      ],
+      "args": []
+    },
+    {
+      "name": "withdraw_offchain_reward",
+      "docs": [
+        "withdraw offchain reward from the pool"
+      ],
+      "discriminator": [
+        86,
+        53,
+        59,
+        76,
+        217,
+        38,
+        71,
+        213
+      ],
+      "accounts": [
+        {
+          "name": "authority",
+          "docs": [
+            "The authority make decision that who can withdraw the offchain reward."
+          ],
+          "signer": true
+        },
+        {
+          "name": "admin_group",
+          "docs": [
+            "Initialize amm admin group account to store admin permissions."
+          ],
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  97,
+                  100,
+                  109,
+                  105,
+                  110,
+                  95,
+                  103,
+                  114,
+                  111,
+                  117,
+                  112
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "pool_id",
+          "docs": [
+            "the pool id, which is the pool state account."
+          ],
+          "relations": [
+            "reward_config"
+          ]
+        },
+        {
+          "name": "token_mint"
+        },
+        {
+          "name": "receiver_token_account",
+          "docs": [
+            "the address who receive the withdrawn offchain reward."
+          ],
+          "writable": true
+        },
+        {
+          "name": "reward_vault_token_account",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "account",
+                "path": "reward_config"
+              },
+              {
+                "kind": "account",
+                "path": "token_program"
+              },
+              {
+                "kind": "account",
+                "path": "token_mint"
+              }
+            ],
+            "program": {
+              "kind": "const",
+              "value": [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ]
+            }
+          }
+        },
+        {
+          "name": "reward_config",
+          "docs": [
+            "The offchain reward config account, it also is the reward vault account."
+          ],
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  111,
+                  102,
+                  102,
+                  99,
+                  104,
+                  97,
+                  105,
+                  110,
+                  95,
+                  114,
+                  101,
+                  119,
+                  97,
+                  114,
+                  100
+                ]
+              },
+              {
+                "kind": "account",
+                "path": "pool_id"
+              }
+            ]
+          }
+        },
+        {
+          "name": "token_program",
+          "docs": [
+            "Spl token program or token program 2022"
+          ]
+        },
+        {
+          "name": "associated_token_program",
+          "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+        }
+      ],
+      "args": [
+        {
+          "name": "amount",
+          "type": "u64"
+        }
+      ]
+    }
+  ],
+  "accounts": [
+    {
+      "name": "AmmAdminGroup",
+      "discriminator": [
+        128,
+        128,
+        234,
+        30,
+        61,
+        172,
+        188,
+        123
+      ]
+    },
+    {
+      "name": "AmmConfig",
+      "discriminator": [
+        218,
+        244,
+        33,
+        104,
+        203,
+        203,
+        43,
+        111
+      ]
+    },
+    {
+      "name": "ObservationState",
+      "discriminator": [
+        122,
+        174,
+        197,
+        53,
+        129,
+        9,
+        165,
+        132
+      ]
+    },
+    {
+      "name": "OffchainRewardConfig",
+      "discriminator": [
+        118,
+        52,
+        115,
+        150,
+        99,
+        69,
+        164,
+        76
+      ]
+    },
+    {
+      "name": "OperationState",
+      "discriminator": [
+        19,
+        236,
+        58,
+        237,
+        81,
+        222,
+        183,
+        252
+      ]
+    },
+    {
+      "name": "PersonalPositionState",
+      "discriminator": [
+        70,
+        111,
+        150,
+        126,
+        230,
+        15,
+        25,
+        117
+      ]
+    },
+    {
+      "name": "PoolState",
+      "discriminator": [
+        247,
+        237,
+        227,
+        245,
+        215,
+        195,
+        222,
+        70
+      ]
+    },
+    {
+      "name": "ProtocolPositionState",
+      "discriminator": [
+        100,
+        226,
+        145,
+        99,
+        146,
+        218,
+        160,
+        106
+      ]
+    },
+    {
+      "name": "SupportMintAssociated",
+      "discriminator": [
+        134,
+        40,
+        183,
+        79,
+        12,
+        112,
+        162,
+        53
+      ]
+    },
+    {
+      "name": "TickArrayBitmapExtension",
+      "discriminator": [
+        60,
+        150,
+        36,
+        219,
+        97,
+        128,
+        139,
+        153
+      ]
+    },
+    {
+      "name": "TickArrayState",
+      "discriminator": [
+        192,
+        155,
+        85,
+        205,
+        49,
+        249,
+        129,
+        42
+      ]
+    }
+  ],
+  "events": [
+    {
+      "name": "CollectPersonalFeeEvent",
+      "discriminator": [
+        166,
+        174,
+        105,
+        192,
+        81,
+        161,
+        83,
+        105
+      ]
+    },
+    {
+      "name": "CollectProtocolFeeEvent",
+      "discriminator": [
+        206,
+        87,
+        17,
+        79,
+        45,
+        41,
+        213,
+        61
+      ]
+    },
+    {
+      "name": "ConfigChangeEvent",
+      "discriminator": [
+        247,
+        189,
+        7,
+        119,
+        106,
+        112,
+        95,
+        151
+      ]
+    },
+    {
+      "name": "CreatePersonalPositionEvent",
+      "discriminator": [
+        100,
+        30,
+        87,
+        249,
+        196,
+        223,
+        154,
+        206
+      ]
+    },
+    {
+      "name": "DecreaseLiquidityEvent",
+      "discriminator": [
+        58,
+        222,
+        86,
+        58,
+        68,
+        50,
+        85,
+        56
+      ]
+    },
+    {
+      "name": "IncreaseLiquidityEvent",
+      "discriminator": [
+        49,
+        79,
+        105,
+        212,
+        32,
+        34,
+        30,
+        84
+      ]
+    },
+    {
+      "name": "LiquidityCalculateEvent",
+      "discriminator": [
+        237,
+        112,
+        148,
+        230,
+        57,
+        84,
+        180,
+        162
+      ]
+    },
+    {
+      "name": "LiquidityChangeEvent",
+      "discriminator": [
+        126,
+        240,
+        175,
+        206,
+        158,
+        88,
+        153,
+        107
+      ]
+    },
+    {
+      "name": "ModifyAmmAdminGroupEvent",
+      "discriminator": [
+        218,
+        118,
+        87,
+        81,
+        53,
+        80,
+        18,
+        235
+      ]
+    },
+    {
+      "name": "PoolCreatedEvent",
+      "discriminator": [
+        25,
+        94,
+        75,
+        47,
+        112,
+        99,
+        53,
+        63
+      ]
+    },
+    {
+      "name": "SwapEvent",
+      "discriminator": [
+        64,
+        198,
+        205,
+        232,
+        38,
+        8,
+        113,
+        226
+      ]
+    },
+    {
+      "name": "UpdateRewardInfosEvent",
+      "discriminator": [
+        109,
+        127,
+        186,
+        78,
+        114,
+        65,
+        37,
+        236
+      ]
+    }
+  ],
+  "errors": [
+    {
+      "code": 6000,
+      "name": "LOK",
+      "msg": "LOK"
+    },
+    {
+      "code": 6001,
+      "name": "NotApproved",
+      "msg": "Not approved"
+    },
+    {
+      "code": 6002,
+      "name": "InvalidUpdateConfigFlag",
+      "msg": "invalid update amm config flag"
+    },
+    {
+      "code": 6003,
+      "name": "AccountLack",
+      "msg": "Account lack"
+    },
+    {
+      "code": 6004,
+      "name": "ClosePositionErr",
+      "msg": "Remove liquitity, collect fees owed and reward then you can close position account"
+    },
+    {
+      "code": 6005,
+      "name": "ZeroMintAmount",
+      "msg": "Minting amount should be greater than 0"
+    },
+    {
+      "code": 6006,
+      "name": "InvalidTickIndex",
+      "msg": "Tick out of range"
+    },
+    {
+      "code": 6007,
+      "name": "TickInvalidOrder",
+      "msg": "The lower tick must be below the upper tick"
+    },
+    {
+      "code": 6008,
+      "name": "TickLowerOverflow",
+      "msg": "The tick must be greater, or equal to the minimum tick(-443636)"
+    },
+    {
+      "code": 6009,
+      "name": "TickUpperOverflow",
+      "msg": "The tick must be lesser than, or equal to the maximum tick(443636)"
+    },
+    {
+      "code": 6010,
+      "name": "TickAndSpacingNotMatch",
+      "msg": "tick % tick_spacing must be zero"
+    },
+    {
+      "code": 6011,
+      "name": "InvalidTickArray",
+      "msg": "Invalid tick array account"
+    },
+    {
+      "code": 6012,
+      "name": "InvalidTickArrayBoundary",
+      "msg": "Invalid tick array boundary"
+    },
+    {
+      "code": 6013,
+      "name": "SqrtPriceLimitOverflow",
+      "msg": "Square root price limit overflow"
+    },
+    {
+      "code": 6014,
+      "name": "SqrtPriceX64",
+      "msg": "sqrt_price_x64 out of range"
+    },
+    {
+      "code": 6015,
+      "name": "LiquiditySubValueErr",
+      "msg": "Liquidity sub delta L must be smaller than before"
+    },
+    {
+      "code": 6016,
+      "name": "LiquidityAddValueErr",
+      "msg": "Liquidity add delta L must be greater, or equal to before"
+    },
+    {
+      "code": 6017,
+      "name": "InvalidLiquidity",
+      "msg": "Invalid liquidity when update position"
+    },
+    {
+      "code": 6018,
+      "name": "ForbidBothZeroForSupplyLiquidity",
+      "msg": "Both token amount must not be zero while supply liquidity"
+    },
+    {
+      "code": 6019,
+      "name": "LiquidityInsufficient",
+      "msg": "Liquidity insufficient"
+    },
+    {
+      "code": 6020,
+      "name": "TransactionTooOld",
+      "msg": "Transaction too old"
+    },
+    {
+      "code": 6021,
+      "name": "PriceSlippageCheck",
+      "msg": "Price slippage check"
+    },
+    {
+      "code": 6022,
+      "name": "TooLittleOutputReceived",
+      "msg": "Too little output received"
+    },
+    {
+      "code": 6023,
+      "name": "TooMuchInputPaid",
+      "msg": "Too much input paid"
+    },
+    {
+      "code": 6024,
+      "name": "ZeroAmountSpecified",
+      "msg": "Swap special amount can not be zero"
+    },
+    {
+      "code": 6025,
+      "name": "InvalidInputPoolVault",
+      "msg": "Input pool vault is invalid"
+    },
+    {
+      "code": 6026,
+      "name": "TooSmallInputOrOutputAmount",
+      "msg": "Swap input or output amount is too small"
+    },
+    {
+      "code": 6027,
+      "name": "NotEnoughTickArrayAccount",
+      "msg": "Not enought tick array account"
+    },
+    {
+      "code": 6028,
+      "name": "InvalidFirstTickArrayAccount",
+      "msg": "Invalid first tick array account"
+    },
+    {
+      "code": 6029,
+      "name": "InvalidRewardIndex",
+      "msg": "Invalid reward index"
+    },
+    {
+      "code": 6030,
+      "name": "FullRewardInfo",
+      "msg": "The init reward token reach to the max"
+    },
+    {
+      "code": 6031,
+      "name": "RewardTokenAlreadyInUse",
+      "msg": "The init reward token already in use"
+    },
+    {
+      "code": 6032,
+      "name": "ExceptRewardMint",
+      "msg": "The reward tokens must contain one of pool vault mint except the last reward"
+    },
+    {
+      "code": 6033,
+      "name": "InvalidRewardInitParam",
+      "msg": "Invalid reward init param"
+    },
+    {
+      "code": 6034,
+      "name": "InvalidRewardDesiredAmount",
+      "msg": "Invalid collect reward desired amount"
+    },
+    {
+      "code": 6035,
+      "name": "InvalidRewardInputAccountNumber",
+      "msg": "Invalid collect reward input account number"
+    },
+    {
+      "code": 6036,
+      "name": "InvalidRewardPeriod",
+      "msg": "Invalid reward period"
+    },
+    {
+      "code": 6037,
+      "name": "NotApproveUpdateRewardEmissiones",
+      "msg": "Modification of emissiones is allowed within 72 hours from the end of the previous cycle"
+    },
+    {
+      "code": 6038,
+      "name": "UnInitializedRewardInfo",
+      "msg": "uninitialized reward info"
+    },
+    {
+      "code": 6039,
+      "name": "NotSupportMint",
+      "msg": "Not support token_2022 mint extension"
+    },
+    {
+      "code": 6040,
+      "name": "MissingTickArrayBitmapExtensionAccount",
+      "msg": "Missing tickarray bitmap extension account"
+    },
+    {
+      "code": 6041,
+      "name": "InsufficientLiquidityForDirection",
+      "msg": "Insufficient liquidity for this direction"
+    },
+    {
+      "code": 6042,
+      "name": "MaxTokenOverflow",
+      "msg": "Max token overflow"
+    },
+    {
+      "code": 6043,
+      "name": "CalculateOverflow",
+      "msg": "Calculate overflow"
+    },
+    {
+      "code": 6044,
+      "name": "TransferFeeCalculateNotMatch",
+      "msg": "TransferFee calculate not match"
+    },
+    {
+      "code": 6045,
+      "name": "IllegalAccountOwner",
+      "msg": "invalid account owner"
+    },
+    {
+      "code": 6046,
+      "name": "InvalidAccount",
+      "msg": "Invalid account"
+    }
+  ],
+  "types": [
+    {
+      "name": "AmmAdminGroup",
+      "docs": [
+        "Holds the admin group information."
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "fee_keeper",
+            "docs": [
+              "the address who can hold the fee,",
+              "anyone can trigger the fee collection action,"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "reward_config_manager",
+            "docs": [
+              "the address who can config the reward(config, deposit, withdraw),",
+              "deposit reward, set the account who can deposit the reward, withdraw the remaining reward(withdraw)"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "reward_claim_manager",
+            "docs": [
+              "the address who can manage the offchain reward claim"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "pool_manager",
+            "docs": [
+              "the address who can manage the pool create action,",
+              "without this account's permission, no one can create a pool"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "emergency_manager",
+            "docs": [
+              "the address who can manage the emergency action,",
+              "emergency action includes stop/resume the pool, stop/resume withdraw lp"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "normal_manager",
+            "docs": [
+              "normal action manager,",
+              "such as create amm config, update amm config"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "pad",
+            "docs": [
+              "The space required for the account. may be used for future extensions."
+            ],
+            "type": {
+              "array": [
+                "pubkey",
+                6
+              ]
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "AmmConfig",
+      "docs": [
+        "Holds the current owner of the factory"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "bump",
+            "docs": [
+              "Bump to identify PDA"
+            ],
+            "type": "u8"
+          },
+          {
+            "name": "index",
+            "type": "u16"
+          },
+          {
+            "name": "owner",
+            "docs": [
+              "Address of the protocol owner"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "protocol_fee_rate",
+            "docs": [
+              "The protocol fee"
+            ],
+            "type": "u32"
+          },
+          {
+            "name": "trade_fee_rate",
+            "docs": [
+              "The trade fee, denominated in hundredths of a bip (10^-6)"
+            ],
+            "type": "u32"
+          },
+          {
+            "name": "tick_spacing",
+            "docs": [
+              "The tick spacing"
+            ],
+            "type": "u16"
+          },
+          {
+            "name": "fund_fee_rate",
+            "docs": [
+              "The fund fee, denominated in hundredths of a bip (10^-6)"
+            ],
+            "type": "u32"
+          },
+          {
+            "name": "padding_u32",
+            "type": "u32"
+          },
+          {
+            "name": "fund_owner",
+            "type": "pubkey"
+          },
+          {
+            "name": "padding",
+            "type": {
+              "array": [
+                "u64",
+                3
+              ]
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "CollectPersonalFeeEvent",
+      "docs": [
+        "Emitted when tokens are collected for a position"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "position_nft_mint",
+            "docs": [
+              "The ID of the token for which underlying tokens were collected"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "recipient_token_account_0",
+            "docs": [
+              "The token account that received the collected token_0 tokens"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "recipient_token_account_1",
+            "docs": [
+              "The token account that received the collected token_1 tokens"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "amount_0",
+            "docs": [
+              "The amount of token_0 owed to the position that was collected"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "amount_1",
+            "docs": [
+              "The amount of token_1 owed to the position that was collected"
+            ],
+            "type": "u64"
+          }
+        ]
+      }
+    },
+    {
+      "name": "CollectProtocolFeeEvent",
+      "docs": [
+        "Emitted when the collected protocol fees are withdrawn by the factory owner"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "pool_state",
+            "docs": [
+              "The pool whose protocol fee is collected"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "recipient_token_account_0",
+            "docs": [
+              "The address that receives the collected token_0 protocol fees"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "recipient_token_account_1",
+            "docs": [
+              "The address that receives the collected token_1 protocol fees"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "amount_0",
+            "docs": [
+              "The amount of token_0 protocol fees that is withdrawn"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "amount_1",
+            "docs": [
+              "The amount of token_0 protocol fees that is withdrawn"
+            ],
+            "type": "u64"
+          }
+        ]
+      }
+    },
+    {
+      "name": "ConfigChangeEvent",
+      "docs": [
+        "Emitted when create or update a config"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "index",
+            "type": "u16"
+          },
+          {
+            "name": "owner",
+            "type": "pubkey"
+          },
+          {
+            "name": "protocol_fee_rate",
+            "type": "u32"
+          },
+          {
+            "name": "trade_fee_rate",
+            "type": "u32"
+          },
+          {
+            "name": "tick_spacing",
+            "type": "u16"
+          },
+          {
+            "name": "fund_fee_rate",
+            "type": "u32"
+          },
+          {
+            "name": "fund_owner",
+            "type": "pubkey"
+          }
+        ]
+      }
+    },
+    {
+      "name": "CreatePersonalPositionEvent",
+      "docs": [
+        "Emitted when create a new position"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "pool_state",
+            "docs": [
+              "The pool for which liquidity was added"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "minter",
+            "docs": [
+              "The address that create the position"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "nft_owner",
+            "docs": [
+              "The owner of the position and recipient of any minted liquidity"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "tick_lower_index",
+            "docs": [
+              "The lower tick of the position"
+            ],
+            "type": "i32"
+          },
+          {
+            "name": "tick_upper_index",
+            "docs": [
+              "The upper tick of the position"
+            ],
+            "type": "i32"
+          },
+          {
+            "name": "liquidity",
+            "docs": [
+              "The amount of liquidity minted to the position range"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "deposit_amount_0",
+            "docs": [
+              "The amount of token_0 was deposit for the liquidity"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "deposit_amount_1",
+            "docs": [
+              "The amount of token_1 was deposit for the liquidity"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "deposit_amount_0_transfer_fee",
+            "docs": [
+              "The token transfer fee for deposit_amount_0"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "deposit_amount_1_transfer_fee",
+            "docs": [
+              "The token transfer fee for deposit_amount_1"
+            ],
+            "type": "u64"
+          }
+        ]
+      }
+    },
+    {
+      "name": "DecreaseLiquidityEvent",
+      "docs": [
+        "Emitted when liquidity is decreased."
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "position_nft_mint",
+            "docs": [
+              "The ID of the token for which liquidity was decreased"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "liquidity",
+            "docs": [
+              "The amount by which liquidity for the position was decreased"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "decrease_amount_0",
+            "docs": [
+              "The amount of token_0 that was paid for the decrease in liquidity"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "decrease_amount_1",
+            "docs": [
+              "The amount of token_1 that was paid for the decrease in liquidity"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "fee_amount_0",
+            "type": "u64"
+          },
+          {
+            "name": "fee_amount_1",
+            "docs": [
+              "The amount of token_1 fee"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "reward_amounts",
+            "docs": [
+              "The amount of rewards"
+            ],
+            "type": {
+              "array": [
+                "u64",
+                3
+              ]
+            }
+          },
+          {
+            "name": "transfer_fee_0",
+            "docs": [
+              "The amount of token_0 transfer fee"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "transfer_fee_1",
+            "docs": [
+              "The amount of token_1 transfer fee"
+            ],
+            "type": "u64"
+          }
+        ]
+      }
+    },
+    {
+      "name": "IncreaseLiquidityEvent",
+      "docs": [
+        "Emitted when liquidity is increased."
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "position_nft_mint",
+            "docs": [
+              "The ID of the token for which liquidity was increased"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "liquidity",
+            "docs": [
+              "The amount by which liquidity for the NFT position was increased"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "amount_0",
+            "docs": [
+              "The amount of token_0 that was paid for the increase in liquidity"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "amount_1",
+            "docs": [
+              "The amount of token_1 that was paid for the increase in liquidity"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "amount_0_transfer_fee",
+            "docs": [
+              "The token transfer fee for amount_0"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "amount_1_transfer_fee",
+            "docs": [
+              "The token transfer fee for amount_1"
+            ],
+            "type": "u64"
+          }
+        ]
+      }
+    },
+    {
+      "name": "InitAdminGroupParams",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "fee_keeper",
+            "docs": [
+              "the address who can hold the fee,",
+              "anyone can trigger the fee collection action,"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "reward_config_manager",
+            "docs": [
+              "the address who can config the reward(config, deposit, withdraw),",
+              "deposit reward, set the account who can deposit the reward, withdraw the remaining reward(withdraw)"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "reward_claim_manager",
+            "docs": [
+              "the address who can manage the offchain reward claim"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "pool_manager",
+            "docs": [
+              "the address who can manage the pool create action,",
+              "without this account's permission, no one can create a pool"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "emergency_manager",
+            "docs": [
+              "the address who can manage the emergency action,",
+              "emergency action includes stop/resume the pool, stop/resume withdraw lp"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "normal_manager",
+            "docs": [
+              "normal action manager,",
+              "such as create amm config, update amm config"
+            ],
+            "type": "pubkey"
+          }
+        ]
+      }
+    },
+    {
+      "name": "InitializeRewardParam",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "open_time",
+            "docs": [
+              "Reward open time"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "end_time",
+            "docs": [
+              "Reward end time"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "emissions_per_second_x64",
+            "docs": [
+              "Token reward per second are earned per unit of liquidity"
+            ],
+            "type": "u128"
+          }
+        ]
+      }
+    },
+    {
+      "name": "LiquidityCalculateEvent",
+      "docs": [
+        "Emitted when liquidity decreased or increase."
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "pool_liquidity",
+            "docs": [
+              "The pool liquidity before decrease or increase"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "pool_sqrt_price_x64",
+            "docs": [
+              "The pool price when decrease or increase in liquidity"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "pool_tick",
+            "docs": [
+              "The pool tick when decrease or increase in liquidity"
+            ],
+            "type": "i32"
+          },
+          {
+            "name": "calc_amount_0",
+            "docs": [
+              "The amount of token_0 that was calculated for the decrease or increase in liquidity"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "calc_amount_1",
+            "docs": [
+              "The amount of token_1 that was calculated for the decrease or increase in liquidity"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "trade_fee_owed_0",
+            "type": "u64"
+          },
+          {
+            "name": "trade_fee_owed_1",
+            "docs": [
+              "The amount of token_1 fee"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "transfer_fee_0",
+            "docs": [
+              "The amount of token_0 transfer fee without trade_fee_amount_0"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "transfer_fee_1",
+            "docs": [
+              "The amount of token_1 transfer fee without trade_fee_amount_0"
+            ],
+            "type": "u64"
+          }
+        ]
+      }
+    },
+    {
+      "name": "LiquidityChangeEvent",
+      "docs": [
+        "Emitted pool liquidity change when increase and decrease liquidity"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "pool_state",
+            "docs": [
+              "The pool for swap"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "tick",
+            "docs": [
+              "The tick of the pool"
+            ],
+            "type": "i32"
+          },
+          {
+            "name": "tick_lower",
+            "docs": [
+              "The tick lower of position"
+            ],
+            "type": "i32"
+          },
+          {
+            "name": "tick_upper",
+            "docs": [
+              "The tick lower of position"
+            ],
+            "type": "i32"
+          },
+          {
+            "name": "liquidity_before",
+            "docs": [
+              "The liquidity of the pool before liquidity change"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "liquidity_after",
+            "docs": [
+              "The liquidity of the pool after liquidity change"
+            ],
+            "type": "u128"
+          }
+        ]
+      }
+    },
+    {
+      "name": "ModifyAmmAdminGroupEvent",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "fee_keeper",
+            "type": "pubkey"
+          },
+          {
+            "name": "reward_config_manager",
+            "type": "pubkey"
+          },
+          {
+            "name": "reward_claim_manager",
+            "type": "pubkey"
+          },
+          {
+            "name": "pool_manager",
+            "type": "pubkey"
+          },
+          {
+            "name": "emergency_manager",
+            "type": "pubkey"
+          },
+          {
+            "name": "normal_manager",
+            "type": "pubkey"
+          }
+        ]
+      }
+    },
+    {
+      "name": "Observation",
+      "docs": [
+        "The element of observations in ObservationState"
+      ],
+      "serialization": "bytemuckunsafe",
+      "repr": {
+        "kind": "c",
+        "packed": true
+      },
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "block_timestamp",
+            "docs": [
+              "The block timestamp of the observation"
+            ],
+            "type": "u32"
+          },
+          {
+            "name": "tick_cumulative",
+            "docs": [
+              "the cumulative of tick during the duration time"
+            ],
+            "type": "i64"
+          },
+          {
+            "name": "padding",
+            "docs": [
+              "padding for feature update"
+            ],
+            "type": {
+              "array": [
+                "u64",
+                4
+              ]
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "ObservationState",
+      "serialization": "bytemuckunsafe",
+      "repr": {
+        "kind": "c",
+        "packed": true
+      },
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "initialized",
+            "docs": [
+              "Whether the ObservationState is initialized"
+            ],
+            "type": "bool"
+          },
+          {
+            "name": "recent_epoch",
+            "docs": [
+              "recent update epoch"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "observation_index",
+            "docs": [
+              "the most-recently updated index of the observations array"
+            ],
+            "type": "u16"
+          },
+          {
+            "name": "pool_id",
+            "docs": [
+              "belongs to which pool"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "observations",
+            "docs": [
+              "observation array"
+            ],
+            "type": {
+              "array": [
+                {
+                  "defined": {
+                    "name": "Observation"
+                  }
+                },
+                100
+              ]
+            }
+          },
+          {
+            "name": "padding",
+            "docs": [
+              "padding for feature update"
+            ],
+            "type": {
+              "array": [
+                "u64",
+                4
+              ]
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "OffchainRewardConfig",
+      "docs": [
+        "Holds the current owner of the factory"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "pool_id",
+            "docs": [
+              "the pool state address"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "reward_vault",
+            "docs": [
+              "the vault to hold the reward",
+              "vault address is this config address"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "vault_bump",
+            "docs": [
+              "Bump to identify vault PDA"
+            ],
+            "type": {
+              "array": [
+                "u8",
+                1
+              ]
+            }
+          },
+          {
+            "name": "reward_mint_vec",
+            "docs": [
+              "reward token mint address list",
+              "reward token account is ATA(reward_vault, reward_mint)"
+            ],
+            "type": {
+              "vec": "pubkey"
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "OperationState",
+      "docs": [
+        "Holds the current owner of the factory"
+      ],
+      "serialization": "bytemuckunsafe",
+      "repr": {
+        "kind": "c",
+        "packed": true
+      },
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "bump",
+            "docs": [
+              "Bump to identify PDA"
+            ],
+            "type": "u8"
+          },
+          {
+            "name": "operation_owners",
+            "docs": [
+              "Address of the operation owner"
+            ],
+            "type": {
+              "array": [
+                "pubkey",
+                10
+              ]
+            }
+          },
+          {
+            "name": "whitelist_mints",
+            "docs": [
+              "The mint address of whitelist to emit reward"
+            ],
+            "type": {
+              "array": [
+                "pubkey",
+                100
+              ]
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "PersonalPositionState",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "bump",
+            "docs": [
+              "Bump to identify PDA"
+            ],
+            "type": {
+              "array": [
+                "u8",
+                1
+              ]
+            }
+          },
+          {
+            "name": "nft_mint",
+            "docs": [
+              "Mint address of the tokenized position"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "pool_id",
+            "docs": [
+              "The ID of the pool with which this token is connected"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "tick_lower_index",
+            "docs": [
+              "The lower bound tick of the position"
+            ],
+            "type": "i32"
+          },
+          {
+            "name": "tick_upper_index",
+            "docs": [
+              "The upper bound tick of the position"
+            ],
+            "type": "i32"
+          },
+          {
+            "name": "liquidity",
+            "docs": [
+              "The amount of liquidity owned by this position"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "fee_growth_inside_0_last_x64",
+            "docs": [
+              "The token_0 fee growth of the aggregate position as of the last action on the individual position"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "fee_growth_inside_1_last_x64",
+            "docs": [
+              "The token_1 fee growth of the aggregate position as of the last action on the individual position"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "token_fees_owed_0",
+            "docs": [
+              "The fees owed to the position owner in token_0, as of the last computation"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "token_fees_owed_1",
+            "docs": [
+              "The fees owed to the position owner in token_1, as of the last computation"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "reward_infos",
+            "type": {
+              "array": [
+                {
+                  "defined": {
+                    "name": "PositionRewardInfo"
+                  }
+                },
+                3
+              ]
+            }
+          },
+          {
+            "name": "recent_epoch",
+            "type": "u64"
+          },
+          {
+            "name": "padding",
+            "type": {
+              "array": [
+                "u64",
+                7
+              ]
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "PoolCreatedEvent",
+      "docs": [
+        "Emitted when a pool is created and initialized with a starting price",
+        ""
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "token_mint_0",
+            "docs": [
+              "The first token of the pool by address sort order"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "token_mint_1",
+            "docs": [
+              "The second token of the pool by address sort order"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "tick_spacing",
+            "docs": [
+              "The minimum number of ticks between initialized ticks"
+            ],
+            "type": "u16"
+          },
+          {
+            "name": "pool_state",
+            "docs": [
+              "The address of the created pool"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "sqrt_price_x64",
+            "docs": [
+              "The initial sqrt price of the pool, as a Q64.64"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "tick",
+            "docs": [
+              "The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool"
+            ],
+            "type": "i32"
+          },
+          {
+            "name": "token_vault_0",
+            "docs": [
+              "Vault of token_0"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "token_vault_1",
+            "docs": [
+              "Vault of token_1"
+            ],
+            "type": "pubkey"
+          }
+        ]
+      }
+    },
+    {
+      "name": "PoolState",
+      "docs": [
+        "The pool state",
+        "",
+        "PDA of `[POOL_SEED, config, token_mint_0, token_mint_1]`",
+        ""
+      ],
+      "serialization": "bytemuckunsafe",
+      "repr": {
+        "kind": "c",
+        "packed": true
+      },
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "bump",
+            "docs": [
+              "Bump to identify PDA"
+            ],
+            "type": {
+              "array": [
+                "u8",
+                1
+              ]
+            }
+          },
+          {
+            "name": "amm_config",
+            "type": "pubkey"
+          },
+          {
+            "name": "owner",
+            "type": "pubkey"
+          },
+          {
+            "name": "token_mint_0",
+            "docs": [
+              "Token pair of the pool, where token_mint_0 address < token_mint_1 address"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "token_mint_1",
+            "type": "pubkey"
+          },
+          {
+            "name": "token_vault_0",
+            "docs": [
+              "Token pair vault"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "token_vault_1",
+            "type": "pubkey"
+          },
+          {
+            "name": "observation_key",
+            "docs": [
+              "observation account key"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "mint_decimals_0",
+            "docs": [
+              "mint0 and mint1 decimals"
+            ],
+            "type": "u8"
+          },
+          {
+            "name": "mint_decimals_1",
+            "type": "u8"
+          },
+          {
+            "name": "tick_spacing",
+            "docs": [
+              "The minimum number of ticks between initialized ticks"
+            ],
+            "type": "u16"
+          },
+          {
+            "name": "liquidity",
+            "docs": [
+              "The currently in range liquidity available to the pool."
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "sqrt_price_x64",
+            "docs": [
+              "The current price of the pool as a sqrt(token_1/token_0) Q64.64 value"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "tick_current",
+            "docs": [
+              "The current tick of the pool, i.e. according to the last tick transition that was run."
+            ],
+            "type": "i32"
+          },
+          {
+            "name": "padding3",
+            "type": "u16"
+          },
+          {
+            "name": "padding4",
+            "type": "u16"
+          },
+          {
+            "name": "fee_growth_global_0_x64",
+            "docs": [
+              "The fee growth as a Q64.64 number, i.e. fees of token_0 and token_1 collected per",
+              "unit of liquidity for the entire life of the pool."
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "fee_growth_global_1_x64",
+            "type": "u128"
+          },
+          {
+            "name": "protocol_fees_token_0",
+            "docs": [
+              "The amounts of token_0 and token_1 that are owed to the protocol."
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "protocol_fees_token_1",
+            "type": "u64"
+          },
+          {
+            "name": "swap_in_amount_token_0",
+            "docs": [
+              "The amounts in and out of swap token_0 and token_1"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "swap_out_amount_token_1",
+            "type": "u128"
+          },
+          {
+            "name": "swap_in_amount_token_1",
+            "type": "u128"
+          },
+          {
+            "name": "swap_out_amount_token_0",
+            "type": "u128"
+          },
+          {
+            "name": "status",
+            "docs": [
+              "Bitwise representation of the state of the pool",
+              "bit0, 1: disable open position and increase liquidity, 0: normal",
+              "bit1, 1: disable decrease liquidity, 0: normal",
+              "bit2, 1: disable collect fee, 0: normal",
+              "bit3, 1: disable collect reward, 0: normal",
+              "bit4, 1: disable swap, 0: normal"
+            ],
+            "type": "u8"
+          },
+          {
+            "name": "padding",
+            "docs": [
+              "Leave blank for future use"
+            ],
+            "type": {
+              "array": [
+                "u8",
+                7
+              ]
+            }
+          },
+          {
+            "name": "reward_infos",
+            "type": {
+              "array": [
+                {
+                  "defined": {
+                    "name": "RewardInfo"
+                  }
+                },
+                3
+              ]
+            }
+          },
+          {
+            "name": "tick_array_bitmap",
+            "docs": [
+              "Packed initialized tick array state"
+            ],
+            "type": {
+              "array": [
+                "u64",
+                16
+              ]
+            }
+          },
+          {
+            "name": "total_fees_token_0",
+            "docs": [
+              "except protocol_fee and fund_fee"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "total_fees_claimed_token_0",
+            "docs": [
+              "except protocol_fee and fund_fee"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "total_fees_token_1",
+            "type": "u64"
+          },
+          {
+            "name": "total_fees_claimed_token_1",
+            "type": "u64"
+          },
+          {
+            "name": "fund_fees_token_0",
+            "type": "u64"
+          },
+          {
+            "name": "fund_fees_token_1",
+            "type": "u64"
+          },
+          {
+            "name": "open_time",
+            "type": "u64"
+          },
+          {
+            "name": "recent_epoch",
+            "type": "u64"
+          },
+          {
+            "name": "padding1",
+            "type": {
+              "array": [
+                "u64",
+                24
+              ]
+            }
+          },
+          {
+            "name": "padding2",
+            "type": {
+              "array": [
+                "u64",
+                32
+              ]
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "PositionRewardInfo",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "growth_inside_last_x64",
+            "type": "u128"
+          },
+          {
+            "name": "reward_amount_owed",
+            "type": "u64"
+          }
+        ]
+      }
+    },
+    {
+      "name": "ProtocolPositionState",
+      "docs": [
+        "Info stored for each user's position"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "bump",
+            "docs": [
+              "Bump to identify PDA"
+            ],
+            "type": "u8"
+          },
+          {
+            "name": "pool_id",
+            "docs": [
+              "The ID of the pool with which this token is connected"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "tick_lower_index",
+            "docs": [
+              "The lower bound tick of the position"
+            ],
+            "type": "i32"
+          },
+          {
+            "name": "tick_upper_index",
+            "docs": [
+              "The upper bound tick of the position"
+            ],
+            "type": "i32"
+          },
+          {
+            "name": "liquidity",
+            "docs": [
+              "The amount of liquidity owned by this position"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "fee_growth_inside_0_last_x64",
+            "docs": [
+              "The token_0 fee growth per unit of liquidity as of the last update to liquidity or fees owed"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "fee_growth_inside_1_last_x64",
+            "docs": [
+              "The token_1 fee growth per unit of liquidity as of the last update to liquidity or fees owed"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "token_fees_owed_0",
+            "docs": [
+              "The fees owed to the position owner in token_0"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "token_fees_owed_1",
+            "docs": [
+              "The fees owed to the position owner in token_1"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "reward_growth_inside",
+            "docs": [
+              "The reward growth per unit of liquidity as of the last update to liquidity"
+            ],
+            "type": {
+              "array": [
+                "u128",
+                3
+              ]
+            }
+          },
+          {
+            "name": "recent_epoch",
+            "type": "u64"
+          },
+          {
+            "name": "padding",
+            "type": {
+              "array": [
+                "u64",
+                7
+              ]
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "RewardInfo",
+      "serialization": "bytemuckunsafe",
+      "repr": {
+        "kind": "c",
+        "packed": true
+      },
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "reward_state",
+            "docs": [
+              "Reward state"
+            ],
+            "type": "u8"
+          },
+          {
+            "name": "open_time",
+            "docs": [
+              "Reward open time"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "end_time",
+            "docs": [
+              "Reward end time"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "last_update_time",
+            "docs": [
+              "Reward last update time"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "emissions_per_second_x64",
+            "docs": [
+              "Q64.64 number indicates how many tokens per second are earned per unit of liquidity."
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "reward_total_emissioned",
+            "docs": [
+              "The total amount of reward emissioned"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "reward_claimed",
+            "docs": [
+              "The total amount of claimed reward"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "token_mint",
+            "docs": [
+              "Reward token mint."
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "token_vault",
+            "docs": [
+              "Reward vault token account."
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "authority",
+            "docs": [
+              "The owner that has permission to set reward param"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "reward_growth_global_x64",
+            "docs": [
+              "Q64.64 number that tracks the total tokens earned per unit of liquidity since the reward",
+              "emissions were turned on."
+            ],
+            "type": "u128"
+          }
+        ]
+      }
+    },
+    {
+      "name": "SupportMintAssociated",
+      "docs": [
+        "Holds the current owner of the factory"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "bump",
+            "docs": [
+              "Bump to identify PDA"
+            ],
+            "type": "u8"
+          },
+          {
+            "name": "mint",
+            "docs": [
+              "Address of the supported token22 mint"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "padding",
+            "type": {
+              "array": [
+                "u64",
+                8
+              ]
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "SwapEvent",
+      "docs": [
+        "Emitted by when a swap is performed for a pool"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "pool_state",
+            "docs": [
+              "The pool for which token_0 and token_1 were swapped"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "sender",
+            "docs": [
+              "The address that initiated the swap call, and that received the callback"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "token_account_0",
+            "docs": [
+              "The payer token account in zero for one swaps, or the recipient token account",
+              "in one for zero swaps"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "token_account_1",
+            "docs": [
+              "The payer token account in one for zero swaps, or the recipient token account",
+              "in zero for one swaps"
+            ],
+            "type": "pubkey"
+          },
+          {
+            "name": "amount_0",
+            "docs": [
+              "The real delta amount of the token_0 of the pool or user"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "transfer_fee_0",
+            "docs": [
+              "The transfer fee charged by the withheld_amount of the token_0"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "amount_1",
+            "docs": [
+              "The real delta of the token_1 of the pool or user"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "transfer_fee_1",
+            "docs": [
+              "The transfer fee charged by the withheld_amount of the token_1"
+            ],
+            "type": "u64"
+          },
+          {
+            "name": "zero_for_one",
+            "docs": [
+              "if true, amount_0 is negtive and amount_1 is positive"
+            ],
+            "type": "bool"
+          },
+          {
+            "name": "sqrt_price_x64",
+            "docs": [
+              "The sqrt(price) of the pool after the swap, as a Q64.64"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "liquidity",
+            "docs": [
+              "The liquidity of the pool after the swap"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "tick",
+            "docs": [
+              "The log base 1.0001 of price of the pool after the swap"
+            ],
+            "type": "i32"
+          }
+        ]
+      }
+    },
+    {
+      "name": "TickArrayBitmapExtension",
+      "serialization": "bytemuckunsafe",
+      "repr": {
+        "kind": "c",
+        "packed": true
+      },
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "pool_id",
+            "type": "pubkey"
+          },
+          {
+            "name": "positive_tick_array_bitmap",
+            "docs": [
+              "Packed initialized tick array state for start_tick_index is positive"
+            ],
+            "type": {
+              "array": [
+                {
+                  "array": [
+                    "u64",
+                    8
+                  ]
+                },
+                14
+              ]
+            }
+          },
+          {
+            "name": "negative_tick_array_bitmap",
+            "docs": [
+              "Packed initialized tick array state for start_tick_index is negitive"
+            ],
+            "type": {
+              "array": [
+                {
+                  "array": [
+                    "u64",
+                    8
+                  ]
+                },
+                14
+              ]
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "TickArrayState",
+      "serialization": "bytemuckunsafe",
+      "repr": {
+        "kind": "c",
+        "packed": true
+      },
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "pool_id",
+            "type": "pubkey"
+          },
+          {
+            "name": "start_tick_index",
+            "type": "i32"
+          },
+          {
+            "name": "ticks",
+            "type": {
+              "array": [
+                {
+                  "defined": {
+                    "name": "TickState"
+                  }
+                },
+                60
+              ]
+            }
+          },
+          {
+            "name": "initialized_tick_count",
+            "type": "u8"
+          },
+          {
+            "name": "recent_epoch",
+            "type": "u64"
+          },
+          {
+            "name": "padding",
+            "type": {
+              "array": [
+                "u8",
+                107
+              ]
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "TickState",
+      "serialization": "bytemuckunsafe",
+      "repr": {
+        "kind": "c",
+        "packed": true
+      },
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "tick",
+            "type": "i32"
+          },
+          {
+            "name": "liquidity_net",
+            "docs": [
+              "Amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)"
+            ],
+            "type": "i128"
+          },
+          {
+            "name": "liquidity_gross",
+            "docs": [
+              "The total position liquidity that references this tick"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "fee_growth_outside_0_x64",
+            "docs": [
+              "Fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)",
+              "only has relative meaning, not absolute — the value depends on when the tick is initialized"
+            ],
+            "type": "u128"
+          },
+          {
+            "name": "fee_growth_outside_1_x64",
+            "type": "u128"
+          },
+          {
+            "name": "reward_growths_outside_x64",
+            "type": {
+              "array": [
+                "u128",
+                3
+              ]
+            }
+          },
+          {
+            "name": "padding",
+            "type": {
+              "array": [
+                "u32",
+                13
+              ]
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "UpdateAdminGroupParams",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "fee_keeper",
+            "docs": [
+              "the address who can hold the fee,",
+              "anyone can trigger the fee collection action,"
+            ],
+            "type": {
+              "option": "pubkey"
+            }
+          },
+          {
+            "name": "reward_config_manager",
+            "docs": [
+              "the address who can config the reward(config, deposit, withdraw),",
+              "deposit reward, set the account who can deposit the reward, withdraw the remaining reward(withdraw)"
+            ],
+            "type": {
+              "option": "pubkey"
+            }
+          },
+          {
+            "name": "reward_claim_manager",
+            "docs": [
+              "the address who can manage the offchain reward claim"
+            ],
+            "type": {
+              "option": "pubkey"
+            }
+          },
+          {
+            "name": "pool_manager",
+            "docs": [
+              "the address who can manage the pool create action,",
+              "without this account's permission, no one can create a pool"
+            ],
+            "type": {
+              "option": "pubkey"
+            }
+          },
+          {
+            "name": "emergency_manager",
+            "docs": [
+              "the address who can manage the emergency action,",
+              "emergency action includes stop/resume the pool, stop/resume withdraw lp"
+            ],
+            "type": {
+              "option": "pubkey"
+            }
+          },
+          {
+            "name": "normal_manager",
+            "docs": [
+              "normal action manager,",
+              "such as create amm config, update amm config"
+            ],
+            "type": {
+              "option": "pubkey"
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "UpdateRewardInfosEvent",
+      "docs": [
+        "Emitted when Reward are updated for a pool"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "reward_growth_global_x64",
+            "docs": [
+              "Reward info"
+            ],
+            "type": {
+              "array": [
+                "u128",
+                3
+              ]
+            }
+          }
+        ]
+      }
+    }
+  ]
+}

+ 5328 - 0
src/lib/clmm-sdk/src/instructions/target/types/byreal_amm_v3.ts

@@ -0,0 +1,5328 @@
+/**
+ * Program IDL in camelCase format in order to be used in JS/TS.
+ *
+ * Note that this is only a type helper and is not the actual IDL. The original
+ * IDL can be found at `target/idl/byreal_clmm.json`.
+ */
+export type ByrealClmm = {
+  address: 'REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2';
+  metadata: {
+    name: 'byrealClmm';
+    version: '0.1.0';
+    spec: '0.1.0';
+    description: 'Anchor client and source for Byreal concentrated liquidity AMM';
+  };
+  instructions: [
+    {
+      name: 'claimOffchainReward';
+      docs: ['claim offchain reward from the pool'];
+      discriminator: [195, 87, 221, 149, 141, 195, 146, 19];
+      accounts: [
+        {
+          name: 'claimer';
+          docs: ['the address who claim the offchain reward.'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'authority';
+          docs: ['The authority make decision that who can claim the offchain reward.'];
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['Initialize amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'poolId';
+          docs: ['the pool id, which is the pool state account.'];
+          relations: ['rewardConfig'];
+        },
+        {
+          name: 'tokenMint';
+        },
+        {
+          name: 'claimerTokenAccount';
+          docs: [''];
+          writable: true;
+        },
+        {
+          name: 'rewardVaultTokenAccount';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'account';
+                path: 'rewardConfig';
+              },
+              {
+                kind: 'account';
+                path: 'tokenProgram';
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint';
+              }
+            ];
+            program: {
+              kind: 'const';
+              value: [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ];
+            };
+          };
+        },
+        {
+          name: 'rewardConfig';
+          docs: ['The offchain reward config account, it also is the reward vault account.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 102, 102, 99, 104, 97, 105, 110, 95, 114, 101, 119, 97, 114, 100];
+              },
+              {
+                kind: 'account';
+                path: 'poolId';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Spl token program or token program 2022'];
+        },
+        {
+          name: 'associatedTokenProgram';
+          address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
+        }
+      ];
+      args: [
+        {
+          name: 'amount';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'closePosition';
+      docs: [
+        "Close the user's position and NFT account. If the NFT mint belongs to token2022, it will also be closed and the funds returned to the NFT owner.",
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        ''
+      ];
+      discriminator: [123, 134, 81, 0, 49, 68, 98, 98];
+      accounts: [
+        {
+          name: 'nftOwner';
+          docs: ['The position nft owner'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftMint';
+          docs: ['Mint address bound to the personal position.'];
+          writable: true;
+        },
+        {
+          name: 'positionNftAccount';
+          docs: ['User token account where position NFT be minted to'];
+          writable: true;
+        },
+        {
+          name: 'personalPosition';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'positionNftMint';
+              }
+            ];
+          };
+        },
+        {
+          name: 'systemProgram';
+          docs: ['System program to close the position state account'];
+          address: '11111111111111111111111111111111';
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Token/Token2022 program to close token/mint account'];
+        }
+      ];
+      args: [];
+    },
+    {
+      name: 'collectFundFee';
+      docs: [
+        'Collect the fund fee accrued to the pool',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `amount_0_requested` - The maximum amount of token_0 to send, can be 0 to collect fees in only token_1',
+        '* `amount_1_requested` - The maximum amount of token_1 to send, can be 0 to collect fees in only token_0',
+        ''
+      ];
+      discriminator: [167, 138, 78, 149, 223, 194, 6, 126];
+      accounts: [
+        {
+          name: 'adminGroup';
+          docs: [
+            'amm admin group account to store admin permissions.',
+            'anyone can collect fee, but only fee-manager in admin group can receive fee'
+          ];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'poolState';
+          docs: ['Pool state stores accumulated protocol fee amount'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'vault0Mint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'vault1Mint';
+          docs: ['The mint of token vault 1'];
+        },
+        {
+          name: 'recipientTokenAccount0';
+          docs: ['The address that receives the collected token_0 protocol fees'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'account';
+                path: 'admin_group.fee_keeper';
+                account: 'ammAdminGroup';
+              },
+              {
+                kind: 'const';
+                value: [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ];
+              },
+              {
+                kind: 'account';
+                path: 'vault0Mint';
+              }
+            ];
+            program: {
+              kind: 'const';
+              value: [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ];
+            };
+          };
+        },
+        {
+          name: 'recipientTokenAccount1';
+          docs: ['The address that receives the collected token_1 protocol fees'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'account';
+                path: 'admin_group.fee_keeper';
+                account: 'ammAdminGroup';
+              },
+              {
+                kind: 'const';
+                value: [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ];
+              },
+              {
+                kind: 'account';
+                path: 'vault1Mint';
+              }
+            ];
+            program: {
+              kind: 'const';
+              value: [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ];
+            };
+          };
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['The SPL program to perform token transfers'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['The SPL program 2022 to perform token transfers'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'associatedTokenProgram';
+          address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
+        }
+      ];
+      args: [
+        {
+          name: 'amount0Requested';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Requested';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'collectProtocolFee';
+      docs: [
+        'Collect the protocol fee accrued to the pool',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `amount_0_requested` - The maximum amount of token_0 to send, can be 0 to collect fees in only token_1',
+        '* `amount_1_requested` - The maximum amount of token_1 to send, can be 0 to collect fees in only token_0',
+        ''
+      ];
+      discriminator: [136, 136, 252, 221, 194, 66, 126, 89];
+      accounts: [
+        {
+          name: 'adminGroup';
+          docs: [
+            'amm admin group account to store admin permissions.',
+            'anyone can collect fee, but only fee-manager in admin group can receive fee'
+          ];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'poolState';
+          docs: ['Pool state stores accumulated protocol fee amount'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'vault0Mint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'vault1Mint';
+          docs: ['The mint of token vault 1'];
+        },
+        {
+          name: 'recipientTokenAccount0';
+          docs: ['The address that receives the collected token_0 protocol fees'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'account';
+                path: 'admin_group.fee_keeper';
+                account: 'ammAdminGroup';
+              },
+              {
+                kind: 'const';
+                value: [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ];
+              },
+              {
+                kind: 'account';
+                path: 'vault0Mint';
+              }
+            ];
+            program: {
+              kind: 'const';
+              value: [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ];
+            };
+          };
+        },
+        {
+          name: 'recipientTokenAccount1';
+          docs: ['The address that receives the collected token_1 protocol fees'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'account';
+                path: 'admin_group.fee_keeper';
+                account: 'ammAdminGroup';
+              },
+              {
+                kind: 'const';
+                value: [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ];
+              },
+              {
+                kind: 'account';
+                path: 'vault1Mint';
+              }
+            ];
+            program: {
+              kind: 'const';
+              value: [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ];
+            };
+          };
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['The SPL program to perform token transfers'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['The SPL program 2022 to perform token transfers'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'associatedTokenProgram';
+          address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
+        }
+      ];
+      args: [
+        {
+          name: 'amount0Requested';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Requested';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'collectRemainingRewards';
+      docs: [
+        'Collect remaining reward token for reward founder',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `reward_index` - the index to reward info',
+        ''
+      ];
+      discriminator: [18, 237, 166, 197, 34, 16, 213, 144];
+      accounts: [
+        {
+          name: 'rewardFunder';
+          docs: ['The founder who init reward info previously'];
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'funderTokenAccount';
+          docs: ["The funder's reward token account"];
+          writable: true;
+        },
+        {
+          name: 'poolState';
+          docs: ['Set reward for this pool'];
+          writable: true;
+        },
+        {
+          name: 'rewardTokenVault';
+          docs: ['Reward vault transfer remaining token to founder token account'];
+        },
+        {
+          name: 'rewardVaultMint';
+          docs: ['The mint of reward token vault'];
+        },
+        {
+          name: 'tokenProgram';
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['Token program 2022'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'memoProgram';
+          docs: ['memo program'];
+          address: 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
+        }
+      ];
+      args: [
+        {
+          name: 'rewardIndex';
+          type: 'u8';
+        }
+      ];
+    },
+    {
+      name: 'createAmmConfig';
+      docs: [
+        '# Arguments',
+        '',
+        '* `ctx`- The accounts needed by instruction.',
+        '* `index` - The index of amm config, there may be multiple config.',
+        '* `tick_spacing` - The tickspacing binding with config, cannot be changed.',
+        '* `trade_fee_rate` - Trade fee rate, can be changed.',
+        '* `protocol_fee_rate` - The rate of protocol fee within trade fee.',
+        '* `fund_fee_rate` - The rate of fund fee within trade fee.',
+        ''
+      ];
+      discriminator: [137, 52, 237, 212, 215, 117, 108, 104];
+      accounts: [
+        {
+          name: 'owner';
+          docs: ['Address to be set as normal manager in admin group.'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'ammConfig';
+          docs: ['Initialize config state account to store protocol owner address and fee rates.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 109, 109, 95, 99, 111, 110, 102, 105, 103];
+              },
+              {
+                kind: 'arg';
+                path: 'index';
+              }
+            ];
+          };
+        },
+        {
+          name: 'systemProgram';
+          address: '11111111111111111111111111111111';
+        }
+      ];
+      args: [
+        {
+          name: 'index';
+          type: 'u16';
+        },
+        {
+          name: 'tickSpacing';
+          type: 'u16';
+        },
+        {
+          name: 'tradeFeeRate';
+          type: 'u32';
+        },
+        {
+          name: 'protocolFeeRate';
+          type: 'u32';
+        },
+        {
+          name: 'fundFeeRate';
+          type: 'u32';
+        }
+      ];
+    },
+    {
+      name: 'createOperationAccount';
+      docs: [
+        'Creates an operation account for the program',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        ''
+      ];
+      discriminator: [63, 87, 148, 33, 109, 35, 8, 104];
+      accounts: [
+        {
+          name: 'owner';
+          docs: ['Address to be set as operation account owner.'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'operationState';
+          docs: ['Initialize operation state account to store operation owner address and white list mint.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 112, 101, 114, 97, 116, 105, 111, 110];
+              }
+            ];
+          };
+        },
+        {
+          name: 'systemProgram';
+          address: '11111111111111111111111111111111';
+        }
+      ];
+      args: [];
+    },
+    {
+      name: 'createPool';
+      docs: [
+        'Creates a pool for the given token pair and the initial price',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `sqrt_price_x64` - the initial sqrt price (amount_token_1 / amount_token_0) of the pool as a Q64.64',
+        'Note: The open_time must be smaller than the current block_timestamp on chain.'
+      ];
+      discriminator: [233, 146, 209, 142, 207, 104, 64, 188];
+      accounts: [
+        {
+          name: 'poolCreator';
+          docs: ['Address paying to create the pool. Can be anyone'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'poolManager';
+          docs: ['with pool_manager permission, the pool creator can create a pool.'];
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'ammConfig';
+          docs: ['Which config the pool belongs to.'];
+        },
+        {
+          name: 'poolState';
+          docs: ['Initialize an account to store the pool state'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 111, 108];
+              },
+              {
+                kind: 'account';
+                path: 'ammConfig';
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint0';
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint1';
+              }
+            ];
+          };
+        },
+        {
+          name: 'offchainRewardConfig';
+          docs: ['Initialize an account to store the off-chain reward config'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 102, 102, 99, 104, 97, 105, 110, 95, 114, 101, 119, 97, 114, 100];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenMint0';
+          docs: ['Token_0 mint, the key must be smaller then token_1 mint.'];
+        },
+        {
+          name: 'tokenMint1';
+          docs: ['Token_1 mint'];
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['Token_0 vault for the pool'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 111, 108, 95, 118, 97, 117, 108, 116];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint0';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['Token_1 vault for the pool'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 111, 108, 95, 118, 97, 117, 108, 116];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint1';
+              }
+            ];
+          };
+        },
+        {
+          name: 'observationState';
+          docs: ['Initialize an account to store oracle observations'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 98, 115, 101, 114, 118, 97, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayBitmap';
+          docs: ['Initialize an account to store if a tick array is initialized.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [
+                  112,
+                  111,
+                  111,
+                  108,
+                  95,
+                  116,
+                  105,
+                  99,
+                  107,
+                  95,
+                  97,
+                  114,
+                  114,
+                  97,
+                  121,
+                  95,
+                  98,
+                  105,
+                  116,
+                  109,
+                  97,
+                  112,
+                  95,
+                  101,
+                  120,
+                  116,
+                  101,
+                  110,
+                  115,
+                  105,
+                  111,
+                  110
+                ];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenProgram0';
+          docs: ['Spl token program or token program 2022'];
+        },
+        {
+          name: 'tokenProgram1';
+          docs: ['Spl token program or token program 2022'];
+        },
+        {
+          name: 'systemProgram';
+          docs: ['To create a new program account'];
+          address: '11111111111111111111111111111111';
+        },
+        {
+          name: 'rent';
+          docs: ['Sysvar for program account'];
+          address: 'SysvarRent111111111111111111111111111111111';
+        }
+      ];
+      args: [
+        {
+          name: 'sqrtPriceX64';
+          type: 'u128';
+        },
+        {
+          name: 'openTime';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'createSupportMintAssociated';
+      docs: [
+        'Create support token22 mint account which can create pool and send rewards with ignoring the not support extensions.'
+      ];
+      discriminator: [17, 251, 65, 92, 136, 242, 14, 169];
+      accounts: [
+        {
+          name: 'owner';
+          docs: ['Address to be set as protocol owner.'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenMint';
+          docs: ['Support token mint'];
+        },
+        {
+          name: 'supportMintAssociated';
+          docs: ['Initialize support mint state account to store support mint address and bump.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [115, 117, 112, 112, 111, 114, 116, 95, 109, 105, 110, 116];
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint';
+              }
+            ];
+          };
+        },
+        {
+          name: 'systemProgram';
+          address: '11111111111111111111111111111111';
+        }
+      ];
+      args: [];
+    },
+    {
+      name: 'decreaseLiquidity';
+      docs: [
+        '#[deprecated(note = "Use `decrease_liquidity_v2` instead.")]',
+        'Decreases liquidity for an existing position',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` -  The context of accounts',
+        '* `liquidity` - The amount by which liquidity will be decreased',
+        '* `amount_0_min` - The minimum amount of token_0 that should be accounted for the burned liquidity',
+        '* `amount_1_min` - The minimum amount of token_1 that should be accounted for the burned liquidity',
+        ''
+      ];
+      discriminator: [160, 38, 208, 111, 104, 91, 44, 1];
+      accounts: [
+        {
+          name: 'nftOwner';
+          docs: ['The position owner or delegated authority'];
+          signer: true;
+        },
+        {
+          name: 'nftAccount';
+          docs: ['The token account for the tokenized position'];
+        },
+        {
+          name: 'personalPosition';
+          docs: ['Decrease liquidity for this position'];
+          writable: true;
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_lower_index';
+                account: 'personalPositionState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_upper_index';
+                account: 'personalPositionState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['Token_0 vault'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['Token_1 vault'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayLower';
+          docs: ['Stores init state for the lower tick'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayUpper';
+          docs: ['Stores init state for the upper tick'];
+          writable: true;
+        },
+        {
+          name: 'recipientTokenAccount0';
+          docs: ['The destination token account for receive amount_0'];
+          writable: true;
+        },
+        {
+          name: 'recipientTokenAccount1';
+          docs: ['The destination token account for receive amount_1'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['SPL program to transfer out tokens'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        }
+      ];
+      args: [
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Min';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Min';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'decreaseLiquidityV2';
+      docs: [
+        'Decreases liquidity for an existing position, support Token2022',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` -  The context of accounts',
+        '* `liquidity` - The amount by which liquidity will be decreased',
+        '* `amount_0_min` - The minimum amount of token_0 that should be accounted for the burned liquidity',
+        '* `amount_1_min` - The minimum amount of token_1 that should be accounted for the burned liquidity',
+        ''
+      ];
+      discriminator: [58, 127, 188, 62, 79, 82, 196, 96];
+      accounts: [
+        {
+          name: 'nftOwner';
+          docs: ['The position owner or delegated authority'];
+          signer: true;
+        },
+        {
+          name: 'nftAccount';
+          docs: ['The token account for the tokenized position'];
+        },
+        {
+          name: 'personalPosition';
+          docs: ['Decrease liquidity for this position'];
+          writable: true;
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_lower_index';
+                account: 'personalPositionState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_upper_index';
+                account: 'personalPositionState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['Token_0 vault'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['Token_1 vault'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayLower';
+          docs: ['Stores init state for the lower tick'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayUpper';
+          docs: ['Stores init state for the upper tick'];
+          writable: true;
+        },
+        {
+          name: 'recipientTokenAccount0';
+          docs: ['The destination token account for receive amount_0'];
+          writable: true;
+        },
+        {
+          name: 'recipientTokenAccount1';
+          docs: ['The destination token account for receive amount_1'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['SPL program to transfer out tokens'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['Token program 2022'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'memoProgram';
+          docs: ['memo program'];
+          address: 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
+        },
+        {
+          name: 'vault0Mint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'vault1Mint';
+          docs: ['The mint of token vault 1'];
+        }
+      ];
+      args: [
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Min';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Min';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'depositOffchainReward';
+      docs: ['deposit offchain reward into the pool'];
+      discriminator: [97, 125, 48, 169, 92, 241, 44, 142];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['the address paying to deposit the offchain reward.'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'authority';
+          docs: ['The authority make decision that who can deposit the offchain reward.'];
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['Initialize amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'poolId';
+          docs: ['the pool id, which is the pool state account.'];
+          relations: ['rewardConfig'];
+        },
+        {
+          name: 'tokenMint';
+        },
+        {
+          name: 'payerTokenAccount';
+          docs: [''];
+          writable: true;
+        },
+        {
+          name: 'rewardVaultTokenAccount';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'account';
+                path: 'rewardConfig';
+              },
+              {
+                kind: 'account';
+                path: 'tokenProgram';
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint';
+              }
+            ];
+            program: {
+              kind: 'const';
+              value: [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ];
+            };
+          };
+        },
+        {
+          name: 'rewardConfig';
+          docs: ['The offchain reward config account, it also is the reward vault account.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 102, 102, 99, 104, 97, 105, 110, 95, 114, 101, 119, 97, 114, 100];
+              },
+              {
+                kind: 'account';
+                path: 'poolId';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Spl token program or token program 2022'];
+        },
+        {
+          name: 'associatedTokenProgram';
+          address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
+        },
+        {
+          name: 'systemProgram';
+          address: '11111111111111111111111111111111';
+        }
+      ];
+      args: [
+        {
+          name: 'amount';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'increaseLiquidity';
+      docs: [
+        '#[deprecated(note = "Use `increase_liquidity_v2` instead.")]',
+        'Increases liquidity for an existing position, with amount paid by `payer`',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        "* `liquidity` - The desired liquidity to be added, can't be zero",
+        '* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check',
+        '* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check',
+        ''
+      ];
+      discriminator: [46, 156, 243, 118, 13, 205, 251, 178];
+      accounts: [
+        {
+          name: 'nftOwner';
+          docs: ['Pays to mint the position'];
+          signer: true;
+        },
+        {
+          name: 'nftAccount';
+          docs: ['The token account for nft'];
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_lower_index';
+                account: 'personalPositionState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_upper_index';
+                account: 'personalPositionState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'personalPosition';
+          docs: ['Increase liquidity for this position'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayLower';
+          docs: ['Stores init state for the lower tick'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayUpper';
+          docs: ['Stores init state for the upper tick'];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount0';
+          docs: ["The payer's token account for token_0"];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount1';
+          docs: ['The token account spending token_1 to mint the position'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Program to create mint account and mint tokens'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        }
+      ];
+      args: [
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Max';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Max';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'increaseLiquidityV2';
+      docs: [
+        'Increases liquidity for an existing position, with amount paid by `payer`, support Token2022',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `liquidity` - The desired liquidity to be added, if zero, calculate liquidity base amount_0 or amount_1 according base_flag',
+        '* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check',
+        '* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check',
+        '* `base_flag` - must be specified if liquidity is zero, true: calculate liquidity base amount_0_max otherwise base amount_1_max',
+        ''
+      ];
+      discriminator: [133, 29, 89, 223, 69, 238, 176, 10];
+      accounts: [
+        {
+          name: 'nftOwner';
+          docs: ['Pays to mint the position'];
+          signer: true;
+        },
+        {
+          name: 'nftAccount';
+          docs: ['The token account for nft'];
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_lower_index';
+                account: 'personalPositionState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_upper_index';
+                account: 'personalPositionState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'personalPosition';
+          docs: ['Increase liquidity for this position'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayLower';
+          docs: ['Stores init state for the lower tick'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayUpper';
+          docs: ['Stores init state for the upper tick'];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount0';
+          docs: ["The payer's token account for token_0"];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount1';
+          docs: ['The token account spending token_1 to mint the position'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Program to create mint account and mint tokens'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['Token program 2022'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'vault0Mint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'vault1Mint';
+          docs: ['The mint of token vault 1'];
+        }
+      ];
+      args: [
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Max';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Max';
+          type: 'u64';
+        },
+        {
+          name: 'baseFlag';
+          type: {
+            option: 'bool';
+          };
+        }
+      ];
+    },
+    {
+      name: 'initAmmAdminGroup';
+      docs: ['Initialize the AMM admin group account, which is used to manage the AMM protocol.'];
+      discriminator: [209, 108, 32, 246, 157, 214, 237, 86];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['only super admin can create admin group'];
+          writable: true;
+          signer: true;
+          address: 'AY196f8U5EvM999PVnvLmyvaUnzL4GLiFaGKUgnJXN6o';
+        },
+        {
+          name: 'adminGroup';
+          docs: ['Initialize amm admin group account to store admin permissions.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'systemProgram';
+          address: '11111111111111111111111111111111';
+        }
+      ];
+      args: [
+        {
+          name: 'params';
+          type: {
+            defined: {
+              name: 'initAdminGroupParams';
+            };
+          };
+        }
+      ];
+    },
+    {
+      name: 'initializeReward';
+      docs: [
+        'Initialize a reward info for a given pool and reward index',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `reward_index` - the index to reward info',
+        '* `open_time` - reward open timestamp',
+        '* `end_time` - reward end timestamp',
+        '* `emissions_per_second_x64` - Token reward per second are earned per unit of liquidity.',
+        ''
+      ];
+      discriminator: [95, 135, 192, 196, 242, 129, 230, 68];
+      accounts: [
+        {
+          name: 'rewardFunder';
+          docs: ['The founder deposit reward token to vault'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'funderTokenAccount';
+          writable: true;
+        },
+        {
+          name: 'ammConfig';
+          docs: ['For check the reward_funder authority'];
+        },
+        {
+          name: 'poolState';
+          docs: ['Set reward for this pool'];
+          writable: true;
+        },
+        {
+          name: 'operationState';
+          docs: ['load info from the account to judge reward permission'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 112, 101, 114, 97, 116, 105, 111, 110];
+              }
+            ];
+          };
+        },
+        {
+          name: 'rewardTokenMint';
+          docs: ['Reward mint'];
+        },
+        {
+          name: 'rewardTokenVault';
+          docs: ['A pda, reward vault'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 111, 108, 95, 114, 101, 119, 97, 114, 100, 95, 118, 97, 117, 108, 116];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'rewardTokenMint';
+              }
+            ];
+          };
+        },
+        {
+          name: 'rewardTokenProgram';
+        },
+        {
+          name: 'systemProgram';
+          address: '11111111111111111111111111111111';
+        },
+        {
+          name: 'rent';
+          address: 'SysvarRent111111111111111111111111111111111';
+        }
+      ];
+      args: [
+        {
+          name: 'param';
+          type: {
+            defined: {
+              name: 'initializeRewardParam';
+            };
+          };
+        }
+      ];
+    },
+    {
+      name: 'openPosition';
+      docs: [
+        '#[deprecated(note = "Use `open_position_with_token22_nft` instead.")]',
+        'Creates a new position wrapped in a NFT',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `tick_lower_index` - The low boundary of market',
+        '* `tick_upper_index` - The upper boundary of market',
+        '* `tick_array_lower_start_index` - The start index of tick array which include tick low',
+        '* `tick_array_upper_start_index` - The start index of tick array which include tick upper',
+        '* `liquidity` - The liquidity to be added',
+        '* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check',
+        '* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check',
+        ''
+      ];
+      discriminator: [135, 128, 47, 77, 15, 152, 240, 49];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['Pays to mint the position'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftOwner';
+        },
+        {
+          name: 'positionNftMint';
+          docs: ['Unique token mint address'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftAccount';
+          docs: [
+            'Token account where position NFT will be minted',
+            'This account created in the contract by cpi to avoid large stack variables'
+          ];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'account';
+                path: 'positionNftOwner';
+              },
+              {
+                kind: 'const';
+                value: [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ];
+              },
+              {
+                kind: 'account';
+                path: 'positionNftMint';
+              }
+            ];
+            program: {
+              kind: 'const';
+              value: [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ];
+            };
+          };
+        },
+        {
+          name: 'metadataAccount';
+          docs: ['To store metaplex metadata'];
+          writable: true;
+        },
+        {
+          name: 'poolState';
+          docs: ['Add liquidity for this pool'];
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          docs: ['Store the information of market marking in range'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickLowerIndex';
+              },
+              {
+                kind: 'arg';
+                path: 'tickUpperIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayLower';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [116, 105, 99, 107, 95, 97, 114, 114, 97, 121];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickArrayLowerStartIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayUpper';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [116, 105, 99, 107, 95, 97, 114, 114, 97, 121];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickArrayUpperStartIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'personalPosition';
+          docs: ['personal position state'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'positionNftMint';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenAccount0';
+          docs: ['The token_0 account deposit token to the pool'];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount1';
+          docs: ['The token_1 account deposit token to the pool'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'rent';
+          docs: ['Sysvar for token mint and ATA creation'];
+          address: 'SysvarRent111111111111111111111111111111111';
+        },
+        {
+          name: 'systemProgram';
+          docs: ['Program to create the position manager state account'];
+          address: '11111111111111111111111111111111';
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Program to create mint account and mint tokens'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'associatedTokenProgram';
+          docs: ['Program to create an ATA for receiving position NFT'];
+          address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
+        },
+        {
+          name: 'metadataProgram';
+          docs: ['Program to create NFT metadata'];
+          address: 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s';
+        }
+      ];
+      args: [
+        {
+          name: 'tickLowerIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickUpperIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickArrayLowerStartIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickArrayUpperStartIndex';
+          type: 'i32';
+        },
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Max';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Max';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'openPositionV2';
+      docs: [
+        '#[deprecated(note = "Use `open_position_with_token22_nft` instead.")]',
+        'Creates a new position wrapped in a NFT, support Token2022',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `tick_lower_index` - The low boundary of market',
+        '* `tick_upper_index` - The upper boundary of market',
+        '* `tick_array_lower_start_index` - The start index of tick array which include tick low',
+        '* `tick_array_upper_start_index` - The start index of tick array which include tick upper',
+        '* `liquidity` - The liquidity to be added, if zero, and the base_flag is specified, calculate liquidity base amount_0_max or amount_1_max according base_flag, otherwise open position with zero liquidity',
+        '* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check',
+        '* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check',
+        '* `with_metadata` - The flag indicating whether to create NFT mint metadata',
+        '* `base_flag` - if the liquidity specified as zero, true: calculate liquidity base amount_0_max otherwise base amount_1_max',
+        ''
+      ];
+      discriminator: [77, 184, 74, 214, 112, 86, 241, 199];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['Pays to mint the position'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftOwner';
+        },
+        {
+          name: 'positionNftMint';
+          docs: ['Unique token mint address'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftAccount';
+          docs: ['Token account where position NFT will be minted'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'account';
+                path: 'positionNftOwner';
+              },
+              {
+                kind: 'const';
+                value: [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ];
+              },
+              {
+                kind: 'account';
+                path: 'positionNftMint';
+              }
+            ];
+            program: {
+              kind: 'const';
+              value: [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ];
+            };
+          };
+        },
+        {
+          name: 'metadataAccount';
+          docs: ['To store metaplex metadata'];
+          writable: true;
+        },
+        {
+          name: 'poolState';
+          docs: ['Add liquidity for this pool'];
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          docs: ['Store the information of market marking in range'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickLowerIndex';
+              },
+              {
+                kind: 'arg';
+                path: 'tickUpperIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayLower';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [116, 105, 99, 107, 95, 97, 114, 114, 97, 121];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickArrayLowerStartIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayUpper';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [116, 105, 99, 107, 95, 97, 114, 114, 97, 121];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickArrayUpperStartIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'personalPosition';
+          docs: ['personal position state'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'positionNftMint';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenAccount0';
+          docs: ['The token_0 account deposit token to the pool'];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount1';
+          docs: ['The token_1 account deposit token to the pool'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'rent';
+          docs: ['Sysvar for token mint and ATA creation'];
+          address: 'SysvarRent111111111111111111111111111111111';
+        },
+        {
+          name: 'systemProgram';
+          docs: ['Program to create the position manager state account'];
+          address: '11111111111111111111111111111111';
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Program to create mint account and mint tokens'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'associatedTokenProgram';
+          docs: ['Program to create an ATA for receiving position NFT'];
+          address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
+        },
+        {
+          name: 'metadataProgram';
+          docs: ['Program to create NFT metadata'];
+          address: 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['Program to create mint account and mint tokens'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'vault0Mint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'vault1Mint';
+          docs: ['The mint of token vault 1'];
+        }
+      ];
+      args: [
+        {
+          name: 'tickLowerIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickUpperIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickArrayLowerStartIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickArrayUpperStartIndex';
+          type: 'i32';
+        },
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Max';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Max';
+          type: 'u64';
+        },
+        {
+          name: 'withMetadata';
+          type: 'bool';
+        },
+        {
+          name: 'baseFlag';
+          type: {
+            option: 'bool';
+          };
+        }
+      ];
+    },
+    {
+      name: 'openPositionWithToken22Nft';
+      docs: [
+        'Creates a new position wrapped in a Token2022 NFT without relying on metadata_program and metadata_account, reduce the cost for user to create a personal position.',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `tick_lower_index` - The low boundary of market',
+        '* `tick_upper_index` - The upper boundary of market',
+        '* `tick_array_lower_start_index` - The start index of tick array which include tick low',
+        '* `tick_array_upper_start_index` - The start index of tick array which include tick upper',
+        '* `liquidity` - The liquidity to be added, if zero, and the base_flag is specified, calculate liquidity base amount_0_max or amount_1_max according base_flag, otherwise open position with zero liquidity',
+        '* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check',
+        '* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check',
+        '* `with_metadata` - The flag indicating whether to create NFT mint metadata',
+        '* `base_flag` - if the liquidity specified as zero, true: calculate liquidity base amount_0_max otherwise base amount_1_max',
+        ''
+      ];
+      discriminator: [77, 255, 174, 82, 125, 29, 201, 46];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['Pays to mint the position'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftOwner';
+        },
+        {
+          name: 'positionNftMint';
+          docs: ['Unique token mint address, initialize in contract'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftAccount';
+          writable: true;
+        },
+        {
+          name: 'poolState';
+          docs: ['Add liquidity for this pool'];
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          docs: ['Store the information of market marking in range'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickLowerIndex';
+              },
+              {
+                kind: 'arg';
+                path: 'tickUpperIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayLower';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [116, 105, 99, 107, 95, 97, 114, 114, 97, 121];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickArrayLowerStartIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayUpper';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [116, 105, 99, 107, 95, 97, 114, 114, 97, 121];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickArrayUpperStartIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'personalPosition';
+          docs: ['personal position state'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'positionNftMint';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenAccount0';
+          docs: ['The token_0 account deposit token to the pool'];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount1';
+          docs: ['The token_1 account deposit token to the pool'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'rent';
+          docs: ['Sysvar for token mint and ATA creation'];
+          address: 'SysvarRent111111111111111111111111111111111';
+        },
+        {
+          name: 'systemProgram';
+          docs: ['Program to create the position manager state account'];
+          address: '11111111111111111111111111111111';
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Program to transfer for token account'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'associatedTokenProgram';
+          docs: ['Program to create an ATA for receiving position NFT'];
+          address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['Program to create NFT mint/token account and transfer for token22 account'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'vault0Mint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'vault1Mint';
+          docs: ['The mint of token vault 1'];
+        }
+      ];
+      args: [
+        {
+          name: 'tickLowerIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickUpperIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickArrayLowerStartIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickArrayUpperStartIndex';
+          type: 'i32';
+        },
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Max';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Max';
+          type: 'u64';
+        },
+        {
+          name: 'withMetadata';
+          type: 'bool';
+        },
+        {
+          name: 'baseFlag';
+          type: {
+            option: 'bool';
+          };
+        }
+      ];
+    },
+    {
+      name: 'setRewardParams';
+      docs: [
+        'Reset reward param, start a new reward cycle or extend the current cycle.',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `reward_index` - The index of reward token in the pool.',
+        '* `emissions_per_second_x64` - The per second emission reward, when extend the current cycle,',
+        "new value can't be less than old value",
+        '* `open_time` - reward open timestamp, must be set when starting a new cycle',
+        '* `end_time` - reward end timestamp',
+        ''
+      ];
+      discriminator: [112, 52, 167, 75, 32, 201, 211, 137];
+      accounts: [
+        {
+          name: 'authority';
+          docs: ['Address to be set as protocol owner. It pays to create factory state account.'];
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'ammConfig';
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        },
+        {
+          name: 'operationState';
+          docs: ['load info from the account to judge reward permission'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 112, 101, 114, 97, 116, 105, 111, 110];
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Token program'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['Token program 2022'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        }
+      ];
+      args: [
+        {
+          name: 'rewardIndex';
+          type: 'u8';
+        },
+        {
+          name: 'emissionsPerSecondX64';
+          type: 'u128';
+        },
+        {
+          name: 'openTime';
+          type: 'u64';
+        },
+        {
+          name: 'endTime';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'swap';
+      docs: [
+        '#[deprecated(note = "Use `swap_v2` instead.")]',
+        'Swaps one token for as much as possible of another token across a single pool',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `amount` - Arranged in pairs with other_amount_threshold. (amount_in, amount_out_minimum) or (amount_out, amount_in_maximum)',
+        '* `other_amount_threshold` - For slippage check',
+        '* `sqrt_price_limit` - The Q64.64 sqrt price √P limit. If zero for one, the price cannot',
+        '* `is_base_input` - swap base input or swap base output',
+        ''
+      ];
+      discriminator: [248, 198, 158, 145, 225, 117, 135, 200];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['The user performing the swap'];
+          signer: true;
+        },
+        {
+          name: 'ammConfig';
+          docs: ['The factory state to read protocol fees'];
+        },
+        {
+          name: 'poolState';
+          docs: ['The program account of the pool in which the swap will be performed'];
+          writable: true;
+        },
+        {
+          name: 'inputTokenAccount';
+          docs: ['The user token account for input token'];
+          writable: true;
+        },
+        {
+          name: 'outputTokenAccount';
+          docs: ['The user token account for output token'];
+          writable: true;
+        },
+        {
+          name: 'inputVault';
+          docs: ['The vault token account for input token'];
+          writable: true;
+        },
+        {
+          name: 'outputVault';
+          docs: ['The vault token account for output token'];
+          writable: true;
+        },
+        {
+          name: 'observationState';
+          docs: ['The program account for the most recent oracle observation'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['SPL program for token transfers'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tickArray';
+          writable: true;
+        }
+      ];
+      args: [
+        {
+          name: 'amount';
+          type: 'u64';
+        },
+        {
+          name: 'otherAmountThreshold';
+          type: 'u64';
+        },
+        {
+          name: 'sqrtPriceLimitX64';
+          type: 'u128';
+        },
+        {
+          name: 'isBaseInput';
+          type: 'bool';
+        }
+      ];
+    },
+    {
+      name: 'swapV2';
+      docs: [
+        'Swaps one token for as much as possible of another token across a single pool, support token program 2022',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `amount` - Arranged in pairs with other_amount_threshold. (amount_in, amount_out_minimum) or (amount_out, amount_in_maximum)',
+        '* `other_amount_threshold` - For slippage check',
+        '* `sqrt_price_limit` - The Q64.64 sqrt price √P limit. If zero for one, the price cannot',
+        '* `is_base_input` - swap base input or swap base output',
+        ''
+      ];
+      discriminator: [43, 4, 237, 11, 26, 201, 30, 98];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['The user performing the swap'];
+          signer: true;
+        },
+        {
+          name: 'ammConfig';
+          docs: ['The factory state to read protocol fees'];
+        },
+        {
+          name: 'poolState';
+          docs: ['The program account of the pool in which the swap will be performed'];
+          writable: true;
+        },
+        {
+          name: 'inputTokenAccount';
+          docs: ['The user token account for input token'];
+          writable: true;
+        },
+        {
+          name: 'outputTokenAccount';
+          docs: ['The user token account for output token'];
+          writable: true;
+        },
+        {
+          name: 'inputVault';
+          docs: ['The vault token account for input token'];
+          writable: true;
+        },
+        {
+          name: 'outputVault';
+          docs: ['The vault token account for output token'];
+          writable: true;
+        },
+        {
+          name: 'observationState';
+          docs: ['The program account for the most recent oracle observation'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['SPL program for token transfers'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['SPL program 2022 for token transfers'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'memoProgram';
+          docs: ['Memo program'];
+          address: 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
+        },
+        {
+          name: 'inputVaultMint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'outputVaultMint';
+          docs: ['The mint of token vault 1'];
+        }
+      ];
+      args: [
+        {
+          name: 'amount';
+          type: 'u64';
+        },
+        {
+          name: 'otherAmountThreshold';
+          type: 'u64';
+        },
+        {
+          name: 'sqrtPriceLimitX64';
+          type: 'u128';
+        },
+        {
+          name: 'isBaseInput';
+          type: 'bool';
+        }
+      ];
+    },
+    {
+      name: 'transferRewardOwner';
+      docs: [
+        'Transfer reward owner',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `new_owner`- new owner pubkey',
+        ''
+      ];
+      discriminator: [7, 22, 12, 83, 242, 43, 48, 121];
+      accounts: [
+        {
+          name: 'authority';
+          docs: ['Address to be set as operation account owner.'];
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        }
+      ];
+      args: [
+        {
+          name: 'newOwner';
+          type: 'pubkey';
+        }
+      ];
+    },
+    {
+      name: 'updateAmmAdminGroup';
+      docs: ['Update the AMM admin group account, which is used to manage the AMM protocol.'];
+      discriminator: [61, 183, 185, 188, 82, 81, 141, 197];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['only super admin can create admin group'];
+          writable: true;
+          signer: true;
+          address: 'AY196f8U5EvM999PVnvLmyvaUnzL4GLiFaGKUgnJXN6o';
+        },
+        {
+          name: 'adminGroup';
+          docs: ['update amm admin group account to store admin permissions.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        }
+      ];
+      args: [
+        {
+          name: 'params';
+          type: {
+            defined: {
+              name: 'updateAdminGroupParams';
+            };
+          };
+        }
+      ];
+    },
+    {
+      name: 'updateAmmConfig';
+      docs: [
+        'Updates the owner of the amm config',
+        'Must be called by the current owner or admin',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `trade_fee_rate`- The new trade fee rate of amm config, be set when `param` is 0',
+        '* `protocol_fee_rate`- The new protocol fee rate of amm config, be set when `param` is 1',
+        '* `fund_fee_rate`- The new fund fee rate of amm config, be set when `param` is 2',
+        "* `new_owner`- The config's new owner, be set when `param` is 3",
+        "* `new_fund_owner`- The config's new fund owner, be set when `param` is 4",
+        '* `param`- The value can be 0 | 1 | 2 | 3 | 4, otherwise will report a error',
+        ''
+      ];
+      discriminator: [49, 60, 174, 136, 154, 28, 116, 200];
+      accounts: [
+        {
+          name: 'owner';
+          docs: ['The amm config owner or admin'];
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'ammConfig';
+          docs: ['Amm config account to be changed'];
+          writable: true;
+        }
+      ];
+      args: [
+        {
+          name: 'param';
+          type: 'u8';
+        },
+        {
+          name: 'value';
+          type: 'u32';
+        }
+      ];
+    },
+    {
+      name: 'updateOperationAccount';
+      docs: [
+        'Update the operation account',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `param`- The value can be 0 | 1 | 2 | 3, otherwise will report a error',
+        '* `keys`- update operation owner when the `param` is 0',
+        'remove operation owner when the `param` is 1',
+        'update whitelist mint when the `param` is 2',
+        'remove whitelist mint when the `param` is 3',
+        ''
+      ];
+      discriminator: [127, 70, 119, 40, 188, 227, 61, 7];
+      accounts: [
+        {
+          name: 'owner';
+          docs: ['Address to be set as operation account owner.'];
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'operationState';
+          docs: ['Initialize operation state account to store operation owner address and white list mint.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 112, 101, 114, 97, 116, 105, 111, 110];
+              }
+            ];
+          };
+        },
+        {
+          name: 'systemProgram';
+          address: '11111111111111111111111111111111';
+        }
+      ];
+      args: [
+        {
+          name: 'param';
+          type: 'u8';
+        },
+        {
+          name: 'keys';
+          type: {
+            vec: 'pubkey';
+          };
+        }
+      ];
+    },
+    {
+      name: 'updatePoolStatus';
+      docs: [
+        'Update pool status for given value',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `status` - The value of status',
+        ''
+      ];
+      discriminator: [130, 87, 108, 6, 46, 224, 117, 123];
+      accounts: [
+        {
+          name: 'authority';
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        }
+      ];
+      args: [
+        {
+          name: 'status';
+          type: 'u8';
+        }
+      ];
+    },
+    {
+      name: 'updateRewardInfos';
+      docs: [
+        'Update rewards info of the given pool, can be called for everyone',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        ''
+      ];
+      discriminator: [163, 172, 224, 52, 11, 154, 106, 223];
+      accounts: [
+        {
+          name: 'poolState';
+          docs: ['The liquidity pool for which reward info to update'];
+          writable: true;
+        }
+      ];
+      args: [];
+    },
+    {
+      name: 'withdrawOffchainReward';
+      docs: ['withdraw offchain reward from the pool'];
+      discriminator: [86, 53, 59, 76, 217, 38, 71, 213];
+      accounts: [
+        {
+          name: 'authority';
+          docs: ['The authority make decision that who can withdraw the offchain reward.'];
+          signer: true;
+        },
+        {
+          name: 'adminGroup';
+          docs: ['Initialize amm admin group account to store admin permissions.'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 100, 109, 105, 110, 95, 103, 114, 111, 117, 112];
+              }
+            ];
+          };
+        },
+        {
+          name: 'poolId';
+          docs: ['the pool id, which is the pool state account.'];
+          relations: ['rewardConfig'];
+        },
+        {
+          name: 'tokenMint';
+        },
+        {
+          name: 'receiverTokenAccount';
+          docs: ['the address who receive the withdrawn offchain reward.'];
+          writable: true;
+        },
+        {
+          name: 'rewardVaultTokenAccount';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'account';
+                path: 'rewardConfig';
+              },
+              {
+                kind: 'account';
+                path: 'tokenProgram';
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint';
+              }
+            ];
+            program: {
+              kind: 'const';
+              value: [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ];
+            };
+          };
+        },
+        {
+          name: 'rewardConfig';
+          docs: ['The offchain reward config account, it also is the reward vault account.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 102, 102, 99, 104, 97, 105, 110, 95, 114, 101, 119, 97, 114, 100];
+              },
+              {
+                kind: 'account';
+                path: 'poolId';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Spl token program or token program 2022'];
+        },
+        {
+          name: 'associatedTokenProgram';
+          address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
+        }
+      ];
+      args: [
+        {
+          name: 'amount';
+          type: 'u64';
+        }
+      ];
+    }
+  ];
+  accounts: [
+    {
+      name: 'ammAdminGroup';
+      discriminator: [128, 128, 234, 30, 61, 172, 188, 123];
+    },
+    {
+      name: 'ammConfig';
+      discriminator: [218, 244, 33, 104, 203, 203, 43, 111];
+    },
+    {
+      name: 'observationState';
+      discriminator: [122, 174, 197, 53, 129, 9, 165, 132];
+    },
+    {
+      name: 'offchainRewardConfig';
+      discriminator: [118, 52, 115, 150, 99, 69, 164, 76];
+    },
+    {
+      name: 'operationState';
+      discriminator: [19, 236, 58, 237, 81, 222, 183, 252];
+    },
+    {
+      name: 'personalPositionState';
+      discriminator: [70, 111, 150, 126, 230, 15, 25, 117];
+    },
+    {
+      name: 'poolState';
+      discriminator: [247, 237, 227, 245, 215, 195, 222, 70];
+    },
+    {
+      name: 'protocolPositionState';
+      discriminator: [100, 226, 145, 99, 146, 218, 160, 106];
+    },
+    {
+      name: 'supportMintAssociated';
+      discriminator: [134, 40, 183, 79, 12, 112, 162, 53];
+    },
+    {
+      name: 'tickArrayBitmapExtension';
+      discriminator: [60, 150, 36, 219, 97, 128, 139, 153];
+    },
+    {
+      name: 'tickArrayState';
+      discriminator: [192, 155, 85, 205, 49, 249, 129, 42];
+    }
+  ];
+  events: [
+    {
+      name: 'collectPersonalFeeEvent';
+      discriminator: [166, 174, 105, 192, 81, 161, 83, 105];
+    },
+    {
+      name: 'collectProtocolFeeEvent';
+      discriminator: [206, 87, 17, 79, 45, 41, 213, 61];
+    },
+    {
+      name: 'configChangeEvent';
+      discriminator: [247, 189, 7, 119, 106, 112, 95, 151];
+    },
+    {
+      name: 'createPersonalPositionEvent';
+      discriminator: [100, 30, 87, 249, 196, 223, 154, 206];
+    },
+    {
+      name: 'decreaseLiquidityEvent';
+      discriminator: [58, 222, 86, 58, 68, 50, 85, 56];
+    },
+    {
+      name: 'increaseLiquidityEvent';
+      discriminator: [49, 79, 105, 212, 32, 34, 30, 84];
+    },
+    {
+      name: 'liquidityCalculateEvent';
+      discriminator: [237, 112, 148, 230, 57, 84, 180, 162];
+    },
+    {
+      name: 'liquidityChangeEvent';
+      discriminator: [126, 240, 175, 206, 158, 88, 153, 107];
+    },
+    {
+      name: 'modifyAmmAdminGroupEvent';
+      discriminator: [218, 118, 87, 81, 53, 80, 18, 235];
+    },
+    {
+      name: 'poolCreatedEvent';
+      discriminator: [25, 94, 75, 47, 112, 99, 53, 63];
+    },
+    {
+      name: 'swapEvent';
+      discriminator: [64, 198, 205, 232, 38, 8, 113, 226];
+    },
+    {
+      name: 'updateRewardInfosEvent';
+      discriminator: [109, 127, 186, 78, 114, 65, 37, 236];
+    }
+  ];
+  errors: [
+    {
+      code: 6000;
+      name: 'lok';
+      msg: 'lok';
+    },
+    {
+      code: 6001;
+      name: 'notApproved';
+      msg: 'Not approved';
+    },
+    {
+      code: 6002;
+      name: 'invalidUpdateConfigFlag';
+      msg: 'invalid update amm config flag';
+    },
+    {
+      code: 6003;
+      name: 'accountLack';
+      msg: 'Account lack';
+    },
+    {
+      code: 6004;
+      name: 'closePositionErr';
+      msg: 'Remove liquitity, collect fees owed and reward then you can close position account';
+    },
+    {
+      code: 6005;
+      name: 'zeroMintAmount';
+      msg: 'Minting amount should be greater than 0';
+    },
+    {
+      code: 6006;
+      name: 'invalidTickIndex';
+      msg: 'Tick out of range';
+    },
+    {
+      code: 6007;
+      name: 'tickInvalidOrder';
+      msg: 'The lower tick must be below the upper tick';
+    },
+    {
+      code: 6008;
+      name: 'tickLowerOverflow';
+      msg: 'The tick must be greater, or equal to the minimum tick(-443636)';
+    },
+    {
+      code: 6009;
+      name: 'tickUpperOverflow';
+      msg: 'The tick must be lesser than, or equal to the maximum tick(443636)';
+    },
+    {
+      code: 6010;
+      name: 'tickAndSpacingNotMatch';
+      msg: 'tick % tick_spacing must be zero';
+    },
+    {
+      code: 6011;
+      name: 'invalidTickArray';
+      msg: 'Invalid tick array account';
+    },
+    {
+      code: 6012;
+      name: 'invalidTickArrayBoundary';
+      msg: 'Invalid tick array boundary';
+    },
+    {
+      code: 6013;
+      name: 'sqrtPriceLimitOverflow';
+      msg: 'Square root price limit overflow';
+    },
+    {
+      code: 6014;
+      name: 'sqrtPriceX64';
+      msg: 'sqrt_price_x64 out of range';
+    },
+    {
+      code: 6015;
+      name: 'liquiditySubValueErr';
+      msg: 'Liquidity sub delta L must be smaller than before';
+    },
+    {
+      code: 6016;
+      name: 'liquidityAddValueErr';
+      msg: 'Liquidity add delta L must be greater, or equal to before';
+    },
+    {
+      code: 6017;
+      name: 'invalidLiquidity';
+      msg: 'Invalid liquidity when update position';
+    },
+    {
+      code: 6018;
+      name: 'forbidBothZeroForSupplyLiquidity';
+      msg: 'Both token amount must not be zero while supply liquidity';
+    },
+    {
+      code: 6019;
+      name: 'liquidityInsufficient';
+      msg: 'Liquidity insufficient';
+    },
+    {
+      code: 6020;
+      name: 'transactionTooOld';
+      msg: 'Transaction too old';
+    },
+    {
+      code: 6021;
+      name: 'priceSlippageCheck';
+      msg: 'Price slippage check';
+    },
+    {
+      code: 6022;
+      name: 'tooLittleOutputReceived';
+      msg: 'Too little output received';
+    },
+    {
+      code: 6023;
+      name: 'tooMuchInputPaid';
+      msg: 'Too much input paid';
+    },
+    {
+      code: 6024;
+      name: 'zeroAmountSpecified';
+      msg: 'Swap special amount can not be zero';
+    },
+    {
+      code: 6025;
+      name: 'invalidInputPoolVault';
+      msg: 'Input pool vault is invalid';
+    },
+    {
+      code: 6026;
+      name: 'tooSmallInputOrOutputAmount';
+      msg: 'Swap input or output amount is too small';
+    },
+    {
+      code: 6027;
+      name: 'notEnoughTickArrayAccount';
+      msg: 'Not enought tick array account';
+    },
+    {
+      code: 6028;
+      name: 'invalidFirstTickArrayAccount';
+      msg: 'Invalid first tick array account';
+    },
+    {
+      code: 6029;
+      name: 'invalidRewardIndex';
+      msg: 'Invalid reward index';
+    },
+    {
+      code: 6030;
+      name: 'fullRewardInfo';
+      msg: 'The init reward token reach to the max';
+    },
+    {
+      code: 6031;
+      name: 'rewardTokenAlreadyInUse';
+      msg: 'The init reward token already in use';
+    },
+    {
+      code: 6032;
+      name: 'exceptRewardMint';
+      msg: 'The reward tokens must contain one of pool vault mint except the last reward';
+    },
+    {
+      code: 6033;
+      name: 'invalidRewardInitParam';
+      msg: 'Invalid reward init param';
+    },
+    {
+      code: 6034;
+      name: 'invalidRewardDesiredAmount';
+      msg: 'Invalid collect reward desired amount';
+    },
+    {
+      code: 6035;
+      name: 'invalidRewardInputAccountNumber';
+      msg: 'Invalid collect reward input account number';
+    },
+    {
+      code: 6036;
+      name: 'invalidRewardPeriod';
+      msg: 'Invalid reward period';
+    },
+    {
+      code: 6037;
+      name: 'notApproveUpdateRewardEmissiones';
+      msg: 'Modification of emissiones is allowed within 72 hours from the end of the previous cycle';
+    },
+    {
+      code: 6038;
+      name: 'unInitializedRewardInfo';
+      msg: 'uninitialized reward info';
+    },
+    {
+      code: 6039;
+      name: 'notSupportMint';
+      msg: 'Not support token_2022 mint extension';
+    },
+    {
+      code: 6040;
+      name: 'missingTickArrayBitmapExtensionAccount';
+      msg: 'Missing tickarray bitmap extension account';
+    },
+    {
+      code: 6041;
+      name: 'insufficientLiquidityForDirection';
+      msg: 'Insufficient liquidity for this direction';
+    },
+    {
+      code: 6042;
+      name: 'maxTokenOverflow';
+      msg: 'Max token overflow';
+    },
+    {
+      code: 6043;
+      name: 'calculateOverflow';
+      msg: 'Calculate overflow';
+    },
+    {
+      code: 6044;
+      name: 'transferFeeCalculateNotMatch';
+      msg: 'TransferFee calculate not match';
+    },
+    {
+      code: 6045;
+      name: 'illegalAccountOwner';
+      msg: 'invalid account owner';
+    },
+    {
+      code: 6046;
+      name: 'invalidAccount';
+      msg: 'Invalid account';
+    }
+  ];
+  types: [
+    {
+      name: 'ammAdminGroup';
+      docs: ['Holds the admin group information.'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'feeKeeper';
+            docs: ['the address who can hold the fee,', 'anyone can trigger the fee collection action,'];
+            type: 'pubkey';
+          },
+          {
+            name: 'rewardConfigManager';
+            docs: [
+              'the address who can config the reward(config, deposit, withdraw),',
+              'deposit reward, set the account who can deposit the reward, withdraw the remaining reward(withdraw)'
+            ];
+            type: 'pubkey';
+          },
+          {
+            name: 'rewardClaimManager';
+            docs: ['the address who can manage the offchain reward claim'];
+            type: 'pubkey';
+          },
+          {
+            name: 'poolManager';
+            docs: [
+              'the address who can manage the pool create action,',
+              "without this account's permission, no one can create a pool"
+            ];
+            type: 'pubkey';
+          },
+          {
+            name: 'emergencyManager';
+            docs: [
+              'the address who can manage the emergency action,',
+              'emergency action includes stop/resume the pool, stop/resume withdraw lp'
+            ];
+            type: 'pubkey';
+          },
+          {
+            name: 'normalManager';
+            docs: ['normal action manager,', 'such as create amm config, update amm config'];
+            type: 'pubkey';
+          },
+          {
+            name: 'pad';
+            docs: ['The space required for the account. may be used for future extensions.'];
+            type: {
+              array: ['pubkey', 6];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'ammConfig';
+      docs: ['Holds the current owner of the factory'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'bump';
+            docs: ['Bump to identify PDA'];
+            type: 'u8';
+          },
+          {
+            name: 'index';
+            type: 'u16';
+          },
+          {
+            name: 'owner';
+            docs: ['Address of the protocol owner'];
+            type: 'pubkey';
+          },
+          {
+            name: 'protocolFeeRate';
+            docs: ['The protocol fee'];
+            type: 'u32';
+          },
+          {
+            name: 'tradeFeeRate';
+            docs: ['The trade fee, denominated in hundredths of a bip (10^-6)'];
+            type: 'u32';
+          },
+          {
+            name: 'tickSpacing';
+            docs: ['The tick spacing'];
+            type: 'u16';
+          },
+          {
+            name: 'fundFeeRate';
+            docs: ['The fund fee, denominated in hundredths of a bip (10^-6)'];
+            type: 'u32';
+          },
+          {
+            name: 'paddingU32';
+            type: 'u32';
+          },
+          {
+            name: 'fundOwner';
+            type: 'pubkey';
+          },
+          {
+            name: 'padding';
+            type: {
+              array: ['u64', 3];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'collectPersonalFeeEvent';
+      docs: ['Emitted when tokens are collected for a position'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'positionNftMint';
+            docs: ['The ID of the token for which underlying tokens were collected'];
+            type: 'pubkey';
+          },
+          {
+            name: 'recipientTokenAccount0';
+            docs: ['The token account that received the collected token_0 tokens'];
+            type: 'pubkey';
+          },
+          {
+            name: 'recipientTokenAccount1';
+            docs: ['The token account that received the collected token_1 tokens'];
+            type: 'pubkey';
+          },
+          {
+            name: 'amount0';
+            docs: ['The amount of token_0 owed to the position that was collected'];
+            type: 'u64';
+          },
+          {
+            name: 'amount1';
+            docs: ['The amount of token_1 owed to the position that was collected'];
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'collectProtocolFeeEvent';
+      docs: ['Emitted when the collected protocol fees are withdrawn by the factory owner'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolState';
+            docs: ['The pool whose protocol fee is collected'];
+            type: 'pubkey';
+          },
+          {
+            name: 'recipientTokenAccount0';
+            docs: ['The address that receives the collected token_0 protocol fees'];
+            type: 'pubkey';
+          },
+          {
+            name: 'recipientTokenAccount1';
+            docs: ['The address that receives the collected token_1 protocol fees'];
+            type: 'pubkey';
+          },
+          {
+            name: 'amount0';
+            docs: ['The amount of token_0 protocol fees that is withdrawn'];
+            type: 'u64';
+          },
+          {
+            name: 'amount1';
+            docs: ['The amount of token_0 protocol fees that is withdrawn'];
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'configChangeEvent';
+      docs: ['Emitted when create or update a config'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'index';
+            type: 'u16';
+          },
+          {
+            name: 'owner';
+            type: 'pubkey';
+          },
+          {
+            name: 'protocolFeeRate';
+            type: 'u32';
+          },
+          {
+            name: 'tradeFeeRate';
+            type: 'u32';
+          },
+          {
+            name: 'tickSpacing';
+            type: 'u16';
+          },
+          {
+            name: 'fundFeeRate';
+            type: 'u32';
+          },
+          {
+            name: 'fundOwner';
+            type: 'pubkey';
+          }
+        ];
+      };
+    },
+    {
+      name: 'createPersonalPositionEvent';
+      docs: ['Emitted when create a new position'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolState';
+            docs: ['The pool for which liquidity was added'];
+            type: 'pubkey';
+          },
+          {
+            name: 'minter';
+            docs: ['The address that create the position'];
+            type: 'pubkey';
+          },
+          {
+            name: 'nftOwner';
+            docs: ['The owner of the position and recipient of any minted liquidity'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tickLowerIndex';
+            docs: ['The lower tick of the position'];
+            type: 'i32';
+          },
+          {
+            name: 'tickUpperIndex';
+            docs: ['The upper tick of the position'];
+            type: 'i32';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The amount of liquidity minted to the position range'];
+            type: 'u128';
+          },
+          {
+            name: 'depositAmount0';
+            docs: ['The amount of token_0 was deposit for the liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'depositAmount1';
+            docs: ['The amount of token_1 was deposit for the liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'depositAmount0TransferFee';
+            docs: ['The token transfer fee for deposit_amount_0'];
+            type: 'u64';
+          },
+          {
+            name: 'depositAmount1TransferFee';
+            docs: ['The token transfer fee for deposit_amount_1'];
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'decreaseLiquidityEvent';
+      docs: ['Emitted when liquidity is decreased.'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'positionNftMint';
+            docs: ['The ID of the token for which liquidity was decreased'];
+            type: 'pubkey';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The amount by which liquidity for the position was decreased'];
+            type: 'u128';
+          },
+          {
+            name: 'decreaseAmount0';
+            docs: ['The amount of token_0 that was paid for the decrease in liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'decreaseAmount1';
+            docs: ['The amount of token_1 that was paid for the decrease in liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'feeAmount0';
+            type: 'u64';
+          },
+          {
+            name: 'feeAmount1';
+            docs: ['The amount of token_1 fee'];
+            type: 'u64';
+          },
+          {
+            name: 'rewardAmounts';
+            docs: ['The amount of rewards'];
+            type: {
+              array: ['u64', 3];
+            };
+          },
+          {
+            name: 'transferFee0';
+            docs: ['The amount of token_0 transfer fee'];
+            type: 'u64';
+          },
+          {
+            name: 'transferFee1';
+            docs: ['The amount of token_1 transfer fee'];
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'increaseLiquidityEvent';
+      docs: ['Emitted when liquidity is increased.'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'positionNftMint';
+            docs: ['The ID of the token for which liquidity was increased'];
+            type: 'pubkey';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The amount by which liquidity for the NFT position was increased'];
+            type: 'u128';
+          },
+          {
+            name: 'amount0';
+            docs: ['The amount of token_0 that was paid for the increase in liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'amount1';
+            docs: ['The amount of token_1 that was paid for the increase in liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'amount0TransferFee';
+            docs: ['The token transfer fee for amount_0'];
+            type: 'u64';
+          },
+          {
+            name: 'amount1TransferFee';
+            docs: ['The token transfer fee for amount_1'];
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'initAdminGroupParams';
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'feeKeeper';
+            docs: ['the address who can hold the fee,', 'anyone can trigger the fee collection action,'];
+            type: 'pubkey';
+          },
+          {
+            name: 'rewardConfigManager';
+            docs: [
+              'the address who can config the reward(config, deposit, withdraw),',
+              'deposit reward, set the account who can deposit the reward, withdraw the remaining reward(withdraw)'
+            ];
+            type: 'pubkey';
+          },
+          {
+            name: 'rewardClaimManager';
+            docs: ['the address who can manage the offchain reward claim'];
+            type: 'pubkey';
+          },
+          {
+            name: 'poolManager';
+            docs: [
+              'the address who can manage the pool create action,',
+              "without this account's permission, no one can create a pool"
+            ];
+            type: 'pubkey';
+          },
+          {
+            name: 'emergencyManager';
+            docs: [
+              'the address who can manage the emergency action,',
+              'emergency action includes stop/resume the pool, stop/resume withdraw lp'
+            ];
+            type: 'pubkey';
+          },
+          {
+            name: 'normalManager';
+            docs: ['normal action manager,', 'such as create amm config, update amm config'];
+            type: 'pubkey';
+          }
+        ];
+      };
+    },
+    {
+      name: 'initializeRewardParam';
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'openTime';
+            docs: ['Reward open time'];
+            type: 'u64';
+          },
+          {
+            name: 'endTime';
+            docs: ['Reward end time'];
+            type: 'u64';
+          },
+          {
+            name: 'emissionsPerSecondX64';
+            docs: ['Token reward per second are earned per unit of liquidity'];
+            type: 'u128';
+          }
+        ];
+      };
+    },
+    {
+      name: 'liquidityCalculateEvent';
+      docs: ['Emitted when liquidity decreased or increase.'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolLiquidity';
+            docs: ['The pool liquidity before decrease or increase'];
+            type: 'u128';
+          },
+          {
+            name: 'poolSqrtPriceX64';
+            docs: ['The pool price when decrease or increase in liquidity'];
+            type: 'u128';
+          },
+          {
+            name: 'poolTick';
+            docs: ['The pool tick when decrease or increase in liquidity'];
+            type: 'i32';
+          },
+          {
+            name: 'calcAmount0';
+            docs: ['The amount of token_0 that was calculated for the decrease or increase in liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'calcAmount1';
+            docs: ['The amount of token_1 that was calculated for the decrease or increase in liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'tradeFeeOwed0';
+            type: 'u64';
+          },
+          {
+            name: 'tradeFeeOwed1';
+            docs: ['The amount of token_1 fee'];
+            type: 'u64';
+          },
+          {
+            name: 'transferFee0';
+            docs: ['The amount of token_0 transfer fee without trade_fee_amount_0'];
+            type: 'u64';
+          },
+          {
+            name: 'transferFee1';
+            docs: ['The amount of token_1 transfer fee without trade_fee_amount_0'];
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'liquidityChangeEvent';
+      docs: ['Emitted pool liquidity change when increase and decrease liquidity'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolState';
+            docs: ['The pool for swap'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tick';
+            docs: ['The tick of the pool'];
+            type: 'i32';
+          },
+          {
+            name: 'tickLower';
+            docs: ['The tick lower of position'];
+            type: 'i32';
+          },
+          {
+            name: 'tickUpper';
+            docs: ['The tick lower of position'];
+            type: 'i32';
+          },
+          {
+            name: 'liquidityBefore';
+            docs: ['The liquidity of the pool before liquidity change'];
+            type: 'u128';
+          },
+          {
+            name: 'liquidityAfter';
+            docs: ['The liquidity of the pool after liquidity change'];
+            type: 'u128';
+          }
+        ];
+      };
+    },
+    {
+      name: 'modifyAmmAdminGroupEvent';
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'feeKeeper';
+            type: 'pubkey';
+          },
+          {
+            name: 'rewardConfigManager';
+            type: 'pubkey';
+          },
+          {
+            name: 'rewardClaimManager';
+            type: 'pubkey';
+          },
+          {
+            name: 'poolManager';
+            type: 'pubkey';
+          },
+          {
+            name: 'emergencyManager';
+            type: 'pubkey';
+          },
+          {
+            name: 'normalManager';
+            type: 'pubkey';
+          }
+        ];
+      };
+    },
+    {
+      name: 'observation';
+      docs: ['The element of observations in ObservationState'];
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'blockTimestamp';
+            docs: ['The block timestamp of the observation'];
+            type: 'u32';
+          },
+          {
+            name: 'tickCumulative';
+            docs: ['the cumulative of tick during the duration time'];
+            type: 'i64';
+          },
+          {
+            name: 'padding';
+            docs: ['padding for feature update'];
+            type: {
+              array: ['u64', 4];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'observationState';
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'initialized';
+            docs: ['Whether the ObservationState is initialized'];
+            type: 'bool';
+          },
+          {
+            name: 'recentEpoch';
+            docs: ['recent update epoch'];
+            type: 'u64';
+          },
+          {
+            name: 'observationIndex';
+            docs: ['the most-recently updated index of the observations array'];
+            type: 'u16';
+          },
+          {
+            name: 'poolId';
+            docs: ['belongs to which pool'];
+            type: 'pubkey';
+          },
+          {
+            name: 'observations';
+            docs: ['observation array'];
+            type: {
+              array: [
+                {
+                  defined: {
+                    name: 'observation';
+                  };
+                },
+                100
+              ];
+            };
+          },
+          {
+            name: 'padding';
+            docs: ['padding for feature update'];
+            type: {
+              array: ['u64', 4];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'offchainRewardConfig';
+      docs: ['Holds the current owner of the factory'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolId';
+            docs: ['the pool state address'];
+            type: 'pubkey';
+          },
+          {
+            name: 'rewardVault';
+            docs: ['the vault to hold the reward', 'vault address is this config address'];
+            type: 'pubkey';
+          },
+          {
+            name: 'vaultBump';
+            docs: ['Bump to identify vault PDA'];
+            type: {
+              array: ['u8', 1];
+            };
+          },
+          {
+            name: 'rewardMintVec';
+            docs: ['reward token mint address list', 'reward token account is ATA(reward_vault, reward_mint)'];
+            type: {
+              vec: 'pubkey';
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'operationState';
+      docs: ['Holds the current owner of the factory'];
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'bump';
+            docs: ['Bump to identify PDA'];
+            type: 'u8';
+          },
+          {
+            name: 'operationOwners';
+            docs: ['Address of the operation owner'];
+            type: {
+              array: ['pubkey', 10];
+            };
+          },
+          {
+            name: 'whitelistMints';
+            docs: ['The mint address of whitelist to emit reward'];
+            type: {
+              array: ['pubkey', 100];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'personalPositionState';
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'bump';
+            docs: ['Bump to identify PDA'];
+            type: {
+              array: ['u8', 1];
+            };
+          },
+          {
+            name: 'nftMint';
+            docs: ['Mint address of the tokenized position'];
+            type: 'pubkey';
+          },
+          {
+            name: 'poolId';
+            docs: ['The ID of the pool with which this token is connected'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tickLowerIndex';
+            docs: ['The lower bound tick of the position'];
+            type: 'i32';
+          },
+          {
+            name: 'tickUpperIndex';
+            docs: ['The upper bound tick of the position'];
+            type: 'i32';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The amount of liquidity owned by this position'];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthInside0LastX64';
+            docs: ['The token_0 fee growth of the aggregate position as of the last action on the individual position'];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthInside1LastX64';
+            docs: ['The token_1 fee growth of the aggregate position as of the last action on the individual position'];
+            type: 'u128';
+          },
+          {
+            name: 'tokenFeesOwed0';
+            docs: ['The fees owed to the position owner in token_0, as of the last computation'];
+            type: 'u64';
+          },
+          {
+            name: 'tokenFeesOwed1';
+            docs: ['The fees owed to the position owner in token_1, as of the last computation'];
+            type: 'u64';
+          },
+          {
+            name: 'rewardInfos';
+            type: {
+              array: [
+                {
+                  defined: {
+                    name: 'positionRewardInfo';
+                  };
+                },
+                3
+              ];
+            };
+          },
+          {
+            name: 'recentEpoch';
+            type: 'u64';
+          },
+          {
+            name: 'padding';
+            type: {
+              array: ['u64', 7];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'poolCreatedEvent';
+      docs: ['Emitted when a pool is created and initialized with a starting price', ''];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'tokenMint0';
+            docs: ['The first token of the pool by address sort order'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenMint1';
+            docs: ['The second token of the pool by address sort order'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tickSpacing';
+            docs: ['The minimum number of ticks between initialized ticks'];
+            type: 'u16';
+          },
+          {
+            name: 'poolState';
+            docs: ['The address of the created pool'];
+            type: 'pubkey';
+          },
+          {
+            name: 'sqrtPriceX64';
+            docs: ['The initial sqrt price of the pool, as a Q64.64'];
+            type: 'u128';
+          },
+          {
+            name: 'tick';
+            docs: ['The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool'];
+            type: 'i32';
+          },
+          {
+            name: 'tokenVault0';
+            docs: ['Vault of token_0'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenVault1';
+            docs: ['Vault of token_1'];
+            type: 'pubkey';
+          }
+        ];
+      };
+    },
+    {
+      name: 'poolState';
+      docs: ['The pool state', '', 'PDA of `[POOL_SEED, config, token_mint_0, token_mint_1]`', ''];
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'bump';
+            docs: ['Bump to identify PDA'];
+            type: {
+              array: ['u8', 1];
+            };
+          },
+          {
+            name: 'ammConfig';
+            type: 'pubkey';
+          },
+          {
+            name: 'owner';
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenMint0';
+            docs: ['Token pair of the pool, where token_mint_0 address < token_mint_1 address'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenMint1';
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenVault0';
+            docs: ['Token pair vault'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenVault1';
+            type: 'pubkey';
+          },
+          {
+            name: 'observationKey';
+            docs: ['observation account key'];
+            type: 'pubkey';
+          },
+          {
+            name: 'mintDecimals0';
+            docs: ['mint0 and mint1 decimals'];
+            type: 'u8';
+          },
+          {
+            name: 'mintDecimals1';
+            type: 'u8';
+          },
+          {
+            name: 'tickSpacing';
+            docs: ['The minimum number of ticks between initialized ticks'];
+            type: 'u16';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The currently in range liquidity available to the pool.'];
+            type: 'u128';
+          },
+          {
+            name: 'sqrtPriceX64';
+            docs: ['The current price of the pool as a sqrt(token_1/token_0) Q64.64 value'];
+            type: 'u128';
+          },
+          {
+            name: 'tickCurrent';
+            docs: ['The current tick of the pool, i.e. according to the last tick transition that was run.'];
+            type: 'i32';
+          },
+          {
+            name: 'padding3';
+            type: 'u16';
+          },
+          {
+            name: 'padding4';
+            type: 'u16';
+          },
+          {
+            name: 'feeGrowthGlobal0X64';
+            docs: [
+              'The fee growth as a Q64.64 number, i.e. fees of token_0 and token_1 collected per',
+              'unit of liquidity for the entire life of the pool.'
+            ];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthGlobal1X64';
+            type: 'u128';
+          },
+          {
+            name: 'protocolFeesToken0';
+            docs: ['The amounts of token_0 and token_1 that are owed to the protocol.'];
+            type: 'u64';
+          },
+          {
+            name: 'protocolFeesToken1';
+            type: 'u64';
+          },
+          {
+            name: 'swapInAmountToken0';
+            docs: ['The amounts in and out of swap token_0 and token_1'];
+            type: 'u128';
+          },
+          {
+            name: 'swapOutAmountToken1';
+            type: 'u128';
+          },
+          {
+            name: 'swapInAmountToken1';
+            type: 'u128';
+          },
+          {
+            name: 'swapOutAmountToken0';
+            type: 'u128';
+          },
+          {
+            name: 'status';
+            docs: [
+              'Bitwise representation of the state of the pool',
+              'bit0, 1: disable open position and increase liquidity, 0: normal',
+              'bit1, 1: disable decrease liquidity, 0: normal',
+              'bit2, 1: disable collect fee, 0: normal',
+              'bit3, 1: disable collect reward, 0: normal',
+              'bit4, 1: disable swap, 0: normal'
+            ];
+            type: 'u8';
+          },
+          {
+            name: 'padding';
+            docs: ['Leave blank for future use'];
+            type: {
+              array: ['u8', 7];
+            };
+          },
+          {
+            name: 'rewardInfos';
+            type: {
+              array: [
+                {
+                  defined: {
+                    name: 'rewardInfo';
+                  };
+                },
+                3
+              ];
+            };
+          },
+          {
+            name: 'tickArrayBitmap';
+            docs: ['Packed initialized tick array state'];
+            type: {
+              array: ['u64', 16];
+            };
+          },
+          {
+            name: 'totalFeesToken0';
+            docs: ['except protocol_fee and fund_fee'];
+            type: 'u64';
+          },
+          {
+            name: 'totalFeesClaimedToken0';
+            docs: ['except protocol_fee and fund_fee'];
+            type: 'u64';
+          },
+          {
+            name: 'totalFeesToken1';
+            type: 'u64';
+          },
+          {
+            name: 'totalFeesClaimedToken1';
+            type: 'u64';
+          },
+          {
+            name: 'fundFeesToken0';
+            type: 'u64';
+          },
+          {
+            name: 'fundFeesToken1';
+            type: 'u64';
+          },
+          {
+            name: 'openTime';
+            type: 'u64';
+          },
+          {
+            name: 'recentEpoch';
+            type: 'u64';
+          },
+          {
+            name: 'padding1';
+            type: {
+              array: ['u64', 24];
+            };
+          },
+          {
+            name: 'padding2';
+            type: {
+              array: ['u64', 32];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'positionRewardInfo';
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'growthInsideLastX64';
+            type: 'u128';
+          },
+          {
+            name: 'rewardAmountOwed';
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'protocolPositionState';
+      docs: ["Info stored for each user's position"];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'bump';
+            docs: ['Bump to identify PDA'];
+            type: 'u8';
+          },
+          {
+            name: 'poolId';
+            docs: ['The ID of the pool with which this token is connected'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tickLowerIndex';
+            docs: ['The lower bound tick of the position'];
+            type: 'i32';
+          },
+          {
+            name: 'tickUpperIndex';
+            docs: ['The upper bound tick of the position'];
+            type: 'i32';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The amount of liquidity owned by this position'];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthInside0LastX64';
+            docs: ['The token_0 fee growth per unit of liquidity as of the last update to liquidity or fees owed'];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthInside1LastX64';
+            docs: ['The token_1 fee growth per unit of liquidity as of the last update to liquidity or fees owed'];
+            type: 'u128';
+          },
+          {
+            name: 'tokenFeesOwed0';
+            docs: ['The fees owed to the position owner in token_0'];
+            type: 'u64';
+          },
+          {
+            name: 'tokenFeesOwed1';
+            docs: ['The fees owed to the position owner in token_1'];
+            type: 'u64';
+          },
+          {
+            name: 'rewardGrowthInside';
+            docs: ['The reward growth per unit of liquidity as of the last update to liquidity'];
+            type: {
+              array: ['u128', 3];
+            };
+          },
+          {
+            name: 'recentEpoch';
+            type: 'u64';
+          },
+          {
+            name: 'padding';
+            type: {
+              array: ['u64', 7];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'rewardInfo';
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'rewardState';
+            docs: ['Reward state'];
+            type: 'u8';
+          },
+          {
+            name: 'openTime';
+            docs: ['Reward open time'];
+            type: 'u64';
+          },
+          {
+            name: 'endTime';
+            docs: ['Reward end time'];
+            type: 'u64';
+          },
+          {
+            name: 'lastUpdateTime';
+            docs: ['Reward last update time'];
+            type: 'u64';
+          },
+          {
+            name: 'emissionsPerSecondX64';
+            docs: ['Q64.64 number indicates how many tokens per second are earned per unit of liquidity.'];
+            type: 'u128';
+          },
+          {
+            name: 'rewardTotalEmissioned';
+            docs: ['The total amount of reward emissioned'];
+            type: 'u64';
+          },
+          {
+            name: 'rewardClaimed';
+            docs: ['The total amount of claimed reward'];
+            type: 'u64';
+          },
+          {
+            name: 'tokenMint';
+            docs: ['Reward token mint.'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenVault';
+            docs: ['Reward vault token account.'];
+            type: 'pubkey';
+          },
+          {
+            name: 'authority';
+            docs: ['The owner that has permission to set reward param'];
+            type: 'pubkey';
+          },
+          {
+            name: 'rewardGrowthGlobalX64';
+            docs: [
+              'Q64.64 number that tracks the total tokens earned per unit of liquidity since the reward',
+              'emissions were turned on.'
+            ];
+            type: 'u128';
+          }
+        ];
+      };
+    },
+    {
+      name: 'supportMintAssociated';
+      docs: ['Holds the current owner of the factory'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'bump';
+            docs: ['Bump to identify PDA'];
+            type: 'u8';
+          },
+          {
+            name: 'mint';
+            docs: ['Address of the supported token22 mint'];
+            type: 'pubkey';
+          },
+          {
+            name: 'padding';
+            type: {
+              array: ['u64', 8];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'swapEvent';
+      docs: ['Emitted by when a swap is performed for a pool'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolState';
+            docs: ['The pool for which token_0 and token_1 were swapped'];
+            type: 'pubkey';
+          },
+          {
+            name: 'sender';
+            docs: ['The address that initiated the swap call, and that received the callback'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenAccount0';
+            docs: [
+              'The payer token account in zero for one swaps, or the recipient token account',
+              'in one for zero swaps'
+            ];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenAccount1';
+            docs: [
+              'The payer token account in one for zero swaps, or the recipient token account',
+              'in zero for one swaps'
+            ];
+            type: 'pubkey';
+          },
+          {
+            name: 'amount0';
+            docs: ['The real delta amount of the token_0 of the pool or user'];
+            type: 'u64';
+          },
+          {
+            name: 'transferFee0';
+            docs: ['The transfer fee charged by the withheld_amount of the token_0'];
+            type: 'u64';
+          },
+          {
+            name: 'amount1';
+            docs: ['The real delta of the token_1 of the pool or user'];
+            type: 'u64';
+          },
+          {
+            name: 'transferFee1';
+            docs: ['The transfer fee charged by the withheld_amount of the token_1'];
+            type: 'u64';
+          },
+          {
+            name: 'zeroForOne';
+            docs: ['if true, amount_0 is negtive and amount_1 is positive'];
+            type: 'bool';
+          },
+          {
+            name: 'sqrtPriceX64';
+            docs: ['The sqrt(price) of the pool after the swap, as a Q64.64'];
+            type: 'u128';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The liquidity of the pool after the swap'];
+            type: 'u128';
+          },
+          {
+            name: 'tick';
+            docs: ['The log base 1.0001 of price of the pool after the swap'];
+            type: 'i32';
+          }
+        ];
+      };
+    },
+    {
+      name: 'tickArrayBitmapExtension';
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolId';
+            type: 'pubkey';
+          },
+          {
+            name: 'positiveTickArrayBitmap';
+            docs: ['Packed initialized tick array state for start_tick_index is positive'];
+            type: {
+              array: [
+                {
+                  array: ['u64', 8];
+                },
+                14
+              ];
+            };
+          },
+          {
+            name: 'negativeTickArrayBitmap';
+            docs: ['Packed initialized tick array state for start_tick_index is negitive'];
+            type: {
+              array: [
+                {
+                  array: ['u64', 8];
+                },
+                14
+              ];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'tickArrayState';
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolId';
+            type: 'pubkey';
+          },
+          {
+            name: 'startTickIndex';
+            type: 'i32';
+          },
+          {
+            name: 'ticks';
+            type: {
+              array: [
+                {
+                  defined: {
+                    name: 'tickState';
+                  };
+                },
+                60
+              ];
+            };
+          },
+          {
+            name: 'initializedTickCount';
+            type: 'u8';
+          },
+          {
+            name: 'recentEpoch';
+            type: 'u64';
+          },
+          {
+            name: 'padding';
+            type: {
+              array: ['u8', 107];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'tickState';
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'tick';
+            type: 'i32';
+          },
+          {
+            name: 'liquidityNet';
+            docs: [
+              'Amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)'
+            ];
+            type: 'i128';
+          },
+          {
+            name: 'liquidityGross';
+            docs: ['The total position liquidity that references this tick'];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthOutside0X64';
+            docs: [
+              'Fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)',
+              'only has relative meaning, not absolute — the value depends on when the tick is initialized'
+            ];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthOutside1X64';
+            type: 'u128';
+          },
+          {
+            name: 'rewardGrowthsOutsideX64';
+            type: {
+              array: ['u128', 3];
+            };
+          },
+          {
+            name: 'padding';
+            type: {
+              array: ['u32', 13];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'updateAdminGroupParams';
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'feeKeeper';
+            docs: ['the address who can hold the fee,', 'anyone can trigger the fee collection action,'];
+            type: {
+              option: 'pubkey';
+            };
+          },
+          {
+            name: 'rewardConfigManager';
+            docs: [
+              'the address who can config the reward(config, deposit, withdraw),',
+              'deposit reward, set the account who can deposit the reward, withdraw the remaining reward(withdraw)'
+            ];
+            type: {
+              option: 'pubkey';
+            };
+          },
+          {
+            name: 'rewardClaimManager';
+            docs: ['the address who can manage the offchain reward claim'];
+            type: {
+              option: 'pubkey';
+            };
+          },
+          {
+            name: 'poolManager';
+            docs: [
+              'the address who can manage the pool create action,',
+              "without this account's permission, no one can create a pool"
+            ];
+            type: {
+              option: 'pubkey';
+            };
+          },
+          {
+            name: 'emergencyManager';
+            docs: [
+              'the address who can manage the emergency action,',
+              'emergency action includes stop/resume the pool, stop/resume withdraw lp'
+            ];
+            type: {
+              option: 'pubkey';
+            };
+          },
+          {
+            name: 'normalManager';
+            docs: ['normal action manager,', 'such as create amm config, update amm config'];
+            type: {
+              option: 'pubkey';
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'updateRewardInfosEvent';
+      docs: ['Emitted when Reward are updated for a pool'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'rewardGrowthGlobalX64';
+            docs: ['Reward info'];
+            type: {
+              array: ['u128', 3];
+            };
+          }
+        ];
+      };
+    }
+  ];
+};

+ 4148 - 0
src/lib/clmm-sdk/src/instructions/target/types/raydium_amm_v3.ts

@@ -0,0 +1,4148 @@
+/**
+ * Program IDL in camelCase format in order to be used in JS/TS.
+ *
+ * Note that this is only a type helper and is not the actual IDL. The original
+ * IDL can be found at `target/idl/amm_v3.json`.
+ */
+export type AmmV3 = {
+  address: 'CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK';
+  metadata: {
+    name: 'ammV3';
+    version: '0.1.0';
+    spec: '0.1.0';
+    description: 'Anchor client and source for Raydium concentrated liquidity AMM';
+  };
+  instructions: [
+    {
+      name: 'closePosition';
+      docs: [
+        "Close the user's position and NFT account. If the NFT mint belongs to token2022, it will also be closed and the funds returned to the NFT owner.",
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        ''
+      ];
+      discriminator: [123, 134, 81, 0, 49, 68, 98, 98];
+      accounts: [
+        {
+          name: 'nftOwner';
+          docs: ['The position nft owner'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftMint';
+          docs: ['Mint address bound to the personal position.'];
+          writable: true;
+        },
+        {
+          name: 'positionNftAccount';
+          docs: ['User token account where position NFT be minted to'];
+          writable: true;
+        },
+        {
+          name: 'personalPosition';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'positionNftMint';
+              }
+            ];
+          };
+        },
+        {
+          name: 'systemProgram';
+          docs: ['System program to close the position state account'];
+          address: '11111111111111111111111111111111';
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Token/Token2022 program to close token/mint account'];
+        }
+      ];
+      args: [];
+    },
+    {
+      name: 'collectFundFee';
+      docs: [
+        'Collect the fund fee accrued to the pool',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `amount_0_requested` - The maximum amount of token_0 to send, can be 0 to collect fees in only token_1',
+        '* `amount_1_requested` - The maximum amount of token_1 to send, can be 0 to collect fees in only token_0',
+        ''
+      ];
+      discriminator: [167, 138, 78, 149, 223, 194, 6, 126];
+      accounts: [
+        {
+          name: 'owner';
+          docs: ['Only admin or fund_owner can collect fee now'];
+          signer: true;
+        },
+        {
+          name: 'poolState';
+          docs: ['Pool state stores accumulated protocol fee amount'];
+          writable: true;
+        },
+        {
+          name: 'ammConfig';
+          docs: ['Amm config account stores fund_owner'];
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'vault0Mint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'vault1Mint';
+          docs: ['The mint of token vault 1'];
+        },
+        {
+          name: 'recipientTokenAccount0';
+          docs: ['The address that receives the collected token_0 protocol fees'];
+          writable: true;
+        },
+        {
+          name: 'recipientTokenAccount1';
+          docs: ['The address that receives the collected token_1 protocol fees'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['The SPL program to perform token transfers'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['The SPL program 2022 to perform token transfers'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        }
+      ];
+      args: [
+        {
+          name: 'amount0Requested';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Requested';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'collectProtocolFee';
+      docs: [
+        'Collect the protocol fee accrued to the pool',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `amount_0_requested` - The maximum amount of token_0 to send, can be 0 to collect fees in only token_1',
+        '* `amount_1_requested` - The maximum amount of token_1 to send, can be 0 to collect fees in only token_0',
+        ''
+      ];
+      discriminator: [136, 136, 252, 221, 194, 66, 126, 89];
+      accounts: [
+        {
+          name: 'owner';
+          docs: ['Only admin or config owner can collect fee now'];
+          signer: true;
+        },
+        {
+          name: 'poolState';
+          docs: ['Pool state stores accumulated protocol fee amount'];
+          writable: true;
+        },
+        {
+          name: 'ammConfig';
+          docs: ['Amm config account stores owner'];
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'vault0Mint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'vault1Mint';
+          docs: ['The mint of token vault 1'];
+        },
+        {
+          name: 'recipientTokenAccount0';
+          docs: ['The address that receives the collected token_0 protocol fees'];
+          writable: true;
+        },
+        {
+          name: 'recipientTokenAccount1';
+          docs: ['The address that receives the collected token_1 protocol fees'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['The SPL program to perform token transfers'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['The SPL program 2022 to perform token transfers'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        }
+      ];
+      args: [
+        {
+          name: 'amount0Requested';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Requested';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'collectRemainingRewards';
+      docs: [
+        'Collect remaining reward token for reward founder',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `reward_index` - the index to reward info',
+        ''
+      ];
+      discriminator: [18, 237, 166, 197, 34, 16, 213, 144];
+      accounts: [
+        {
+          name: 'rewardFunder';
+          docs: ['The founder who init reward info previously'];
+          signer: true;
+        },
+        {
+          name: 'funderTokenAccount';
+          docs: ["The funder's reward token account"];
+          writable: true;
+        },
+        {
+          name: 'poolState';
+          docs: ['Set reward for this pool'];
+          writable: true;
+        },
+        {
+          name: 'rewardTokenVault';
+          docs: ['Reward vault transfer remaining token to founder token account'];
+        },
+        {
+          name: 'rewardVaultMint';
+          docs: ['The mint of reward token vault'];
+        },
+        {
+          name: 'tokenProgram';
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['Token program 2022'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'memoProgram';
+          docs: ['memo program'];
+          address: 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
+        }
+      ];
+      args: [
+        {
+          name: 'rewardIndex';
+          type: 'u8';
+        }
+      ];
+    },
+    {
+      name: 'createAmmConfig';
+      docs: [
+        '# Arguments',
+        '',
+        '* `ctx`- The accounts needed by instruction.',
+        '* `index` - The index of amm config, there may be multiple config.',
+        '* `tick_spacing` - The tickspacing binding with config, cannot be changed.',
+        '* `trade_fee_rate` - Trade fee rate, can be changed.',
+        '* `protocol_fee_rate` - The rate of protocol fee within trade fee.',
+        '* `fund_fee_rate` - The rate of fund fee within trade fee.',
+        ''
+      ];
+      discriminator: [137, 52, 237, 212, 215, 117, 108, 104];
+      accounts: [
+        {
+          name: 'owner';
+          docs: ['Address to be set as protocol owner.'];
+          writable: true;
+          signer: true;
+          address: 'GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ';
+        },
+        {
+          name: 'ammConfig';
+          docs: ['Initialize config state account to store protocol owner address and fee rates.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [97, 109, 109, 95, 99, 111, 110, 102, 105, 103];
+              },
+              {
+                kind: 'arg';
+                path: 'index';
+              }
+            ];
+          };
+        },
+        {
+          name: 'systemProgram';
+          address: '11111111111111111111111111111111';
+        }
+      ];
+      args: [
+        {
+          name: 'index';
+          type: 'u16';
+        },
+        {
+          name: 'tickSpacing';
+          type: 'u16';
+        },
+        {
+          name: 'tradeFeeRate';
+          type: 'u32';
+        },
+        {
+          name: 'protocolFeeRate';
+          type: 'u32';
+        },
+        {
+          name: 'fundFeeRate';
+          type: 'u32';
+        }
+      ];
+    },
+    {
+      name: 'createOperationAccount';
+      docs: [
+        'Creates an operation account for the program',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        ''
+      ];
+      discriminator: [63, 87, 148, 33, 109, 35, 8, 104];
+      accounts: [
+        {
+          name: 'owner';
+          docs: ['Address to be set as operation account owner.'];
+          writable: true;
+          signer: true;
+          address: 'GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ';
+        },
+        {
+          name: 'operationState';
+          docs: ['Initialize operation state account to store operation owner address and white list mint.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 112, 101, 114, 97, 116, 105, 111, 110];
+              }
+            ];
+          };
+        },
+        {
+          name: 'systemProgram';
+          address: '11111111111111111111111111111111';
+        }
+      ];
+      args: [];
+    },
+    {
+      name: 'createPool';
+      docs: [
+        'Creates a pool for the given token pair and the initial price',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `sqrt_price_x64` - the initial sqrt price (amount_token_1 / amount_token_0) of the pool as a Q64.64',
+        'Note: The open_time must be smaller than the current block_timestamp on chain.'
+      ];
+      discriminator: [233, 146, 209, 142, 207, 104, 64, 188];
+      accounts: [
+        {
+          name: 'poolCreator';
+          docs: ['Address paying to create the pool. Can be anyone'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'ammConfig';
+          docs: ['Which config the pool belongs to.'];
+        },
+        {
+          name: 'poolState';
+          docs: ['Initialize an account to store the pool state'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 111, 108];
+              },
+              {
+                kind: 'account';
+                path: 'ammConfig';
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint0';
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint1';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenMint0';
+          docs: ['Token_0 mint, the key must be smaller then token_1 mint.'];
+        },
+        {
+          name: 'tokenMint1';
+          docs: ['Token_1 mint'];
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['Token_0 vault for the pool'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 111, 108, 95, 118, 97, 117, 108, 116];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint0';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['Token_1 vault for the pool'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 111, 108, 95, 118, 97, 117, 108, 116];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint1';
+              }
+            ];
+          };
+        },
+        {
+          name: 'observationState';
+          docs: ['Initialize an account to store oracle observations'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 98, 115, 101, 114, 118, 97, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayBitmap';
+          docs: ['Initialize an account to store if a tick array is initialized.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [
+                  112,
+                  111,
+                  111,
+                  108,
+                  95,
+                  116,
+                  105,
+                  99,
+                  107,
+                  95,
+                  97,
+                  114,
+                  114,
+                  97,
+                  121,
+                  95,
+                  98,
+                  105,
+                  116,
+                  109,
+                  97,
+                  112,
+                  95,
+                  101,
+                  120,
+                  116,
+                  101,
+                  110,
+                  115,
+                  105,
+                  111,
+                  110
+                ];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenProgram0';
+          docs: ['Spl token program or token program 2022'];
+        },
+        {
+          name: 'tokenProgram1';
+          docs: ['Spl token program or token program 2022'];
+        },
+        {
+          name: 'systemProgram';
+          docs: ['To create a new program account'];
+          address: '11111111111111111111111111111111';
+        },
+        {
+          name: 'rent';
+          docs: ['Sysvar for program account'];
+          address: 'SysvarRent111111111111111111111111111111111';
+        }
+      ];
+      args: [
+        {
+          name: 'sqrtPriceX64';
+          type: 'u128';
+        },
+        {
+          name: 'openTime';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'createSupportMintAssociated';
+      docs: [
+        'Create support token22 mint account which can create pool and send rewards with ignoring the not support extensions.'
+      ];
+      discriminator: [17, 251, 65, 92, 136, 242, 14, 169];
+      accounts: [
+        {
+          name: 'owner';
+          docs: ['Address to be set as protocol owner.'];
+          writable: true;
+          signer: true;
+          address: 'GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ';
+        },
+        {
+          name: 'tokenMint';
+          docs: ['Support token mint'];
+        },
+        {
+          name: 'supportMintAssociated';
+          docs: ['Initialize support mint state account to store support mint address and bump.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [115, 117, 112, 112, 111, 114, 116, 95, 109, 105, 110, 116];
+              },
+              {
+                kind: 'account';
+                path: 'tokenMint';
+              }
+            ];
+          };
+        },
+        {
+          name: 'systemProgram';
+          address: '11111111111111111111111111111111';
+        }
+      ];
+      args: [];
+    },
+    {
+      name: 'decreaseLiquidity';
+      docs: [
+        '#[deprecated(note = "Use `decrease_liquidity_v2` instead.")]',
+        'Decreases liquidity for an existing position',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` -  The context of accounts',
+        '* `liquidity` - The amount by which liquidity will be decreased',
+        '* `amount_0_min` - The minimum amount of token_0 that should be accounted for the burned liquidity',
+        '* `amount_1_min` - The minimum amount of token_1 that should be accounted for the burned liquidity',
+        ''
+      ];
+      discriminator: [160, 38, 208, 111, 104, 91, 44, 1];
+      accounts: [
+        {
+          name: 'nftOwner';
+          docs: ['The position owner or delegated authority'];
+          signer: true;
+        },
+        {
+          name: 'nftAccount';
+          docs: ['The token account for the tokenized position'];
+        },
+        {
+          name: 'personalPosition';
+          docs: ['Decrease liquidity for this position'];
+          writable: true;
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_lower_index';
+                account: 'personalPositionState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_upper_index';
+                account: 'personalPositionState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['Token_0 vault'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['Token_1 vault'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayLower';
+          docs: ['Stores init state for the lower tick'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayUpper';
+          docs: ['Stores init state for the upper tick'];
+          writable: true;
+        },
+        {
+          name: 'recipientTokenAccount0';
+          docs: ['The destination token account for receive amount_0'];
+          writable: true;
+        },
+        {
+          name: 'recipientTokenAccount1';
+          docs: ['The destination token account for receive amount_1'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['SPL program to transfer out tokens'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        }
+      ];
+      args: [
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Min';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Min';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'decreaseLiquidityV2';
+      docs: [
+        'Decreases liquidity for an existing position, support Token2022',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` -  The context of accounts',
+        '* `liquidity` - The amount by which liquidity will be decreased',
+        '* `amount_0_min` - The minimum amount of token_0 that should be accounted for the burned liquidity',
+        '* `amount_1_min` - The minimum amount of token_1 that should be accounted for the burned liquidity',
+        ''
+      ];
+      discriminator: [58, 127, 188, 62, 79, 82, 196, 96];
+      accounts: [
+        {
+          name: 'nftOwner';
+          docs: ['The position owner or delegated authority'];
+          signer: true;
+        },
+        {
+          name: 'nftAccount';
+          docs: ['The token account for the tokenized position'];
+        },
+        {
+          name: 'personalPosition';
+          docs: ['Decrease liquidity for this position'];
+          writable: true;
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_lower_index';
+                account: 'personalPositionState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_upper_index';
+                account: 'personalPositionState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['Token_0 vault'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['Token_1 vault'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayLower';
+          docs: ['Stores init state for the lower tick'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayUpper';
+          docs: ['Stores init state for the upper tick'];
+          writable: true;
+        },
+        {
+          name: 'recipientTokenAccount0';
+          docs: ['The destination token account for receive amount_0'];
+          writable: true;
+        },
+        {
+          name: 'recipientTokenAccount1';
+          docs: ['The destination token account for receive amount_1'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['SPL program to transfer out tokens'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['Token program 2022'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'memoProgram';
+          docs: ['memo program'];
+          address: 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
+        },
+        {
+          name: 'vault0Mint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'vault1Mint';
+          docs: ['The mint of token vault 1'];
+        }
+      ];
+      args: [
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Min';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Min';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'increaseLiquidity';
+      docs: [
+        '#[deprecated(note = "Use `increase_liquidity_v2` instead.")]',
+        'Increases liquidity for an existing position, with amount paid by `payer`',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        "* `liquidity` - The desired liquidity to be added, can't be zero",
+        '* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check',
+        '* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check',
+        ''
+      ];
+      discriminator: [46, 156, 243, 118, 13, 205, 251, 178];
+      accounts: [
+        {
+          name: 'nftOwner';
+          docs: ['Pays to mint the position'];
+          signer: true;
+        },
+        {
+          name: 'nftAccount';
+          docs: ['The token account for nft'];
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_lower_index';
+                account: 'personalPositionState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_upper_index';
+                account: 'personalPositionState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'personalPosition';
+          docs: ['Increase liquidity for this position'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayLower';
+          docs: ['Stores init state for the lower tick'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayUpper';
+          docs: ['Stores init state for the upper tick'];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount0';
+          docs: ["The payer's token account for token_0"];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount1';
+          docs: ['The token account spending token_1 to mint the position'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Program to create mint account and mint tokens'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        }
+      ];
+      args: [
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Max';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Max';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'increaseLiquidityV2';
+      docs: [
+        'Increases liquidity for an existing position, with amount paid by `payer`, support Token2022',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `liquidity` - The desired liquidity to be added, if zero, calculate liquidity base amount_0 or amount_1 according base_flag',
+        '* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check',
+        '* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check',
+        '* `base_flag` - must be specified if liquidity is zero, true: calculate liquidity base amount_0_max otherwise base amount_1_max',
+        ''
+      ];
+      discriminator: [133, 29, 89, 223, 69, 238, 176, 10];
+      accounts: [
+        {
+          name: 'nftOwner';
+          docs: ['Pays to mint the position'];
+          signer: true;
+        },
+        {
+          name: 'nftAccount';
+          docs: ['The token account for nft'];
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_lower_index';
+                account: 'personalPositionState';
+              },
+              {
+                kind: 'account';
+                path: 'personal_position.tick_upper_index';
+                account: 'personalPositionState';
+              }
+            ];
+          };
+        },
+        {
+          name: 'personalPosition';
+          docs: ['Increase liquidity for this position'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayLower';
+          docs: ['Stores init state for the lower tick'];
+          writable: true;
+        },
+        {
+          name: 'tickArrayUpper';
+          docs: ['Stores init state for the upper tick'];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount0';
+          docs: ["The payer's token account for token_0"];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount1';
+          docs: ['The token account spending token_1 to mint the position'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Program to create mint account and mint tokens'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['Token program 2022'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'vault0Mint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'vault1Mint';
+          docs: ['The mint of token vault 1'];
+        }
+      ];
+      args: [
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Max';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Max';
+          type: 'u64';
+        },
+        {
+          name: 'baseFlag';
+          type: {
+            option: 'bool';
+          };
+        }
+      ];
+    },
+    {
+      name: 'initializeReward';
+      docs: [
+        'Initialize a reward info for a given pool and reward index',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `reward_index` - the index to reward info',
+        '* `open_time` - reward open timestamp',
+        '* `end_time` - reward end timestamp',
+        '* `emissions_per_second_x64` - Token reward per second are earned per unit of liquidity.',
+        ''
+      ];
+      discriminator: [95, 135, 192, 196, 242, 129, 230, 68];
+      accounts: [
+        {
+          name: 'rewardFunder';
+          docs: ['The founder deposit reward token to vault'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'funderTokenAccount';
+          writable: true;
+        },
+        {
+          name: 'ammConfig';
+          docs: ['For check the reward_funder authority'];
+        },
+        {
+          name: 'poolState';
+          docs: ['Set reward for this pool'];
+          writable: true;
+        },
+        {
+          name: 'operationState';
+          docs: ['load info from the account to judge reward permission'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 112, 101, 114, 97, 116, 105, 111, 110];
+              }
+            ];
+          };
+        },
+        {
+          name: 'rewardTokenMint';
+          docs: ['Reward mint'];
+        },
+        {
+          name: 'rewardTokenVault';
+          docs: ['A pda, reward vault'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 111, 108, 95, 114, 101, 119, 97, 114, 100, 95, 118, 97, 117, 108, 116];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'account';
+                path: 'rewardTokenMint';
+              }
+            ];
+          };
+        },
+        {
+          name: 'rewardTokenProgram';
+        },
+        {
+          name: 'systemProgram';
+          address: '11111111111111111111111111111111';
+        },
+        {
+          name: 'rent';
+          address: 'SysvarRent111111111111111111111111111111111';
+        }
+      ];
+      args: [
+        {
+          name: 'param';
+          type: {
+            defined: {
+              name: 'initializeRewardParam';
+            };
+          };
+        }
+      ];
+    },
+    {
+      name: 'openPosition';
+      docs: [
+        '#[deprecated(note = "Use `open_position_with_token22_nft` instead.")]',
+        'Creates a new position wrapped in a NFT',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `tick_lower_index` - The low boundary of market',
+        '* `tick_upper_index` - The upper boundary of market',
+        '* `tick_array_lower_start_index` - The start index of tick array which include tick low',
+        '* `tick_array_upper_start_index` - The start index of tick array which include tick upper',
+        '* `liquidity` - The liquidity to be added',
+        '* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check',
+        '* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check',
+        ''
+      ];
+      discriminator: [135, 128, 47, 77, 15, 152, 240, 49];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['Pays to mint the position'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftOwner';
+        },
+        {
+          name: 'positionNftMint';
+          docs: ['Unique token mint address'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftAccount';
+          docs: [
+            'Token account where position NFT will be minted',
+            'This account created in the contract by cpi to avoid large stack variables'
+          ];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'account';
+                path: 'positionNftOwner';
+              },
+              {
+                kind: 'const';
+                value: [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ];
+              },
+              {
+                kind: 'account';
+                path: 'positionNftMint';
+              }
+            ];
+            program: {
+              kind: 'const';
+              value: [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ];
+            };
+          };
+        },
+        {
+          name: 'metadataAccount';
+          docs: ['To store metaplex metadata'];
+          writable: true;
+        },
+        {
+          name: 'poolState';
+          docs: ['Add liquidity for this pool'];
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          docs: ['Store the information of market marking in range'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickLowerIndex';
+              },
+              {
+                kind: 'arg';
+                path: 'tickUpperIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayLower';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [116, 105, 99, 107, 95, 97, 114, 114, 97, 121];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickArrayLowerStartIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayUpper';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [116, 105, 99, 107, 95, 97, 114, 114, 97, 121];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickArrayUpperStartIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'personalPosition';
+          docs: ['personal position state'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'positionNftMint';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenAccount0';
+          docs: ['The token_0 account deposit token to the pool'];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount1';
+          docs: ['The token_1 account deposit token to the pool'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'rent';
+          docs: ['Sysvar for token mint and ATA creation'];
+          address: 'SysvarRent111111111111111111111111111111111';
+        },
+        {
+          name: 'systemProgram';
+          docs: ['Program to create the position manager state account'];
+          address: '11111111111111111111111111111111';
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Program to create mint account and mint tokens'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'associatedTokenProgram';
+          docs: ['Program to create an ATA for receiving position NFT'];
+          address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
+        },
+        {
+          name: 'metadataProgram';
+          docs: ['Program to create NFT metadata'];
+          address: 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s';
+        }
+      ];
+      args: [
+        {
+          name: 'tickLowerIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickUpperIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickArrayLowerStartIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickArrayUpperStartIndex';
+          type: 'i32';
+        },
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Max';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Max';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'openPositionV2';
+      docs: [
+        '#[deprecated(note = "Use `open_position_with_token22_nft` instead.")]',
+        'Creates a new position wrapped in a NFT, support Token2022',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `tick_lower_index` - The low boundary of market',
+        '* `tick_upper_index` - The upper boundary of market',
+        '* `tick_array_lower_start_index` - The start index of tick array which include tick low',
+        '* `tick_array_upper_start_index` - The start index of tick array which include tick upper',
+        '* `liquidity` - The liquidity to be added, if zero, and the base_flag is specified, calculate liquidity base amount_0_max or amount_1_max according base_flag, otherwise open position with zero liquidity',
+        '* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check',
+        '* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check',
+        '* `with_metadata` - The flag indicating whether to create NFT mint metadata',
+        '* `base_flag` - if the liquidity specified as zero, true: calculate liquidity base amount_0_max otherwise base amount_1_max',
+        ''
+      ];
+      discriminator: [77, 184, 74, 214, 112, 86, 241, 199];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['Pays to mint the position'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftOwner';
+        },
+        {
+          name: 'positionNftMint';
+          docs: ['Unique token mint address'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftAccount';
+          docs: ['Token account where position NFT will be minted'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'account';
+                path: 'positionNftOwner';
+              },
+              {
+                kind: 'const';
+                value: [
+                  6,
+                  221,
+                  246,
+                  225,
+                  215,
+                  101,
+                  161,
+                  147,
+                  217,
+                  203,
+                  225,
+                  70,
+                  206,
+                  235,
+                  121,
+                  172,
+                  28,
+                  180,
+                  133,
+                  237,
+                  95,
+                  91,
+                  55,
+                  145,
+                  58,
+                  140,
+                  245,
+                  133,
+                  126,
+                  255,
+                  0,
+                  169
+                ];
+              },
+              {
+                kind: 'account';
+                path: 'positionNftMint';
+              }
+            ];
+            program: {
+              kind: 'const';
+              value: [
+                140,
+                151,
+                37,
+                143,
+                78,
+                36,
+                137,
+                241,
+                187,
+                61,
+                16,
+                41,
+                20,
+                142,
+                13,
+                131,
+                11,
+                90,
+                19,
+                153,
+                218,
+                255,
+                16,
+                132,
+                4,
+                142,
+                123,
+                216,
+                219,
+                233,
+                248,
+                89
+              ];
+            };
+          };
+        },
+        {
+          name: 'metadataAccount';
+          docs: ['To store metaplex metadata'];
+          writable: true;
+        },
+        {
+          name: 'poolState';
+          docs: ['Add liquidity for this pool'];
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          docs: ['Store the information of market marking in range'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickLowerIndex';
+              },
+              {
+                kind: 'arg';
+                path: 'tickUpperIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayLower';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [116, 105, 99, 107, 95, 97, 114, 114, 97, 121];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickArrayLowerStartIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayUpper';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [116, 105, 99, 107, 95, 97, 114, 114, 97, 121];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickArrayUpperStartIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'personalPosition';
+          docs: ['personal position state'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'positionNftMint';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenAccount0';
+          docs: ['The token_0 account deposit token to the pool'];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount1';
+          docs: ['The token_1 account deposit token to the pool'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'rent';
+          docs: ['Sysvar for token mint and ATA creation'];
+          address: 'SysvarRent111111111111111111111111111111111';
+        },
+        {
+          name: 'systemProgram';
+          docs: ['Program to create the position manager state account'];
+          address: '11111111111111111111111111111111';
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Program to create mint account and mint tokens'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'associatedTokenProgram';
+          docs: ['Program to create an ATA for receiving position NFT'];
+          address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
+        },
+        {
+          name: 'metadataProgram';
+          docs: ['Program to create NFT metadata'];
+          address: 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['Program to create mint account and mint tokens'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'vault0Mint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'vault1Mint';
+          docs: ['The mint of token vault 1'];
+        }
+      ];
+      args: [
+        {
+          name: 'tickLowerIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickUpperIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickArrayLowerStartIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickArrayUpperStartIndex';
+          type: 'i32';
+        },
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Max';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Max';
+          type: 'u64';
+        },
+        {
+          name: 'withMetadata';
+          type: 'bool';
+        },
+        {
+          name: 'baseFlag';
+          type: {
+            option: 'bool';
+          };
+        }
+      ];
+    },
+    {
+      name: 'openPositionWithToken22Nft';
+      docs: [
+        'Creates a new position wrapped in a Token2022 NFT without relying on metadata_program and metadata_account, reduce the cost for user to create a personal position.',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `tick_lower_index` - The low boundary of market',
+        '* `tick_upper_index` - The upper boundary of market',
+        '* `tick_array_lower_start_index` - The start index of tick array which include tick low',
+        '* `tick_array_upper_start_index` - The start index of tick array which include tick upper',
+        '* `liquidity` - The liquidity to be added, if zero, and the base_flag is specified, calculate liquidity base amount_0_max or amount_1_max according base_flag, otherwise open position with zero liquidity',
+        '* `amount_0_max` - The max amount of token_0 to spend, which serves as a slippage check',
+        '* `amount_1_max` - The max amount of token_1 to spend, which serves as a slippage check',
+        '* `with_metadata` - The flag indicating whether to create NFT mint metadata',
+        '* `base_flag` - if the liquidity specified as zero, true: calculate liquidity base amount_0_max otherwise base amount_1_max',
+        ''
+      ];
+      discriminator: [77, 255, 174, 82, 125, 29, 201, 46];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['Pays to mint the position'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftOwner';
+        },
+        {
+          name: 'positionNftMint';
+          docs: ['Unique token mint address, initialize in contract'];
+          writable: true;
+          signer: true;
+        },
+        {
+          name: 'positionNftAccount';
+          writable: true;
+        },
+        {
+          name: 'poolState';
+          docs: ['Add liquidity for this pool'];
+          writable: true;
+        },
+        {
+          name: 'protocolPosition';
+          docs: ['Store the information of market marking in range'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickLowerIndex';
+              },
+              {
+                kind: 'arg';
+                path: 'tickUpperIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayLower';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [116, 105, 99, 107, 95, 97, 114, 114, 97, 121];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickArrayLowerStartIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tickArrayUpper';
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [116, 105, 99, 107, 95, 97, 114, 114, 97, 121];
+              },
+              {
+                kind: 'account';
+                path: 'poolState';
+              },
+              {
+                kind: 'arg';
+                path: 'tickArrayUpperStartIndex';
+              }
+            ];
+          };
+        },
+        {
+          name: 'personalPosition';
+          docs: ['personal position state'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [112, 111, 115, 105, 116, 105, 111, 110];
+              },
+              {
+                kind: 'account';
+                path: 'positionNftMint';
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenAccount0';
+          docs: ['The token_0 account deposit token to the pool'];
+          writable: true;
+        },
+        {
+          name: 'tokenAccount1';
+          docs: ['The token_1 account deposit token to the pool'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault0';
+          docs: ['The address that holds pool tokens for token_0'];
+          writable: true;
+        },
+        {
+          name: 'tokenVault1';
+          docs: ['The address that holds pool tokens for token_1'];
+          writable: true;
+        },
+        {
+          name: 'rent';
+          docs: ['Sysvar for token mint and ATA creation'];
+          address: 'SysvarRent111111111111111111111111111111111';
+        },
+        {
+          name: 'systemProgram';
+          docs: ['Program to create the position manager state account'];
+          address: '11111111111111111111111111111111';
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Program to transfer for token account'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'associatedTokenProgram';
+          docs: ['Program to create an ATA for receiving position NFT'];
+          address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['Program to create NFT mint/token account and transfer for token22 account'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'vault0Mint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'vault1Mint';
+          docs: ['The mint of token vault 1'];
+        }
+      ];
+      args: [
+        {
+          name: 'tickLowerIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickUpperIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickArrayLowerStartIndex';
+          type: 'i32';
+        },
+        {
+          name: 'tickArrayUpperStartIndex';
+          type: 'i32';
+        },
+        {
+          name: 'liquidity';
+          type: 'u128';
+        },
+        {
+          name: 'amount0Max';
+          type: 'u64';
+        },
+        {
+          name: 'amount1Max';
+          type: 'u64';
+        },
+        {
+          name: 'withMetadata';
+          type: 'bool';
+        },
+        {
+          name: 'baseFlag';
+          type: {
+            option: 'bool';
+          };
+        }
+      ];
+    },
+    {
+      name: 'setRewardParams';
+      docs: [
+        'Reset reward param, start a new reward cycle or extend the current cycle.',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `reward_index` - The index of reward token in the pool.',
+        '* `emissions_per_second_x64` - The per second emission reward, when extend the current cycle,',
+        "new value can't be less than old value",
+        '* `open_time` - reward open timestamp, must be set when starting a new cycle',
+        '* `end_time` - reward end timestamp',
+        ''
+      ];
+      discriminator: [112, 52, 167, 75, 32, 201, 211, 137];
+      accounts: [
+        {
+          name: 'authority';
+          docs: ['Address to be set as protocol owner. It pays to create factory state account.'];
+          signer: true;
+        },
+        {
+          name: 'ammConfig';
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        },
+        {
+          name: 'operationState';
+          docs: ['load info from the account to judge reward permission'];
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 112, 101, 114, 97, 116, 105, 111, 110];
+              }
+            ];
+          };
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['Token program'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['Token program 2022'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        }
+      ];
+      args: [
+        {
+          name: 'rewardIndex';
+          type: 'u8';
+        },
+        {
+          name: 'emissionsPerSecondX64';
+          type: 'u128';
+        },
+        {
+          name: 'openTime';
+          type: 'u64';
+        },
+        {
+          name: 'endTime';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'swap';
+      docs: [
+        '#[deprecated(note = "Use `swap_v2` instead.")]',
+        'Swaps one token for as much as possible of another token across a single pool',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `amount` - Arranged in pairs with other_amount_threshold. (amount_in, amount_out_minimum) or (amount_out, amount_in_maximum)',
+        '* `other_amount_threshold` - For slippage check',
+        '* `sqrt_price_limit` - The Q64.64 sqrt price √P limit. If zero for one, the price cannot',
+        '* `is_base_input` - swap base input or swap base output',
+        ''
+      ];
+      discriminator: [248, 198, 158, 145, 225, 117, 135, 200];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['The user performing the swap'];
+          signer: true;
+        },
+        {
+          name: 'ammConfig';
+          docs: ['The factory state to read protocol fees'];
+        },
+        {
+          name: 'poolState';
+          docs: ['The program account of the pool in which the swap will be performed'];
+          writable: true;
+        },
+        {
+          name: 'inputTokenAccount';
+          docs: ['The user token account for input token'];
+          writable: true;
+        },
+        {
+          name: 'outputTokenAccount';
+          docs: ['The user token account for output token'];
+          writable: true;
+        },
+        {
+          name: 'inputVault';
+          docs: ['The vault token account for input token'];
+          writable: true;
+        },
+        {
+          name: 'outputVault';
+          docs: ['The vault token account for output token'];
+          writable: true;
+        },
+        {
+          name: 'observationState';
+          docs: ['The program account for the most recent oracle observation'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['SPL program for token transfers'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tickArray';
+          writable: true;
+        }
+      ];
+      args: [
+        {
+          name: 'amount';
+          type: 'u64';
+        },
+        {
+          name: 'otherAmountThreshold';
+          type: 'u64';
+        },
+        {
+          name: 'sqrtPriceLimitX64';
+          type: 'u128';
+        },
+        {
+          name: 'isBaseInput';
+          type: 'bool';
+        }
+      ];
+    },
+    {
+      name: 'swapRouterBaseIn';
+      docs: [
+        'Swap token for as much as possible of another token across the path provided, base input',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `amount_in` - Token amount to be swapped in',
+        '* `amount_out_minimum` - Panic if output amount is below minimum amount. For slippage.',
+        ''
+      ];
+      discriminator: [69, 125, 115, 218, 245, 186, 242, 196];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['The user performing the swap'];
+          signer: true;
+        },
+        {
+          name: 'inputTokenAccount';
+          docs: ['The token account that pays input tokens for the swap'];
+          writable: true;
+        },
+        {
+          name: 'inputTokenMint';
+          docs: ['The mint of input token'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['SPL program for token transfers'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['SPL program 2022 for token transfers'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'memoProgram';
+          docs: ['Memo program'];
+          address: 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
+        }
+      ];
+      args: [
+        {
+          name: 'amountIn';
+          type: 'u64';
+        },
+        {
+          name: 'amountOutMinimum';
+          type: 'u64';
+        }
+      ];
+    },
+    {
+      name: 'swapV2';
+      docs: [
+        'Swaps one token for as much as possible of another token across a single pool, support token program 2022',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx` - The context of accounts',
+        '* `amount` - Arranged in pairs with other_amount_threshold. (amount_in, amount_out_minimum) or (amount_out, amount_in_maximum)',
+        '* `other_amount_threshold` - For slippage check',
+        '* `sqrt_price_limit` - The Q64.64 sqrt price √P limit. If zero for one, the price cannot',
+        '* `is_base_input` - swap base input or swap base output',
+        ''
+      ];
+      discriminator: [43, 4, 237, 11, 26, 201, 30, 98];
+      accounts: [
+        {
+          name: 'payer';
+          docs: ['The user performing the swap'];
+          signer: true;
+        },
+        {
+          name: 'ammConfig';
+          docs: ['The factory state to read protocol fees'];
+        },
+        {
+          name: 'poolState';
+          docs: ['The program account of the pool in which the swap will be performed'];
+          writable: true;
+        },
+        {
+          name: 'inputTokenAccount';
+          docs: ['The user token account for input token'];
+          writable: true;
+        },
+        {
+          name: 'outputTokenAccount';
+          docs: ['The user token account for output token'];
+          writable: true;
+        },
+        {
+          name: 'inputVault';
+          docs: ['The vault token account for input token'];
+          writable: true;
+        },
+        {
+          name: 'outputVault';
+          docs: ['The vault token account for output token'];
+          writable: true;
+        },
+        {
+          name: 'observationState';
+          docs: ['The program account for the most recent oracle observation'];
+          writable: true;
+        },
+        {
+          name: 'tokenProgram';
+          docs: ['SPL program for token transfers'];
+          address: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
+        },
+        {
+          name: 'tokenProgram2022';
+          docs: ['SPL program 2022 for token transfers'];
+          address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
+        },
+        {
+          name: 'memoProgram';
+          docs: ['Memo program'];
+          address: 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
+        },
+        {
+          name: 'inputVaultMint';
+          docs: ['The mint of token vault 0'];
+        },
+        {
+          name: 'outputVaultMint';
+          docs: ['The mint of token vault 1'];
+        }
+      ];
+      args: [
+        {
+          name: 'amount';
+          type: 'u64';
+        },
+        {
+          name: 'otherAmountThreshold';
+          type: 'u64';
+        },
+        {
+          name: 'sqrtPriceLimitX64';
+          type: 'u128';
+        },
+        {
+          name: 'isBaseInput';
+          type: 'bool';
+        }
+      ];
+    },
+    {
+      name: 'transferRewardOwner';
+      docs: [
+        'Transfer reward owner',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `new_owner`- new owner pubkey',
+        ''
+      ];
+      discriminator: [7, 22, 12, 83, 242, 43, 48, 121];
+      accounts: [
+        {
+          name: 'authority';
+          docs: ['Address to be set as operation account owner.'];
+          signer: true;
+          address: 'GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ';
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        }
+      ];
+      args: [
+        {
+          name: 'newOwner';
+          type: 'pubkey';
+        }
+      ];
+    },
+    {
+      name: 'updateAmmConfig';
+      docs: [
+        'Updates the owner of the amm config',
+        'Must be called by the current owner or admin',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `trade_fee_rate`- The new trade fee rate of amm config, be set when `param` is 0',
+        '* `protocol_fee_rate`- The new protocol fee rate of amm config, be set when `param` is 1',
+        '* `fund_fee_rate`- The new fund fee rate of amm config, be set when `param` is 2',
+        "* `new_owner`- The config's new owner, be set when `param` is 3",
+        "* `new_fund_owner`- The config's new fund owner, be set when `param` is 4",
+        '* `param`- The value can be 0 | 1 | 2 | 3 | 4, otherwise will report a error',
+        ''
+      ];
+      discriminator: [49, 60, 174, 136, 154, 28, 116, 200];
+      accounts: [
+        {
+          name: 'owner';
+          docs: ['The amm config owner or admin'];
+          signer: true;
+          address: 'GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ';
+        },
+        {
+          name: 'ammConfig';
+          docs: ['Amm config account to be changed'];
+          writable: true;
+        }
+      ];
+      args: [
+        {
+          name: 'param';
+          type: 'u8';
+        },
+        {
+          name: 'value';
+          type: 'u32';
+        }
+      ];
+    },
+    {
+      name: 'updateOperationAccount';
+      docs: [
+        'Update the operation account',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `param`- The value can be 0 | 1 | 2 | 3, otherwise will report a error',
+        '* `keys`- update operation owner when the `param` is 0',
+        'remove operation owner when the `param` is 1',
+        'update whitelist mint when the `param` is 2',
+        'remove whitelist mint when the `param` is 3',
+        ''
+      ];
+      discriminator: [127, 70, 119, 40, 188, 227, 61, 7];
+      accounts: [
+        {
+          name: 'owner';
+          docs: ['Address to be set as operation account owner.'];
+          signer: true;
+          address: 'GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ';
+        },
+        {
+          name: 'operationState';
+          docs: ['Initialize operation state account to store operation owner address and white list mint.'];
+          writable: true;
+          pda: {
+            seeds: [
+              {
+                kind: 'const';
+                value: [111, 112, 101, 114, 97, 116, 105, 111, 110];
+              }
+            ];
+          };
+        },
+        {
+          name: 'systemProgram';
+          address: '11111111111111111111111111111111';
+        }
+      ];
+      args: [
+        {
+          name: 'param';
+          type: 'u8';
+        },
+        {
+          name: 'keys';
+          type: {
+            vec: 'pubkey';
+          };
+        }
+      ];
+    },
+    {
+      name: 'updatePoolStatus';
+      docs: [
+        'Update pool status for given value',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        '* `status` - The value of status',
+        ''
+      ];
+      discriminator: [130, 87, 108, 6, 46, 224, 117, 123];
+      accounts: [
+        {
+          name: 'authority';
+          signer: true;
+          address: 'GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ';
+        },
+        {
+          name: 'poolState';
+          writable: true;
+        }
+      ];
+      args: [
+        {
+          name: 'status';
+          type: 'u8';
+        }
+      ];
+    },
+    {
+      name: 'updateRewardInfos';
+      docs: [
+        'Update rewards info of the given pool, can be called for everyone',
+        '',
+        '# Arguments',
+        '',
+        '* `ctx`- The context of accounts',
+        ''
+      ];
+      discriminator: [163, 172, 224, 52, 11, 154, 106, 223];
+      accounts: [
+        {
+          name: 'poolState';
+          docs: ['The liquidity pool for which reward info to update'];
+          writable: true;
+        }
+      ];
+      args: [];
+    }
+  ];
+  accounts: [
+    {
+      name: 'ammConfig';
+      discriminator: [218, 244, 33, 104, 203, 203, 43, 111];
+    },
+    {
+      name: 'observationState';
+      discriminator: [122, 174, 197, 53, 129, 9, 165, 132];
+    },
+    {
+      name: 'operationState';
+      discriminator: [19, 236, 58, 237, 81, 222, 183, 252];
+    },
+    {
+      name: 'personalPositionState';
+      discriminator: [70, 111, 150, 126, 230, 15, 25, 117];
+    },
+    {
+      name: 'poolState';
+      discriminator: [247, 237, 227, 245, 215, 195, 222, 70];
+    },
+    {
+      name: 'protocolPositionState';
+      discriminator: [100, 226, 145, 99, 146, 218, 160, 106];
+    },
+    {
+      name: 'supportMintAssociated';
+      discriminator: [134, 40, 183, 79, 12, 112, 162, 53];
+    },
+    {
+      name: 'tickArrayBitmapExtension';
+      discriminator: [60, 150, 36, 219, 97, 128, 139, 153];
+    },
+    {
+      name: 'tickArrayState';
+      discriminator: [192, 155, 85, 205, 49, 249, 129, 42];
+    }
+  ];
+  events: [
+    {
+      name: 'collectPersonalFeeEvent';
+      discriminator: [166, 174, 105, 192, 81, 161, 83, 105];
+    },
+    {
+      name: 'collectProtocolFeeEvent';
+      discriminator: [206, 87, 17, 79, 45, 41, 213, 61];
+    },
+    {
+      name: 'configChangeEvent';
+      discriminator: [247, 189, 7, 119, 106, 112, 95, 151];
+    },
+    {
+      name: 'createPersonalPositionEvent';
+      discriminator: [100, 30, 87, 249, 196, 223, 154, 206];
+    },
+    {
+      name: 'decreaseLiquidityEvent';
+      discriminator: [58, 222, 86, 58, 68, 50, 85, 56];
+    },
+    {
+      name: 'increaseLiquidityEvent';
+      discriminator: [49, 79, 105, 212, 32, 34, 30, 84];
+    },
+    {
+      name: 'liquidityCalculateEvent';
+      discriminator: [237, 112, 148, 230, 57, 84, 180, 162];
+    },
+    {
+      name: 'liquidityChangeEvent';
+      discriminator: [126, 240, 175, 206, 158, 88, 153, 107];
+    },
+    {
+      name: 'poolCreatedEvent';
+      discriminator: [25, 94, 75, 47, 112, 99, 53, 63];
+    },
+    {
+      name: 'swapEvent';
+      discriminator: [64, 198, 205, 232, 38, 8, 113, 226];
+    },
+    {
+      name: 'updateRewardInfosEvent';
+      discriminator: [109, 127, 186, 78, 114, 65, 37, 236];
+    }
+  ];
+  errors: [
+    {
+      code: 6000;
+      name: 'lok';
+      msg: 'lok';
+    },
+    {
+      code: 6001;
+      name: 'notApproved';
+      msg: 'Not approved';
+    },
+    {
+      code: 6002;
+      name: 'invalidUpdateConfigFlag';
+      msg: 'invalid update amm config flag';
+    },
+    {
+      code: 6003;
+      name: 'accountLack';
+      msg: 'Account lack';
+    },
+    {
+      code: 6004;
+      name: 'closePositionErr';
+      msg: 'Remove liquitity, collect fees owed and reward then you can close position account';
+    },
+    {
+      code: 6005;
+      name: 'zeroMintAmount';
+      msg: 'Minting amount should be greater than 0';
+    },
+    {
+      code: 6006;
+      name: 'invaildTickIndex';
+      msg: 'Tick out of range';
+    },
+    {
+      code: 6007;
+      name: 'tickInvaildOrder';
+      msg: 'The lower tick must be below the upper tick';
+    },
+    {
+      code: 6008;
+      name: 'tickLowerOverflow';
+      msg: 'The tick must be greater, or equal to the minimum tick(-443636)';
+    },
+    {
+      code: 6009;
+      name: 'tickUpperOverflow';
+      msg: 'The tick must be lesser than, or equal to the maximum tick(443636)';
+    },
+    {
+      code: 6010;
+      name: 'tickAndSpacingNotMatch';
+      msg: 'tick % tick_spacing must be zero';
+    },
+    {
+      code: 6011;
+      name: 'invalidTickArray';
+      msg: 'Invaild tick array account';
+    },
+    {
+      code: 6012;
+      name: 'invalidTickArrayBoundary';
+      msg: 'Invaild tick array boundary';
+    },
+    {
+      code: 6013;
+      name: 'sqrtPriceLimitOverflow';
+      msg: 'Square root price limit overflow';
+    },
+    {
+      code: 6014;
+      name: 'sqrtPriceX64';
+      msg: 'sqrt_price_x64 out of range';
+    },
+    {
+      code: 6015;
+      name: 'liquiditySubValueErr';
+      msg: 'Liquidity sub delta L must be smaller than before';
+    },
+    {
+      code: 6016;
+      name: 'liquidityAddValueErr';
+      msg: 'Liquidity add delta L must be greater, or equal to before';
+    },
+    {
+      code: 6017;
+      name: 'invaildLiquidity';
+      msg: 'Invaild liquidity when update position';
+    },
+    {
+      code: 6018;
+      name: 'forbidBothZeroForSupplyLiquidity';
+      msg: 'Both token amount must not be zero while supply liquidity';
+    },
+    {
+      code: 6019;
+      name: 'liquidityInsufficient';
+      msg: 'Liquidity insufficient';
+    },
+    {
+      code: 6020;
+      name: 'transactionTooOld';
+      msg: 'Transaction too old';
+    },
+    {
+      code: 6021;
+      name: 'priceSlippageCheck';
+      msg: 'Price slippage check';
+    },
+    {
+      code: 6022;
+      name: 'tooLittleOutputReceived';
+      msg: 'Too little output received';
+    },
+    {
+      code: 6023;
+      name: 'tooMuchInputPaid';
+      msg: 'Too much input paid';
+    },
+    {
+      code: 6024;
+      name: 'zeroAmountSpecified';
+      msg: 'Swap special amount can not be zero';
+    },
+    {
+      code: 6025;
+      name: 'invalidInputPoolVault';
+      msg: 'Input pool vault is invalid';
+    },
+    {
+      code: 6026;
+      name: 'tooSmallInputOrOutputAmount';
+      msg: 'Swap input or output amount is too small';
+    },
+    {
+      code: 6027;
+      name: 'notEnoughTickArrayAccount';
+      msg: 'Not enought tick array account';
+    },
+    {
+      code: 6028;
+      name: 'invalidFirstTickArrayAccount';
+      msg: 'Invaild first tick array account';
+    },
+    {
+      code: 6029;
+      name: 'invalidRewardIndex';
+      msg: 'Invalid reward index';
+    },
+    {
+      code: 6030;
+      name: 'fullRewardInfo';
+      msg: 'The init reward token reach to the max';
+    },
+    {
+      code: 6031;
+      name: 'rewardTokenAlreadyInUse';
+      msg: 'The init reward token already in use';
+    },
+    {
+      code: 6032;
+      name: 'exceptRewardMint';
+      msg: 'The reward tokens must contain one of pool vault mint except the last reward';
+    },
+    {
+      code: 6033;
+      name: 'invalidRewardInitParam';
+      msg: 'Invalid reward init param';
+    },
+    {
+      code: 6034;
+      name: 'invalidRewardDesiredAmount';
+      msg: 'Invalid collect reward desired amount';
+    },
+    {
+      code: 6035;
+      name: 'invalidRewardInputAccountNumber';
+      msg: 'Invalid collect reward input account number';
+    },
+    {
+      code: 6036;
+      name: 'invalidRewardPeriod';
+      msg: 'Invalid reward period';
+    },
+    {
+      code: 6037;
+      name: 'notApproveUpdateRewardEmissiones';
+      msg: 'Modification of emissiones is allowed within 72 hours from the end of the previous cycle';
+    },
+    {
+      code: 6038;
+      name: 'unInitializedRewardInfo';
+      msg: 'uninitialized reward info';
+    },
+    {
+      code: 6039;
+      name: 'notSupportMint';
+      msg: 'Not support token_2022 mint extension';
+    },
+    {
+      code: 6040;
+      name: 'missingTickArrayBitmapExtensionAccount';
+      msg: 'Missing tickarray bitmap extension account';
+    },
+    {
+      code: 6041;
+      name: 'insufficientLiquidityForDirection';
+      msg: 'Insufficient liquidity for this direction';
+    },
+    {
+      code: 6042;
+      name: 'maxTokenOverflow';
+      msg: 'Max token overflow';
+    },
+    {
+      code: 6043;
+      name: 'calculateOverflow';
+      msg: 'calculate overflow';
+    }
+  ];
+  types: [
+    {
+      name: 'ammConfig';
+      docs: ['Holds the current owner of the factory'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'bump';
+            docs: ['Bump to identify PDA'];
+            type: 'u8';
+          },
+          {
+            name: 'index';
+            type: 'u16';
+          },
+          {
+            name: 'owner';
+            docs: ['Address of the protocol owner'];
+            type: 'pubkey';
+          },
+          {
+            name: 'protocolFeeRate';
+            docs: ['The protocol fee'];
+            type: 'u32';
+          },
+          {
+            name: 'tradeFeeRate';
+            docs: ['The trade fee, denominated in hundredths of a bip (10^-6)'];
+            type: 'u32';
+          },
+          {
+            name: 'tickSpacing';
+            docs: ['The tick spacing'];
+            type: 'u16';
+          },
+          {
+            name: 'fundFeeRate';
+            docs: ['The fund fee, denominated in hundredths of a bip (10^-6)'];
+            type: 'u32';
+          },
+          {
+            name: 'paddingU32';
+            type: 'u32';
+          },
+          {
+            name: 'fundOwner';
+            type: 'pubkey';
+          },
+          {
+            name: 'padding';
+            type: {
+              array: ['u64', 3];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'collectPersonalFeeEvent';
+      docs: ['Emitted when tokens are collected for a position'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'positionNftMint';
+            docs: ['The ID of the token for which underlying tokens were collected'];
+            type: 'pubkey';
+          },
+          {
+            name: 'recipientTokenAccount0';
+            docs: ['The token account that received the collected token_0 tokens'];
+            type: 'pubkey';
+          },
+          {
+            name: 'recipientTokenAccount1';
+            docs: ['The token account that received the collected token_1 tokens'];
+            type: 'pubkey';
+          },
+          {
+            name: 'amount0';
+            docs: ['The amount of token_0 owed to the position that was collected'];
+            type: 'u64';
+          },
+          {
+            name: 'amount1';
+            docs: ['The amount of token_1 owed to the position that was collected'];
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'collectProtocolFeeEvent';
+      docs: ['Emitted when the collected protocol fees are withdrawn by the factory owner'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolState';
+            docs: ['The pool whose protocol fee is collected'];
+            type: 'pubkey';
+          },
+          {
+            name: 'recipientTokenAccount0';
+            docs: ['The address that receives the collected token_0 protocol fees'];
+            type: 'pubkey';
+          },
+          {
+            name: 'recipientTokenAccount1';
+            docs: ['The address that receives the collected token_1 protocol fees'];
+            type: 'pubkey';
+          },
+          {
+            name: 'amount0';
+            docs: ['The amount of token_0 protocol fees that is withdrawn'];
+            type: 'u64';
+          },
+          {
+            name: 'amount1';
+            docs: ['The amount of token_0 protocol fees that is withdrawn'];
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'configChangeEvent';
+      docs: ['Emitted when create or update a config'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'index';
+            type: 'u16';
+          },
+          {
+            name: 'owner';
+            type: 'pubkey';
+          },
+          {
+            name: 'protocolFeeRate';
+            type: 'u32';
+          },
+          {
+            name: 'tradeFeeRate';
+            type: 'u32';
+          },
+          {
+            name: 'tickSpacing';
+            type: 'u16';
+          },
+          {
+            name: 'fundFeeRate';
+            type: 'u32';
+          },
+          {
+            name: 'fundOwner';
+            type: 'pubkey';
+          }
+        ];
+      };
+    },
+    {
+      name: 'createPersonalPositionEvent';
+      docs: ['Emitted when create a new position'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolState';
+            docs: ['The pool for which liquidity was added'];
+            type: 'pubkey';
+          },
+          {
+            name: 'minter';
+            docs: ['The address that create the position'];
+            type: 'pubkey';
+          },
+          {
+            name: 'nftOwner';
+            docs: ['The owner of the position and recipient of any minted liquidity'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tickLowerIndex';
+            docs: ['The lower tick of the position'];
+            type: 'i32';
+          },
+          {
+            name: 'tickUpperIndex';
+            docs: ['The upper tick of the position'];
+            type: 'i32';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The amount of liquidity minted to the position range'];
+            type: 'u128';
+          },
+          {
+            name: 'depositAmount0';
+            docs: ['The amount of token_0 was deposit for the liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'depositAmount1';
+            docs: ['The amount of token_1 was deposit for the liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'depositAmount0TransferFee';
+            docs: ['The token transfer fee for deposit_amount_0'];
+            type: 'u64';
+          },
+          {
+            name: 'depositAmount1TransferFee';
+            docs: ['The token transfer fee for deposit_amount_1'];
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'decreaseLiquidityEvent';
+      docs: ['Emitted when liquidity is decreased.'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'positionNftMint';
+            docs: ['The ID of the token for which liquidity was decreased'];
+            type: 'pubkey';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The amount by which liquidity for the position was decreased'];
+            type: 'u128';
+          },
+          {
+            name: 'decreaseAmount0';
+            docs: ['The amount of token_0 that was paid for the decrease in liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'decreaseAmount1';
+            docs: ['The amount of token_1 that was paid for the decrease in liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'feeAmount0';
+            type: 'u64';
+          },
+          {
+            name: 'feeAmount1';
+            docs: ['The amount of token_1 fee'];
+            type: 'u64';
+          },
+          {
+            name: 'rewardAmounts';
+            docs: ['The amount of rewards'];
+            type: {
+              array: ['u64', 3];
+            };
+          },
+          {
+            name: 'transferFee0';
+            docs: ['The amount of token_0 transfer fee'];
+            type: 'u64';
+          },
+          {
+            name: 'transferFee1';
+            docs: ['The amount of token_1 transfer fee'];
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'increaseLiquidityEvent';
+      docs: ['Emitted when liquidity is increased.'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'positionNftMint';
+            docs: ['The ID of the token for which liquidity was increased'];
+            type: 'pubkey';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The amount by which liquidity for the NFT position was increased'];
+            type: 'u128';
+          },
+          {
+            name: 'amount0';
+            docs: ['The amount of token_0 that was paid for the increase in liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'amount1';
+            docs: ['The amount of token_1 that was paid for the increase in liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'amount0TransferFee';
+            docs: ['The token transfer fee for amount_0'];
+            type: 'u64';
+          },
+          {
+            name: 'amount1TransferFee';
+            docs: ['The token transfer fee for amount_1'];
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'initializeRewardParam';
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'openTime';
+            docs: ['Reward open time'];
+            type: 'u64';
+          },
+          {
+            name: 'endTime';
+            docs: ['Reward end time'];
+            type: 'u64';
+          },
+          {
+            name: 'emissionsPerSecondX64';
+            docs: ['Token reward per second are earned per unit of liquidity'];
+            type: 'u128';
+          }
+        ];
+      };
+    },
+    {
+      name: 'liquidityCalculateEvent';
+      docs: ['Emitted when liquidity decreased or increase.'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolLiquidity';
+            docs: ['The pool liquidity before decrease or increase'];
+            type: 'u128';
+          },
+          {
+            name: 'poolSqrtPriceX64';
+            docs: ['The pool price when decrease or increase in liquidity'];
+            type: 'u128';
+          },
+          {
+            name: 'poolTick';
+            docs: ['The pool tick when decrease or increase in liquidity'];
+            type: 'i32';
+          },
+          {
+            name: 'calcAmount0';
+            docs: ['The amount of token_0 that was calculated for the decrease or increase in liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'calcAmount1';
+            docs: ['The amount of token_1 that was calculated for the decrease or increase in liquidity'];
+            type: 'u64';
+          },
+          {
+            name: 'tradeFeeOwed0';
+            type: 'u64';
+          },
+          {
+            name: 'tradeFeeOwed1';
+            docs: ['The amount of token_1 fee'];
+            type: 'u64';
+          },
+          {
+            name: 'transferFee0';
+            docs: ['The amount of token_0 transfer fee without trade_fee_amount_0'];
+            type: 'u64';
+          },
+          {
+            name: 'transferFee1';
+            docs: ['The amount of token_1 transfer fee without trade_fee_amount_0'];
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'liquidityChangeEvent';
+      docs: ['Emitted pool liquidity change when increase and decrease liquidity'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolState';
+            docs: ['The pool for swap'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tick';
+            docs: ['The tick of the pool'];
+            type: 'i32';
+          },
+          {
+            name: 'tickLower';
+            docs: ['The tick lower of position'];
+            type: 'i32';
+          },
+          {
+            name: 'tickUpper';
+            docs: ['The tick lower of position'];
+            type: 'i32';
+          },
+          {
+            name: 'liquidityBefore';
+            docs: ['The liquidity of the pool before liquidity change'];
+            type: 'u128';
+          },
+          {
+            name: 'liquidityAfter';
+            docs: ['The liquidity of the pool after liquidity change'];
+            type: 'u128';
+          }
+        ];
+      };
+    },
+    {
+      name: 'observation';
+      docs: ['The element of observations in ObservationState'];
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'blockTimestamp';
+            docs: ['The block timestamp of the observation'];
+            type: 'u32';
+          },
+          {
+            name: 'tickCumulative';
+            docs: ['the cumulative of tick during the duration time'];
+            type: 'i64';
+          },
+          {
+            name: 'padding';
+            docs: ['padding for feature update'];
+            type: {
+              array: ['u64', 4];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'observationState';
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'initialized';
+            docs: ['Whether the ObservationState is initialized'];
+            type: 'bool';
+          },
+          {
+            name: 'recentEpoch';
+            docs: ['recent update epoch'];
+            type: 'u64';
+          },
+          {
+            name: 'observationIndex';
+            docs: ['the most-recently updated index of the observations array'];
+            type: 'u16';
+          },
+          {
+            name: 'poolId';
+            docs: ['belongs to which pool'];
+            type: 'pubkey';
+          },
+          {
+            name: 'observations';
+            docs: ['observation array'];
+            type: {
+              array: [
+                {
+                  defined: {
+                    name: 'observation';
+                  };
+                },
+                100
+              ];
+            };
+          },
+          {
+            name: 'padding';
+            docs: ['padding for feature update'];
+            type: {
+              array: ['u64', 4];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'operationState';
+      docs: ['Holds the current owner of the factory'];
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'bump';
+            docs: ['Bump to identify PDA'];
+            type: 'u8';
+          },
+          {
+            name: 'operationOwners';
+            docs: ['Address of the operation owner'];
+            type: {
+              array: ['pubkey', 10];
+            };
+          },
+          {
+            name: 'whitelistMints';
+            docs: ['The mint address of whitelist to emit reward'];
+            type: {
+              array: ['pubkey', 100];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'personalPositionState';
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'bump';
+            docs: ['Bump to identify PDA'];
+            type: {
+              array: ['u8', 1];
+            };
+          },
+          {
+            name: 'nftMint';
+            docs: ['Mint address of the tokenized position'];
+            type: 'pubkey';
+          },
+          {
+            name: 'poolId';
+            docs: ['The ID of the pool with which this token is connected'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tickLowerIndex';
+            docs: ['The lower bound tick of the position'];
+            type: 'i32';
+          },
+          {
+            name: 'tickUpperIndex';
+            docs: ['The upper bound tick of the position'];
+            type: 'i32';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The amount of liquidity owned by this position'];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthInside0LastX64';
+            docs: ['The token_0 fee growth of the aggregate position as of the last action on the individual position'];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthInside1LastX64';
+            docs: ['The token_1 fee growth of the aggregate position as of the last action on the individual position'];
+            type: 'u128';
+          },
+          {
+            name: 'tokenFeesOwed0';
+            docs: ['The fees owed to the position owner in token_0, as of the last computation'];
+            type: 'u64';
+          },
+          {
+            name: 'tokenFeesOwed1';
+            docs: ['The fees owed to the position owner in token_1, as of the last computation'];
+            type: 'u64';
+          },
+          {
+            name: 'rewardInfos';
+            type: {
+              array: [
+                {
+                  defined: {
+                    name: 'positionRewardInfo';
+                  };
+                },
+                3
+              ];
+            };
+          },
+          {
+            name: 'recentEpoch';
+            type: 'u64';
+          },
+          {
+            name: 'padding';
+            type: {
+              array: ['u64', 7];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'poolCreatedEvent';
+      docs: ['Emitted when a pool is created and initialized with a starting price', ''];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'tokenMint0';
+            docs: ['The first token of the pool by address sort order'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenMint1';
+            docs: ['The second token of the pool by address sort order'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tickSpacing';
+            docs: ['The minimum number of ticks between initialized ticks'];
+            type: 'u16';
+          },
+          {
+            name: 'poolState';
+            docs: ['The address of the created pool'];
+            type: 'pubkey';
+          },
+          {
+            name: 'sqrtPriceX64';
+            docs: ['The initial sqrt price of the pool, as a Q64.64'];
+            type: 'u128';
+          },
+          {
+            name: 'tick';
+            docs: ['The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool'];
+            type: 'i32';
+          },
+          {
+            name: 'tokenVault0';
+            docs: ['Vault of token_0'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenVault1';
+            docs: ['Vault of token_1'];
+            type: 'pubkey';
+          }
+        ];
+      };
+    },
+    {
+      name: 'poolState';
+      docs: ['The pool state', '', 'PDA of `[POOL_SEED, config, token_mint_0, token_mint_1]`', ''];
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'bump';
+            docs: ['Bump to identify PDA'];
+            type: {
+              array: ['u8', 1];
+            };
+          },
+          {
+            name: 'ammConfig';
+            type: 'pubkey';
+          },
+          {
+            name: 'owner';
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenMint0';
+            docs: ['Token pair of the pool, where token_mint_0 address < token_mint_1 address'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenMint1';
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenVault0';
+            docs: ['Token pair vault'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenVault1';
+            type: 'pubkey';
+          },
+          {
+            name: 'observationKey';
+            docs: ['observation account key'];
+            type: 'pubkey';
+          },
+          {
+            name: 'mintDecimals0';
+            docs: ['mint0 and mint1 decimals'];
+            type: 'u8';
+          },
+          {
+            name: 'mintDecimals1';
+            type: 'u8';
+          },
+          {
+            name: 'tickSpacing';
+            docs: ['The minimum number of ticks between initialized ticks'];
+            type: 'u16';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The currently in range liquidity available to the pool.'];
+            type: 'u128';
+          },
+          {
+            name: 'sqrtPriceX64';
+            docs: ['The current price of the pool as a sqrt(token_1/token_0) Q64.64 value'];
+            type: 'u128';
+          },
+          {
+            name: 'tickCurrent';
+            docs: ['The current tick of the pool, i.e. according to the last tick transition that was run.'];
+            type: 'i32';
+          },
+          {
+            name: 'padding3';
+            type: 'u16';
+          },
+          {
+            name: 'padding4';
+            type: 'u16';
+          },
+          {
+            name: 'feeGrowthGlobal0X64';
+            docs: [
+              'The fee growth as a Q64.64 number, i.e. fees of token_0 and token_1 collected per',
+              'unit of liquidity for the entire life of the pool.'
+            ];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthGlobal1X64';
+            type: 'u128';
+          },
+          {
+            name: 'protocolFeesToken0';
+            docs: ['The amounts of token_0 and token_1 that are owed to the protocol.'];
+            type: 'u64';
+          },
+          {
+            name: 'protocolFeesToken1';
+            type: 'u64';
+          },
+          {
+            name: 'swapInAmountToken0';
+            docs: ['The amounts in and out of swap token_0 and token_1'];
+            type: 'u128';
+          },
+          {
+            name: 'swapOutAmountToken1';
+            type: 'u128';
+          },
+          {
+            name: 'swapInAmountToken1';
+            type: 'u128';
+          },
+          {
+            name: 'swapOutAmountToken0';
+            type: 'u128';
+          },
+          {
+            name: 'status';
+            docs: [
+              'Bitwise representation of the state of the pool',
+              'bit0, 1: disable open position and increase liquidity, 0: normal',
+              'bit1, 1: disable decrease liquidity, 0: normal',
+              'bit2, 1: disable collect fee, 0: normal',
+              'bit3, 1: disable collect reward, 0: normal',
+              'bit4, 1: disable swap, 0: normal'
+            ];
+            type: 'u8';
+          },
+          {
+            name: 'padding';
+            docs: ['Leave blank for future use'];
+            type: {
+              array: ['u8', 7];
+            };
+          },
+          {
+            name: 'rewardInfos';
+            type: {
+              array: [
+                {
+                  defined: {
+                    name: 'rewardInfo';
+                  };
+                },
+                3
+              ];
+            };
+          },
+          {
+            name: 'tickArrayBitmap';
+            docs: ['Packed initialized tick array state'];
+            type: {
+              array: ['u64', 16];
+            };
+          },
+          {
+            name: 'totalFeesToken0';
+            docs: ['except protocol_fee and fund_fee'];
+            type: 'u64';
+          },
+          {
+            name: 'totalFeesClaimedToken0';
+            docs: ['except protocol_fee and fund_fee'];
+            type: 'u64';
+          },
+          {
+            name: 'totalFeesToken1';
+            type: 'u64';
+          },
+          {
+            name: 'totalFeesClaimedToken1';
+            type: 'u64';
+          },
+          {
+            name: 'fundFeesToken0';
+            type: 'u64';
+          },
+          {
+            name: 'fundFeesToken1';
+            type: 'u64';
+          },
+          {
+            name: 'openTime';
+            type: 'u64';
+          },
+          {
+            name: 'recentEpoch';
+            type: 'u64';
+          },
+          {
+            name: 'padding1';
+            type: {
+              array: ['u64', 24];
+            };
+          },
+          {
+            name: 'padding2';
+            type: {
+              array: ['u64', 32];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'positionRewardInfo';
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'growthInsideLastX64';
+            type: 'u128';
+          },
+          {
+            name: 'rewardAmountOwed';
+            type: 'u64';
+          }
+        ];
+      };
+    },
+    {
+      name: 'protocolPositionState';
+      docs: ["Info stored for each user's position"];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'bump';
+            docs: ['Bump to identify PDA'];
+            type: 'u8';
+          },
+          {
+            name: 'poolId';
+            docs: ['The ID of the pool with which this token is connected'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tickLowerIndex';
+            docs: ['The lower bound tick of the position'];
+            type: 'i32';
+          },
+          {
+            name: 'tickUpperIndex';
+            docs: ['The upper bound tick of the position'];
+            type: 'i32';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The amount of liquidity owned by this position'];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthInside0LastX64';
+            docs: ['The token_0 fee growth per unit of liquidity as of the last update to liquidity or fees owed'];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthInside1LastX64';
+            docs: ['The token_1 fee growth per unit of liquidity as of the last update to liquidity or fees owed'];
+            type: 'u128';
+          },
+          {
+            name: 'tokenFeesOwed0';
+            docs: ['The fees owed to the position owner in token_0'];
+            type: 'u64';
+          },
+          {
+            name: 'tokenFeesOwed1';
+            docs: ['The fees owed to the position owner in token_1'];
+            type: 'u64';
+          },
+          {
+            name: 'rewardGrowthInside';
+            docs: ['The reward growth per unit of liquidity as of the last update to liquidity'];
+            type: {
+              array: ['u128', 3];
+            };
+          },
+          {
+            name: 'recentEpoch';
+            type: 'u64';
+          },
+          {
+            name: 'padding';
+            type: {
+              array: ['u64', 7];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'rewardInfo';
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'rewardState';
+            docs: ['Reward state'];
+            type: 'u8';
+          },
+          {
+            name: 'openTime';
+            docs: ['Reward open time'];
+            type: 'u64';
+          },
+          {
+            name: 'endTime';
+            docs: ['Reward end time'];
+            type: 'u64';
+          },
+          {
+            name: 'lastUpdateTime';
+            docs: ['Reward last update time'];
+            type: 'u64';
+          },
+          {
+            name: 'emissionsPerSecondX64';
+            docs: ['Q64.64 number indicates how many tokens per second are earned per unit of liquidity.'];
+            type: 'u128';
+          },
+          {
+            name: 'rewardTotalEmissioned';
+            docs: ['The total amount of reward emissioned'];
+            type: 'u64';
+          },
+          {
+            name: 'rewardClaimed';
+            docs: ['The total amount of claimed reward'];
+            type: 'u64';
+          },
+          {
+            name: 'tokenMint';
+            docs: ['Reward token mint.'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenVault';
+            docs: ['Reward vault token account.'];
+            type: 'pubkey';
+          },
+          {
+            name: 'authority';
+            docs: ['The owner that has permission to set reward param'];
+            type: 'pubkey';
+          },
+          {
+            name: 'rewardGrowthGlobalX64';
+            docs: [
+              'Q64.64 number that tracks the total tokens earned per unit of liquidity since the reward',
+              'emissions were turned on.'
+            ];
+            type: 'u128';
+          }
+        ];
+      };
+    },
+    {
+      name: 'supportMintAssociated';
+      docs: ['Holds the current owner of the factory'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'bump';
+            docs: ['Bump to identify PDA'];
+            type: 'u8';
+          },
+          {
+            name: 'mint';
+            docs: ['Address of the supported token22 mint'];
+            type: 'pubkey';
+          },
+          {
+            name: 'padding';
+            type: {
+              array: ['u64', 8];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'swapEvent';
+      docs: ['Emitted by when a swap is performed for a pool'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolState';
+            docs: ['The pool for which token_0 and token_1 were swapped'];
+            type: 'pubkey';
+          },
+          {
+            name: 'sender';
+            docs: ['The address that initiated the swap call, and that received the callback'];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenAccount0';
+            docs: [
+              'The payer token account in zero for one swaps, or the recipient token account',
+              'in one for zero swaps'
+            ];
+            type: 'pubkey';
+          },
+          {
+            name: 'tokenAccount1';
+            docs: [
+              'The payer token account in one for zero swaps, or the recipient token account',
+              'in zero for one swaps'
+            ];
+            type: 'pubkey';
+          },
+          {
+            name: 'amount0';
+            docs: ['The real delta amount of the token_0 of the pool or user'];
+            type: 'u64';
+          },
+          {
+            name: 'transferFee0';
+            docs: ['The transfer fee charged by the withheld_amount of the token_0'];
+            type: 'u64';
+          },
+          {
+            name: 'amount1';
+            docs: ['The real delta of the token_1 of the pool or user'];
+            type: 'u64';
+          },
+          {
+            name: 'transferFee1';
+            docs: ['The transfer fee charged by the withheld_amount of the token_1'];
+            type: 'u64';
+          },
+          {
+            name: 'zeroForOne';
+            docs: ['if true, amount_0 is negtive and amount_1 is positive'];
+            type: 'bool';
+          },
+          {
+            name: 'sqrtPriceX64';
+            docs: ['The sqrt(price) of the pool after the swap, as a Q64.64'];
+            type: 'u128';
+          },
+          {
+            name: 'liquidity';
+            docs: ['The liquidity of the pool after the swap'];
+            type: 'u128';
+          },
+          {
+            name: 'tick';
+            docs: ['The log base 1.0001 of price of the pool after the swap'];
+            type: 'i32';
+          }
+        ];
+      };
+    },
+    {
+      name: 'tickArrayBitmapExtension';
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolId';
+            type: 'pubkey';
+          },
+          {
+            name: 'positiveTickArrayBitmap';
+            docs: ['Packed initialized tick array state for start_tick_index is positive'];
+            type: {
+              array: [
+                {
+                  array: ['u64', 8];
+                },
+                14
+              ];
+            };
+          },
+          {
+            name: 'negativeTickArrayBitmap';
+            docs: ['Packed initialized tick array state for start_tick_index is negitive'];
+            type: {
+              array: [
+                {
+                  array: ['u64', 8];
+                },
+                14
+              ];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'tickArrayState';
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'poolId';
+            type: 'pubkey';
+          },
+          {
+            name: 'startTickIndex';
+            type: 'i32';
+          },
+          {
+            name: 'ticks';
+            type: {
+              array: [
+                {
+                  defined: {
+                    name: 'tickState';
+                  };
+                },
+                60
+              ];
+            };
+          },
+          {
+            name: 'initializedTickCount';
+            type: 'u8';
+          },
+          {
+            name: 'recentEpoch';
+            type: 'u64';
+          },
+          {
+            name: 'padding';
+            type: {
+              array: ['u8', 107];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'tickState';
+      serialization: 'bytemuckunsafe';
+      repr: {
+        kind: 'c';
+        packed: true;
+      };
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'tick';
+            type: 'i32';
+          },
+          {
+            name: 'liquidityNet';
+            docs: [
+              'Amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left)'
+            ];
+            type: 'i128';
+          },
+          {
+            name: 'liquidityGross';
+            docs: ['The total position liquidity that references this tick'];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthOutside0X64';
+            docs: [
+              'Fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)',
+              'only has relative meaning, not absolute — the value depends on when the tick is initialized'
+            ];
+            type: 'u128';
+          },
+          {
+            name: 'feeGrowthOutside1X64';
+            type: 'u128';
+          },
+          {
+            name: 'rewardGrowthsOutsideX64';
+            type: {
+              array: ['u128', 3];
+            };
+          },
+          {
+            name: 'padding';
+            type: {
+              array: ['u32', 13];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: 'updateRewardInfosEvent';
+      docs: ['Emitted when Reward are updated for a pool'];
+      type: {
+        kind: 'struct';
+        fields: [
+          {
+            name: 'rewardGrowthGlobalX64';
+            docs: ['Reward info'];
+            type: {
+              array: ['u128', 3];
+            };
+          }
+        ];
+      };
+    }
+  ];
+};

+ 79 - 0
src/lib/clmm-sdk/src/instructions/utils/binaryUtils.ts

@@ -0,0 +1,79 @@
+import BN from 'bn.js';
+
+// Convert 16-bit unsigned integer to byte array
+export function u16ToBytes(num: number): Uint8Array {
+  const arr = new ArrayBuffer(2);
+  const view = new DataView(arr);
+  view.setUint16(0, num, false);
+  return new Uint8Array(arr);
+}
+
+// Convert 16-bit integer to byte array
+export function i16ToBytes(num: number): Uint8Array {
+  const arr = new ArrayBuffer(2);
+  const view = new DataView(arr);
+  view.setInt16(0, num, false);
+  return new Uint8Array(arr);
+}
+
+// Convert 32-bit unsigned integer to byte array
+export function u32ToBytes(num: number): Uint8Array {
+  const arr = new ArrayBuffer(4);
+  const view = new DataView(arr);
+  view.setUint32(0, num, false);
+  return new Uint8Array(arr);
+}
+
+// Convert 32-bit integer to byte array
+export function i32ToBytes(num: number): Uint8Array {
+  const arr = new ArrayBuffer(4);
+  const view = new DataView(arr);
+  view.setInt32(0, num, false);
+  return new Uint8Array(arr);
+}
+
+// Find the position of the highest bit '1' in the bitmap
+export function leadingZeros(bitNum: number, data: BN): number {
+  let i = 0;
+  for (let j = bitNum - 1; j >= 0; j--) {
+    if (!data.testn(j)) {
+      i++;
+    } else {
+      break;
+    }
+  }
+  return i;
+}
+
+// Find the position of the lowest '0' in the bitmap
+export function trailingZeros(bitNum: number, data: BN): number {
+  let i = 0;
+  for (let j = 0; j < bitNum; j++) {
+    if (!data.testn(j)) {
+      i++;
+    } else {
+      break;
+    }
+  }
+  return i;
+}
+
+// Check if the bitmap is empty
+export function isZero(bitNum: number, data: BN): boolean {
+  for (let i = 0; i < bitNum; i++) {
+    if (data.testn(i)) return false;
+  }
+  return true;
+}
+
+// Find the position of the highest bit '1' in the bitmap
+export function mostSignificantBit(bitNum: number, data: BN): number | null {
+  if (isZero(bitNum, data)) return null;
+  else return leadingZeros(bitNum, data);
+}
+
+// Find the position of the lowest bit '1' in the bitmap
+export function leastSignificantBit(bitNum: number, data: BN): number | null {
+  if (isZero(bitNum, data)) return null;
+  else return trailingZeros(bitNum, data);
+}

+ 111 - 0
src/lib/clmm-sdk/src/instructions/utils/fetchWalletTokenAccounts.ts

@@ -0,0 +1,111 @@
+import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token';
+import {
+  AccountInfo,
+  Commitment,
+  Connection,
+  GetProgramAccountsResponse,
+  PublicKey,
+  RpcResponseAndContext,
+} from '@solana/web3.js';
+import BN from 'bn.js';
+
+import { SPLTokenAccountLayout } from '../layout.js';
+import { GetStructureSchema } from '../libs/marshmallow/index.js';
+import { getATAAddress } from '../pda.js';
+
+export interface ParseTokenAccount {
+  owner: PublicKey;
+  solAccountResp?: AccountInfo<Buffer> | null;
+  tokenAccountResp: RpcResponseAndContext<GetProgramAccountsResponse>;
+}
+
+export type ISPLTokenAccount = GetStructureSchema<typeof SPLTokenAccountLayout>;
+
+export interface TokenAccountRaw {
+  programId: PublicKey;
+  pubkey: PublicKey;
+  accountInfo: ISPLTokenAccount;
+}
+
+export interface TokenAccount {
+  publicKey?: PublicKey;
+  mint: PublicKey;
+  isAssociated?: boolean;
+  amount: BN;
+  isNative: boolean;
+  programId: PublicKey;
+}
+
+/**
+ * Get all token account information in the wallet (pure function version)
+ *
+ * This function will simultaneously query user's SOL account, standard SPL token accounts, and Token-2022 accounts.
+ *
+ * @param connection - Solana connection instance
+ * @param ownerPubKey - Public key of the wallet owner
+ * @param commitment - Optional commitment level
+ * @returns Promise containing token accounts and raw account information
+ */
+export async function fetchWalletTokenAccounts(
+  connection: Connection,
+  ownerPubKey: PublicKey,
+  commitment?: Commitment
+): Promise<{
+  tokenAccounts: TokenAccount[];
+  rawTokenAccountInfos: TokenAccountRaw[];
+}> {
+  // Request all account information in parallel
+  const [solAccountResp, ownerTokenAccountResp, ownerToken2022AccountResp] = await Promise.all([
+    connection.getAccountInfo(ownerPubKey, commitment),
+    connection.getTokenAccountsByOwner(ownerPubKey, { programId: TOKEN_PROGRAM_ID }, commitment),
+    connection.getTokenAccountsByOwner(ownerPubKey, { programId: TOKEN_2022_PROGRAM_ID }, commitment),
+  ]);
+
+  // Parse and merge all token account information
+  const { tokenAccounts, rawTokenAccountInfos } = parseTokenAccountResp({
+    owner: ownerPubKey,
+    solAccountResp,
+    tokenAccountResp: {
+      context: ownerTokenAccountResp.context,
+      value: [...ownerTokenAccountResp.value, ...ownerToken2022AccountResp.value],
+    },
+  });
+
+  return { tokenAccounts, rawTokenAccountInfos };
+}
+
+export function parseTokenAccountResp({ owner, solAccountResp, tokenAccountResp }: ParseTokenAccount): {
+  tokenAccounts: TokenAccount[];
+  rawTokenAccountInfos: TokenAccountRaw[];
+} {
+  const tokenAccounts: TokenAccount[] = [];
+  const rawTokenAccountInfos: TokenAccountRaw[] = [];
+
+  for (const { pubkey, account } of tokenAccountResp.value) {
+    const accountInfo = SPLTokenAccountLayout.decode(account.data);
+    const { mint, amount } = accountInfo;
+    tokenAccounts.push({
+      publicKey: pubkey,
+      mint,
+      amount,
+      isAssociated: getATAAddress(owner, mint, account.owner).publicKey.equals(pubkey),
+      isNative: false,
+      programId: account.owner,
+    });
+    rawTokenAccountInfos.push({ pubkey, accountInfo, programId: account.owner });
+  }
+
+  if (solAccountResp) {
+    tokenAccounts.push({
+      mint: PublicKey.default,
+      amount: new BN(String(solAccountResp.lamports)),
+      isNative: true,
+      programId: solAccountResp.owner,
+    });
+  }
+
+  return {
+    tokenAccounts,
+    rawTokenAccountInfos,
+  };
+}

+ 29 - 0
src/lib/clmm-sdk/src/instructions/utils/getTickArrayBitmapExtension.ts

@@ -0,0 +1,29 @@
+import { Connection, PublicKey } from '@solana/web3.js';
+
+import { TickArrayBitmapExtensionLayout } from '../layout.js';
+import { getPdaExBitmapAccount } from '../pda.js';
+
+import { TickArrayBitmapExtensionType } from './models';
+
+/**
+ * @description Get the tick array bitmap extension
+ *
+ */
+export async function getTickArrayBitmapExtension(
+  programId: PublicKey,
+  poolId: PublicKey,
+  connection: Connection
+): Promise<TickArrayBitmapExtensionType> {
+  const exBitmapAddress = getPdaExBitmapAccount(programId, new PublicKey(poolId)).publicKey;
+  const exBitmapInfo = await connection.getAccountInfo(exBitmapAddress);
+  if (!exBitmapInfo) {
+    throw new Error('exBitmapInfo not found');
+  }
+  const exBitmapInfoData = TickArrayBitmapExtensionLayout.decode(exBitmapInfo.data);
+  return {
+    poolId: new PublicKey(poolId),
+    exBitmapAddress,
+    positiveTickArrayBitmap: exBitmapInfoData.positiveTickArrayBitmap,
+    negativeTickArrayBitmap: exBitmapInfoData.negativeTickArrayBitmap,
+  };
+}

+ 106 - 0
src/lib/clmm-sdk/src/instructions/utils/getTickArrayInfo.ts

@@ -0,0 +1,106 @@
+import { Connection, PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+
+import { TickArrayLayout } from '../layout.js';
+import { IPoolLayoutWithId } from '../models.js';
+import { TickArrayBitmapExtensionType } from './models.js';
+import { getPdaTickArrayAddress } from '../pda.js';
+
+import { TickArrayContainer } from './models';
+import { TickUtils } from './tick.js';
+import { TickArrayUtils } from './tickArrayUtils.js';
+
+/**
+ * Get tick array cache
+ * Now returns TickArrayContainer which supports both fixed and dynamic tick arrays
+ */
+export async function getTickArrayInfo({
+  connection,
+  poolInfo,
+  exBitmapInfo,
+  expectedCount = 7, // Default get 7 tick arrays
+}: {
+  connection: Connection;
+  poolInfo: IPoolLayoutWithId;
+  exBitmapInfo: TickArrayBitmapExtensionType;
+  expectedCount?: number;
+}): Promise<{ [key: string]: TickArrayContainer }> {
+  poolInfo;
+  // 1. Calculate the starting index of the tick array where the current tick is located
+  const currentTickArrayStartIndex = TickUtils.getTickArrayStartIndexByTick(poolInfo.tickCurrent, poolInfo.tickSpacing);
+
+  // 2. Get the list of tick array start indices that need to be loaded
+  const startIndexArray = TickUtils.getInitializedTickArrayInRange(
+    poolInfo.tickArrayBitmap,
+    exBitmapInfo,
+    poolInfo.tickSpacing,
+    currentTickArrayStartIndex,
+    expectedCount
+  );
+
+  // 3. Calculate PDA addresses for all tick arrays
+  const tickArrayAddresses: PublicKey[] = [];
+  const startIndexToAddress: { [key: number]: PublicKey } = {};
+
+  for (const startIndex of startIndexArray) {
+    const { publicKey: tickArrayAddress } = getPdaTickArrayAddress(poolInfo.programId, poolInfo.poolId, startIndex);
+    tickArrayAddresses.push(tickArrayAddress);
+    startIndexToAddress[startIndex] = tickArrayAddress;
+  }
+
+  // 4. Batch fetch on-chain account data
+  const accountInfos = await connection.getMultipleAccountsInfo(tickArrayAddresses);
+
+  // 5. Decode and build cache (now supports both fixed and dynamic tick arrays)
+  const tickArrayCache: { [key: string]: TickArrayContainer } = {};
+
+  for (let i = 0; i < accountInfos.length; i++) {
+    const accountInfo = accountInfos[i];
+    const startIndex = startIndexArray[i];
+
+    if (!accountInfo || !accountInfo.data) {
+      console.warn(`Tick array at index ${startIndex} not found`);
+      continue;
+    }
+
+    try {
+      // Identify tick array type
+      const tickArrayType = TickArrayUtils.identifyTickArrayType(accountInfo.data);
+
+      if (tickArrayType === 'Fixed') {
+        // Fixed tick array: use TickArrayLayout
+        const decoded = TickArrayLayout.decode(accountInfo.data);
+
+        tickArrayCache[startIndex.toString()] = {
+          type: 'Fixed',
+          data: {
+            address: tickArrayAddresses[i],
+            poolId: decoded.poolId,
+            startTickIndex: decoded.startTickIndex,
+            ticks: decoded.ticks.map((tick: any) => ({
+              tick: tick.tick,
+              liquidityNet: new BN(tick.liquidityNet.toString()),
+              liquidityGross: new BN(tick.liquidityGross.toString()),
+              feeGrowthOutsideX64A: new BN(tick.feeGrowthOutsideX64A.toString()),
+              feeGrowthOutsideX64B: new BN(tick.feeGrowthOutsideX64B.toString()),
+              rewardGrowthsOutsideX64: tick.rewardGrowthsOutsideX64.map((r: any) => new BN(r.toString())),
+            })),
+            initializedTickCount: decoded.initializedTickCount,
+          },
+        };
+      } else {
+        // Dynamic tick array: use decodeDynTickArray
+        const decoded = TickArrayUtils.decodeDynTickArray(accountInfo.data, tickArrayAddresses[i]);
+
+        tickArrayCache[startIndex.toString()] = {
+          type: 'Dynamic',
+          data: decoded,
+        };
+      }
+    } catch (error) {
+      console.error(`Failed to decode tick array at index ${startIndex}:`, error);
+    }
+  }
+
+  return tickArrayCache;
+}

+ 28 - 0
src/lib/clmm-sdk/src/instructions/utils/index.ts

@@ -0,0 +1,28 @@
+import { PublicKey } from '@solana/web3.js';
+
+export function findProgramAddress(
+  seeds: Array<Buffer | Uint8Array>,
+  programId: PublicKey
+): {
+  publicKey: PublicKey;
+  nonce: number;
+} {
+  const [publicKey, nonce] = PublicKey.findProgramAddressSync(seeds, programId);
+  return { publicKey, nonce };
+}
+
+export * from './binaryUtils.js';
+export * from './liquidityMath.js';
+export * from './mathUtils.js';
+export * from './poolUtils.js';
+export * from './sqrtPriceMath.js';
+export * from './tickMath.js';
+export * from './tick.js';
+export * from './tickarrayBitmap.js';
+export * from './transfer.js';
+export * from './position.js';
+export * from './getTickArrayInfo.js';
+export * from './swapMath.js';
+export * from './getTickArrayBitmapExtension.js';
+export * from './tickArrayUtils.js';
+export * from './models.js';

+ 617 - 0
src/lib/clmm-sdk/src/instructions/utils/liquidityMath.test.ts

@@ -0,0 +1,617 @@
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+import { describe, test, expect } from 'vitest';
+
+import { LiquidityMath } from './liquidityMath.js';
+import { SqrtPriceMath } from './sqrtPriceMath.js';
+
+describe('LiquidityMath.getLiquidityFromTokenAmounts', () => {
+  test('current price is below the lower limit - only consider tokenA', () => {
+    // price: current < A < B
+    const sqrtPriceCurrentX64 = new BN('100000');
+    const sqrtPriceX64A = new BN('150000');
+    const sqrtPriceX64B = new BN('300000');
+    const amountA = new BN('1000000');
+    const amountB = new BN('500000');
+
+    // manually calculate the expected result
+    const expectedLiquidity = LiquidityMath.getLiquidityFromTokenAmountA(sqrtPriceX64A, sqrtPriceX64B, amountA, false);
+
+    const liquidity = LiquidityMath.getLiquidityFromTokenAmounts(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      amountA,
+      amountB
+    );
+
+    // verify the result
+    expect(liquidity.toString()).toBe(expectedLiquidity.toString());
+  });
+
+  test('current price is in the range - take the minimum of the two liquiditys', () => {
+    // price: A < current < B
+    const sqrtPriceX64A = new BN('100000');
+    const sqrtPriceCurrentX64 = new BN('200000');
+    const sqrtPriceX64B = new BN('300000');
+    const amountA = new BN('1000000');
+    const amountB = new BN('500000');
+
+    // calculate the two liquiditys
+    const liquidityA = LiquidityMath.getLiquidityFromTokenAmountA(sqrtPriceCurrentX64, sqrtPriceX64B, amountA, false);
+
+    const liquidityB = LiquidityMath.getLiquidityFromTokenAmountB(sqrtPriceX64A, sqrtPriceCurrentX64, amountB);
+
+    // the expected result is the minimum of the two liquiditys
+    const expectedLiquidity = BN.min(liquidityA, liquidityB);
+
+    const liquidity = LiquidityMath.getLiquidityFromTokenAmounts(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      amountA,
+      amountB
+    );
+
+    // verify the result
+    expect(liquidity.toString()).toBe(expectedLiquidity.toString());
+  });
+
+  test('current price is above the upper limit - only consider tokenB', () => {
+    // price: A < B < current
+    const sqrtPriceX64A = new BN('100000');
+    const sqrtPriceX64B = new BN('200000');
+    const sqrtPriceCurrentX64 = new BN('300000');
+    const amountA = new BN('1000000');
+    const amountB = new BN('500000');
+
+    // manually calculate the expected result
+    const expectedLiquidity = LiquidityMath.getLiquidityFromTokenAmountB(sqrtPriceX64A, sqrtPriceX64B, amountB);
+
+    const liquidity = LiquidityMath.getLiquidityFromTokenAmounts(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      amountA,
+      amountB
+    );
+
+    // verify the result
+    expect(liquidity.toString()).toBe(expectedLiquidity.toString());
+  });
+
+  test('price range order adjustment - ensure A is always less than B', () => {
+    // test the case of reversed price order: B < A
+    const sqrtPriceCurrentX64 = new BN('200000');
+    const sqrtPriceX64B = new BN('100000'); // deliberately reversed
+    const sqrtPriceX64A = new BN('300000'); // deliberately reversed
+    const amountA = new BN('1000000');
+    const amountB = new BN('500000');
+
+    // call in the correct order
+    const expectedLiquidity = LiquidityMath.getLiquidityFromTokenAmounts(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      amountA,
+      amountB
+    );
+
+    // call in the reversed order
+    const liquidity = LiquidityMath.getLiquidityFromTokenAmounts(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64B,
+      sqrtPriceX64A,
+      amountA,
+      amountB
+    );
+
+    // verify the result is the same
+    expect(liquidity.toString()).toBe(expectedLiquidity.toString());
+  });
+});
+
+describe('LiquidityMath.getAmountsFromLiquidity', () => {
+  test('current price is below the lower limit - only return tokenA', () => {
+    // price: current < A < B
+    const sqrtPriceCurrentX64 = new BN('100000');
+    const sqrtPriceX64A = new BN('150000');
+    const sqrtPriceX64B = new BN('300000');
+    const liquidity = new BN('10000000');
+    const roundUp = true;
+
+    // manually calculate the expected tokenA quantity
+    const expectedAmountA = LiquidityMath.getTokenAmountAFromLiquidity(
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      liquidity,
+      roundUp
+    );
+
+    const { amountA, amountB } = LiquidityMath.getAmountsFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      liquidity,
+      roundUp
+    );
+
+    // verify the result
+    expect(amountA.toString()).toBe(expectedAmountA.toString());
+    expect(amountB.toString()).toBe('0');
+  });
+
+  test('current price is in the range - return two tokens', () => {
+    // price: A < current < B
+    const sqrtPriceX64A = new BN('100000');
+    const sqrtPriceCurrentX64 = new BN('200000');
+    const sqrtPriceX64B = new BN('300000');
+    const liquidity = new BN('10000000');
+    const roundUp = true;
+
+    // manually calculate the expected quantity
+    const expectedAmountA = LiquidityMath.getTokenAmountAFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64B,
+      liquidity,
+      roundUp
+    );
+
+    const expectedAmountB = LiquidityMath.getTokenAmountBFromLiquidity(
+      sqrtPriceX64A,
+      sqrtPriceCurrentX64,
+      liquidity,
+      roundUp
+    );
+
+    const { amountA, amountB } = LiquidityMath.getAmountsFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      liquidity,
+      roundUp
+    );
+
+    // verify the result
+    expect(amountA.toString()).toBe(expectedAmountA.toString());
+    expect(amountB.toString()).toBe(expectedAmountB.toString());
+  });
+
+  test('current price is above the upper limit - only return tokenB', () => {
+    // price: A < B < current
+    const sqrtPriceX64A = new BN('100000');
+    const sqrtPriceX64B = new BN('200000');
+    const sqrtPriceCurrentX64 = new BN('300000');
+    const liquidity = new BN('10000000');
+    const roundUp = true;
+
+    // manually calculate the expected tokenB quantity
+    const expectedAmountB = LiquidityMath.getTokenAmountBFromLiquidity(
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      liquidity,
+      roundUp
+    );
+
+    const { amountA, amountB } = LiquidityMath.getAmountsFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      liquidity,
+      roundUp
+    );
+
+    // verify the result
+    expect(amountA.toString()).toBe('0');
+    expect(amountB.toString()).toBe(expectedAmountB.toString());
+  });
+
+  test('rounding behavior test - roundUp = true vs false', () => {
+    const sqrtPriceX64A = new BN('100000');
+    const sqrtPriceCurrentX64 = new BN('200000');
+    const sqrtPriceX64B = new BN('300000');
+    const liquidity = new BN('10000000');
+
+    // use roundUp = true
+    const resultRoundUp = LiquidityMath.getAmountsFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      liquidity,
+      true
+    );
+
+    // use roundUp = false
+    const resultRoundDown = LiquidityMath.getAmountsFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      liquidity,
+      false
+    );
+
+    expect(new BN(resultRoundUp.amountA).gte(new BN(resultRoundDown.amountA))).toBeTruthy();
+    expect(new BN(resultRoundUp.amountB).gte(new BN(resultRoundDown.amountB))).toBeTruthy();
+  });
+});
+
+describe('LiquidityMath.getAmountBFromAmountA', () => {
+  test('calculate tokenB from tokenA - normal case', () => {
+    // price: start < current < end
+    const startSqrtPriceX64 = new BN('100000');
+    const currentSqrtPriceX64 = new BN('200000');
+    const endSqrtPriceX64 = new BN('300000');
+    const amountA = new BN('1000000');
+
+    const amountB = LiquidityMath.getAmountBFromAmountA(
+      startSqrtPriceX64,
+      endSqrtPriceX64,
+      currentSqrtPriceX64,
+      amountA
+    );
+
+    // verify the correctness of the calculation process
+    // 1. calculate the liquidity
+    const liquidity = LiquidityMath.getLiquidityFromTokenAmountA(currentSqrtPriceX64, endSqrtPriceX64, amountA, false);
+
+    // 2. calculate the tokenB quantity from the liquidity
+    const expectedAmountB = LiquidityMath.getTokenAmountBFromLiquidity(
+      startSqrtPriceX64,
+      currentSqrtPriceX64,
+      liquidity,
+      true
+    );
+
+    // verify the result
+    expect(amountB.toString()).toBe(expectedAmountB.toString());
+  });
+
+  test('current price is below the lower limit - return 0', () => {
+    // price: current < start < end
+    const startSqrtPriceX64 = new BN('200000');
+    const currentSqrtPriceX64 = new BN('100000'); // below the lower limit
+    const endSqrtPriceX64 = new BN('300000');
+    const amountA = new BN('1000000');
+
+    const amountB = LiquidityMath.getAmountBFromAmountA(
+      startSqrtPriceX64,
+      endSqrtPriceX64,
+      currentSqrtPriceX64,
+      amountA
+    );
+
+    // verify the result is 0
+    expect(amountB.toString()).toBe('0');
+  });
+
+  test('current price is above the upper limit - return 0', () => {
+    // price: start < end < current
+    const startSqrtPriceX64 = new BN('100000');
+    const endSqrtPriceX64 = new BN('200000');
+    const currentSqrtPriceX64 = new BN('300000'); // above the upper limit
+    const amountA = new BN('1000000');
+
+    const amountB = LiquidityMath.getAmountBFromAmountA(
+      startSqrtPriceX64,
+      endSqrtPriceX64,
+      currentSqrtPriceX64,
+      amountA
+    );
+
+    // verify the result is 0
+    expect(amountB.toString()).toBe('0');
+  });
+
+  test('price range order adjustment - ensure start is always less than end', () => {
+    // test the case of reversed price order: end < start
+    const endSqrtPriceX64 = new BN('100000'); // deliberately reversed
+    const currentSqrtPriceX64 = new BN('150000');
+    const startSqrtPriceX64 = new BN('200000'); // deliberately reversed
+    const amountA = new BN('1000000');
+
+    // call in the correct order
+    const expectedAmountB = LiquidityMath.getAmountBFromAmountA(
+      endSqrtPriceX64,
+      startSqrtPriceX64,
+      currentSqrtPriceX64,
+      amountA
+    );
+
+    // call in the reversed order
+    const amountB = LiquidityMath.getAmountBFromAmountA(
+      startSqrtPriceX64,
+      endSqrtPriceX64,
+      currentSqrtPriceX64,
+      amountA
+    );
+
+    // verify the result is the same
+    expect(amountB.toString()).toBe(expectedAmountB.toString());
+  });
+});
+
+describe('LiquidityMath.getAmountAFromAmountB', () => {
+  test('calculate tokenA from tokenB - normal case', () => {
+    // price: start < current < end
+    const startSqrtPriceX64 = new BN('100000');
+    const currentSqrtPriceX64 = new BN('200000');
+    const endSqrtPriceX64 = new BN('300000');
+    const amountB = new BN('500000');
+
+    const amountA = LiquidityMath.getAmountAFromAmountB(
+      startSqrtPriceX64,
+      endSqrtPriceX64,
+      currentSqrtPriceX64,
+      amountB
+    );
+
+    // verify the correctness of the calculation process
+    // 1. calculate the liquidity
+    const liquidity = LiquidityMath.getLiquidityFromTokenAmountB(startSqrtPriceX64, currentSqrtPriceX64, amountB);
+
+    // 2. calculate the tokenA quantity from the liquidity
+    const expectedAmountA = LiquidityMath.getTokenAmountAFromLiquidity(
+      currentSqrtPriceX64,
+      endSqrtPriceX64,
+      liquidity,
+      true
+    );
+
+    // verify the result
+    expect(amountA.toString()).toBe(expectedAmountA.toString());
+  });
+
+  test('current price is below the lower limit - return 0', () => {
+    // price: current < start < end
+    const startSqrtPriceX64 = new BN('200000');
+    const currentSqrtPriceX64 = new BN('100000'); // below the lower limit
+    const endSqrtPriceX64 = new BN('300000');
+    const amountB = new BN('500000');
+
+    const amountA = LiquidityMath.getAmountAFromAmountB(
+      startSqrtPriceX64,
+      endSqrtPriceX64,
+      currentSqrtPriceX64,
+      amountB
+    );
+
+    // verify the result is 0
+    expect(amountA.toString()).toBe('0');
+  });
+
+  test('current price is above the upper limit - return 0', () => {
+    // price: start < end < current
+    const startSqrtPriceX64 = new BN('100000');
+    const endSqrtPriceX64 = new BN('200000');
+    const currentSqrtPriceX64 = new BN('300000'); // above the upper limit
+    const amountB = new BN('500000');
+
+    const amountA = LiquidityMath.getAmountAFromAmountB(
+      startSqrtPriceX64,
+      endSqrtPriceX64,
+      currentSqrtPriceX64,
+      amountB
+    );
+
+    // verify the result is 0
+    expect(amountA.toString()).toBe('0');
+  });
+
+  test('price range order adjustment - ensure start is always less than end', () => {
+    // test the case of reversed price order: end < start
+    const endSqrtPriceX64 = new BN('100000'); // deliberately reversed
+    const currentSqrtPriceX64 = new BN('150000');
+    const startSqrtPriceX64 = new BN('200000'); // deliberately reversed
+    const amountB = new BN('500000');
+
+    // call in the correct order
+    const expectedAmountA = LiquidityMath.getAmountAFromAmountB(
+      endSqrtPriceX64,
+      startSqrtPriceX64,
+      currentSqrtPriceX64,
+      amountB
+    );
+
+    // call in the reversed order
+    const amountA = LiquidityMath.getAmountAFromAmountB(
+      startSqrtPriceX64,
+      endSqrtPriceX64,
+      currentSqrtPriceX64,
+      amountB
+    );
+
+    // verify the result is the same
+    expect(amountA.toString()).toBe(expectedAmountA.toString());
+  });
+});
+
+describe('LiquidityMath consistency test', () => {
+  test('liquidity calculation and token quantity calculation consistency', () => {
+    const startPirce = '1';
+    const currentPrice = '2';
+    const endPrice = '3';
+
+    const decimalsA = 6;
+    const decimalsB = 6;
+
+    // set test parameters
+    const sqrtPriceX64A = SqrtPriceMath.priceToSqrtPriceX64(new Decimal(startPirce), decimalsA, decimalsB);
+    const sqrtPriceCurrentX64 = SqrtPriceMath.priceToSqrtPriceX64(new Decimal(currentPrice), decimalsA, decimalsB);
+    const sqrtPriceX64B = SqrtPriceMath.priceToSqrtPriceX64(new Decimal(endPrice), decimalsA, decimalsB);
+    const amountA = new BN('1000000');
+    const amountB = new BN('500000');
+
+    // 1. use token quantity to calculate liquidity
+    const liquidity = LiquidityMath.getLiquidityFromTokenAmounts(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      amountA,
+      amountB
+    );
+
+    // 2. use the calculated liquidity to calculate the token quantity
+    const { amountA: calculatedAmountA, amountB: calculatedAmountB } = LiquidityMath.getAmountsFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      liquidity,
+      false // do not round up for comparison
+    );
+
+    // verify: the calculated token quantity should be less than or equal to the original token quantity
+    // (since the liquidity depends on the smaller of the two tokens provided)
+    expect(new BN(calculatedAmountA).lte(amountA)).toBeTruthy();
+    expect(new BN(calculatedAmountB).lte(amountB)).toBeTruthy();
+
+    // verify: at least one of the calculated token quantities should be close to the original token quantity
+    const precisionThreshold = 0.0001; // allow 0.01% error
+
+    const amountADiff = Math.abs(1 - Number(calculatedAmountA.toString()) / Number(amountA.toString()));
+
+    const amountBDiff = Math.abs(1 - Number(calculatedAmountB.toString()) / Number(amountB.toString()));
+
+    expect(amountADiff < precisionThreshold || amountBDiff < precisionThreshold).toBeTruthy();
+  });
+
+  test('consistency test between getAmountBFromAmountA and getAmountAFromAmountB', () => {
+    const startPirce = '1';
+    const currentPrice = '2';
+    const endPrice = '3';
+
+    const decimalsA = 6;
+    const decimalsB = 6;
+
+    // set test parameters
+    const startSqrtPriceX64 = SqrtPriceMath.priceToSqrtPriceX64(new Decimal(startPirce), decimalsA, decimalsB);
+    const currentSqrtPriceX64 = SqrtPriceMath.priceToSqrtPriceX64(new Decimal(currentPrice), decimalsA, decimalsB);
+    const endSqrtPriceX64 = SqrtPriceMath.priceToSqrtPriceX64(new Decimal(endPrice), decimalsA, decimalsB);
+    const initialAmountA = new BN('1000000');
+
+    // 1. A -> B
+    const calculatedAmountB = LiquidityMath.getAmountBFromAmountA(
+      startSqrtPriceX64,
+      endSqrtPriceX64,
+      currentSqrtPriceX64,
+      initialAmountA
+    );
+
+    // 2. B -> A
+    const calculatedAmountA = LiquidityMath.getAmountAFromAmountB(
+      startSqrtPriceX64,
+      endSqrtPriceX64,
+      currentSqrtPriceX64,
+      calculatedAmountB
+    );
+
+    // verify: the calculated A quantity should be close to the original value
+    // due to rounding errors, it may not be exactly equal, so use relative error
+    const relativeError =
+      Math.abs(Number(initialAmountA.toString()) - Number(calculatedAmountA.toString())) /
+      Number(initialAmountA.toString());
+
+    expect(relativeError).toBeLessThan(0.0001); // 允许 0.01% 误差
+  });
+});
+
+describe('LiquidityMath boundary and special case test', () => {
+  test('zero input test', () => {
+    const sqrtPriceX64A = new BN('100000');
+    const sqrtPriceCurrentX64 = new BN('200000');
+    const sqrtPriceX64B = new BN('300000');
+
+    // zero liquidity
+    const zeroLiquidity = new BN(0);
+    const { amountA, amountB } = LiquidityMath.getAmountsFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      zeroLiquidity,
+      true
+    );
+
+    expect(amountA.toString()).toBe('0');
+    expect(amountB.toString()).toBe('0');
+
+    // zero token quantity
+    const zeroAmount = new BN(0);
+    const amountAResult = LiquidityMath.getAmountAFromAmountB(
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      sqrtPriceCurrentX64,
+      zeroAmount
+    );
+
+    const amountBResult = LiquidityMath.getAmountBFromAmountA(
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      sqrtPriceCurrentX64,
+      zeroAmount
+    );
+
+    expect(amountAResult.toString()).toBe('0');
+    expect(amountBResult.toString()).toBe('0');
+  });
+
+  test('extreme liquidity value test', () => {
+    const sqrtPriceX64A = new BN('100000');
+    const sqrtPriceCurrentX64 = new BN('200000');
+    const sqrtPriceX64B = new BN('300000');
+
+    // extremely large liquidity value
+    const largeLiquidity = new BN('340282366920938463463374607431768211455'); // MaxUint128 - 1
+
+    // this test is mainly to ensure that no exceptions or overflows are thrown
+    const { amountA, amountB } = LiquidityMath.getAmountsFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      largeLiquidity,
+      true
+    );
+
+    expect(amountA.toString()).not.toBe('NaN');
+    expect(amountB.toString()).not.toBe('NaN');
+  });
+
+  test('equal price point test', () => {
+    // test the case of sqrtPriceX64A = sqrtPriceX64B
+    const sqrtPriceX64 = new BN('200000');
+    const sqrtPriceCurrentX64 = new BN('150000');
+    const liquidity = new BN('10000000');
+
+    // when the price is equal
+    const { amountA, amountB } = LiquidityMath.getAmountsFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64,
+      sqrtPriceX64,
+      liquidity,
+      true
+    );
+
+    // expected to be zero or very small
+    expect(amountA.toString()).toBe('0');
+    expect(amountB.toString()).toBe('0');
+  });
+
+  test('price point near current price test', () => {
+    // test the case of current ≈ A or current ≈ B
+    const sqrtPriceX64A = new BN('200000');
+    const sqrtPriceCurrentX64 = new BN('200001'); // very close to A
+    const sqrtPriceX64B = new BN('300000');
+    const liquidity = new BN('10000000');
+
+    // call the function
+    const result = LiquidityMath.getAmountsFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      liquidity,
+      true
+    );
+
+    // verify the result
+    expect(result.amountA.toString()).not.toBe('NaN');
+    expect(result.amountB.toString()).not.toBe('NaN');
+  });
+});

+ 212 - 0
src/lib/clmm-sdk/src/instructions/utils/liquidityMath.ts

@@ -0,0 +1,212 @@
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import { MaxU64, ONE, Q64, U64Resolution, ZERO } from '../constants.js';
+
+import { MathUtils } from './mathUtils.js';
+
+export class LiquidityMath {
+  public static addDelta(x: BN, y: BN): BN {
+    return x.add(y);
+  }
+
+  public static getTokenAmountAFromLiquidity(
+    sqrtPriceX64A: BN,
+    sqrtPriceX64B: BN,
+    liquidity: BN,
+    roundUp: boolean
+  ): BN {
+    if (sqrtPriceX64A.gt(sqrtPriceX64B)) {
+      [sqrtPriceX64A, sqrtPriceX64B] = [sqrtPriceX64B, sqrtPriceX64A];
+    }
+
+    if (!sqrtPriceX64A.gt(ZERO)) {
+      throw new Error('sqrtPriceX64A must greater than 0');
+    }
+
+    const numerator1 = liquidity.ushln(U64Resolution);
+    const numerator2 = sqrtPriceX64B.sub(sqrtPriceX64A);
+
+    return roundUp
+      ? MathUtils.mulDivRoundingUp(MathUtils.mulDivCeil(numerator1, numerator2, sqrtPriceX64B), ONE, sqrtPriceX64A)
+      : MathUtils.mulDivFloor(numerator1, numerator2, sqrtPriceX64B).div(sqrtPriceX64A);
+  }
+
+  public static getTokenAmountBFromLiquidity(
+    sqrtPriceX64A: BN,
+    sqrtPriceX64B: BN,
+    liquidity: BN,
+    roundUp: boolean
+  ): BN {
+    if (sqrtPriceX64A.gt(sqrtPriceX64B)) {
+      [sqrtPriceX64A, sqrtPriceX64B] = [sqrtPriceX64B, sqrtPriceX64A];
+    }
+    if (!sqrtPriceX64A.gt(ZERO)) {
+      throw new Error('sqrtPriceX64A must greater than 0');
+    }
+
+    return roundUp
+      ? MathUtils.mulDivCeil(liquidity, sqrtPriceX64B.sub(sqrtPriceX64A), Q64)
+      : MathUtils.mulDivFloor(liquidity, sqrtPriceX64B.sub(sqrtPriceX64A), Q64);
+  }
+
+  public static getLiquidityFromTokenAmountA(sqrtPriceX64A: BN, sqrtPriceX64B: BN, amountA: BN, roundUp: boolean): BN {
+    if (sqrtPriceX64A.gt(sqrtPriceX64B)) {
+      [sqrtPriceX64A, sqrtPriceX64B] = [sqrtPriceX64B, sqrtPriceX64A];
+    }
+
+    const numerator = amountA.mul(sqrtPriceX64A).mul(sqrtPriceX64B);
+    const denominator = sqrtPriceX64B.sub(sqrtPriceX64A);
+    const result = numerator.div(denominator);
+
+    if (roundUp) {
+      return MathUtils.mulDivRoundingUp(result, ONE, MaxU64);
+    } else {
+      return result.shrn(U64Resolution);
+    }
+  }
+
+  public static getLiquidityFromTokenAmountB(sqrtPriceX64A: BN, sqrtPriceX64B: BN, amountB: BN): BN {
+    if (sqrtPriceX64A.gt(sqrtPriceX64B)) {
+      [sqrtPriceX64A, sqrtPriceX64B] = [sqrtPriceX64B, sqrtPriceX64A];
+    }
+    return MathUtils.mulDivFloor(amountB, MaxU64, sqrtPriceX64B.sub(sqrtPriceX64A));
+  }
+
+  public static getLiquidityFromTokenAmounts(
+    sqrtPriceCurrentX64: BN,
+    sqrtPriceX64A: BN,
+    sqrtPriceX64B: BN,
+    amountA: BN,
+    amountB: BN
+  ): BN {
+    if (sqrtPriceX64A.gt(sqrtPriceX64B)) {
+      [sqrtPriceX64A, sqrtPriceX64B] = [sqrtPriceX64B, sqrtPriceX64A];
+    }
+
+    if (sqrtPriceCurrentX64.lte(sqrtPriceX64A)) {
+      return LiquidityMath.getLiquidityFromTokenAmountA(sqrtPriceX64A, sqrtPriceX64B, amountA, false);
+    } else if (sqrtPriceCurrentX64.lt(sqrtPriceX64B)) {
+      const liquidity0 = LiquidityMath.getLiquidityFromTokenAmountA(sqrtPriceCurrentX64, sqrtPriceX64B, amountA, false);
+      const liquidity1 = LiquidityMath.getLiquidityFromTokenAmountB(sqrtPriceX64A, sqrtPriceCurrentX64, amountB);
+      return liquidity0.lt(liquidity1) ? liquidity0 : liquidity1;
+    } else {
+      return LiquidityMath.getLiquidityFromTokenAmountB(sqrtPriceX64A, sqrtPriceX64B, amountB);
+    }
+  }
+
+  public static getAmountsFromLiquidity(
+    sqrtPriceCurrentX64: BN,
+    sqrtPriceX64A: BN,
+    sqrtPriceX64B: BN,
+    liquidity: BN,
+    roundUp: boolean
+  ): { amountA: BN; amountB: BN } {
+    if (sqrtPriceX64A.gt(sqrtPriceX64B)) {
+      [sqrtPriceX64A, sqrtPriceX64B] = [sqrtPriceX64B, sqrtPriceX64A];
+    }
+
+    if (sqrtPriceCurrentX64.lte(sqrtPriceX64A)) {
+      return {
+        amountA: LiquidityMath.getTokenAmountAFromLiquidity(sqrtPriceX64A, sqrtPriceX64B, liquidity, roundUp),
+        amountB: new BN(0),
+      };
+    } else if (sqrtPriceCurrentX64.lt(sqrtPriceX64B)) {
+      const amountA = LiquidityMath.getTokenAmountAFromLiquidity(
+        sqrtPriceCurrentX64,
+        sqrtPriceX64B,
+        liquidity,
+        roundUp
+      );
+      const amountB = LiquidityMath.getTokenAmountBFromLiquidity(
+        sqrtPriceX64A,
+        sqrtPriceCurrentX64,
+        liquidity,
+        roundUp
+      );
+      return { amountA, amountB };
+    } else {
+      return {
+        amountA: new BN(0),
+        amountB: LiquidityMath.getTokenAmountBFromLiquidity(sqrtPriceX64A, sqrtPriceX64B, liquidity, roundUp),
+      };
+    }
+  }
+
+  public static getAmountsFromLiquidityWithSlippage(
+    sqrtPriceCurrentX64: BN,
+    sqrtPriceX64A: BN,
+    sqrtPriceX64B: BN,
+    liquidity: BN,
+    amountMax: boolean,
+    roundUp: boolean,
+    amountSlippage: number
+  ): { amountSlippageA: BN; amountSlippageB: BN } {
+    const { amountA, amountB } = LiquidityMath.getAmountsFromLiquidity(
+      sqrtPriceCurrentX64,
+      sqrtPriceX64A,
+      sqrtPriceX64B,
+      liquidity,
+      roundUp
+    );
+    const coefficient = amountMax ? 1 + amountSlippage : 1 - amountSlippage;
+
+    const amount0Slippage = new BN(new Decimal(amountA.toString()).mul(coefficient).toFixed(0));
+    const amount1Slippage = new BN(new Decimal(amountB.toString()).mul(coefficient).toFixed(0));
+    return {
+      amountSlippageA: amount0Slippage,
+      amountSlippageB: amount1Slippage,
+    };
+  }
+
+  /**
+   * Given a price range, calculate the required tokenB amount after investing a specified tokenA amount
+   *
+   * Similar implementation in raydium: getLiquidityAmountOutFromAmountIn
+   */
+  public static getAmountBFromAmountA(
+    startSqrtPriceX64: BN,
+    endSqrtPriceX64: BN,
+    currentSqrtPriceX64: BN,
+    amountA: BN
+  ): BN {
+    if (startSqrtPriceX64.gt(endSqrtPriceX64)) {
+      [startSqrtPriceX64, endSqrtPriceX64] = [endSqrtPriceX64, startSqrtPriceX64];
+    }
+
+    if (currentSqrtPriceX64.lte(startSqrtPriceX64)) {
+      return new BN(0);
+    }
+
+    if (currentSqrtPriceX64.gt(endSqrtPriceX64)) {
+      return new BN(0);
+    }
+    const liquidity = LiquidityMath.getLiquidityFromTokenAmountA(currentSqrtPriceX64, endSqrtPriceX64, amountA, false);
+    return LiquidityMath.getTokenAmountBFromLiquidity(startSqrtPriceX64, currentSqrtPriceX64, liquidity, true);
+  }
+
+  /**
+   * Given a price range, calculate the required tokenA amount after investing a specified tokenB amount
+   *
+   * Similar implementation in raydium: getLiquidityAmountOutFromAmountIn
+   */
+  public static getAmountAFromAmountB(
+    startSqrtPriceX64: BN,
+    endSqrtPriceX64: BN,
+    currentSqrtPriceX64: BN,
+    amountB: BN
+  ): BN {
+    if (startSqrtPriceX64.gt(endSqrtPriceX64)) {
+      [startSqrtPriceX64, endSqrtPriceX64] = [endSqrtPriceX64, startSqrtPriceX64];
+    }
+
+    if (currentSqrtPriceX64.lte(startSqrtPriceX64)) {
+      return new BN(0);
+    }
+    if (currentSqrtPriceX64.gt(endSqrtPriceX64)) {
+      return new BN(0);
+    }
+    const liquidity = LiquidityMath.getLiquidityFromTokenAmountB(startSqrtPriceX64, currentSqrtPriceX64, amountB);
+    return LiquidityMath.getTokenAmountAFromLiquidity(currentSqrtPriceX64, endSqrtPriceX64, liquidity, true);
+  }
+}

+ 42 - 0
src/lib/clmm-sdk/src/instructions/utils/mathUtils.ts

@@ -0,0 +1,42 @@
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import { ONE, Q128, ZERO } from '../constants.js';
+
+export class MathUtils {
+  public static mulDivRoundingUp(a: BN, b: BN, denominator: BN): BN {
+    const numerator = a.mul(b);
+    let result = numerator.div(denominator);
+    if (!numerator.mod(denominator).eq(ZERO)) {
+      result = result.add(ONE);
+    }
+    return result;
+  }
+
+  public static mulDivFloor(a: BN, b: BN, denominator: BN): BN {
+    if (denominator.eq(ZERO)) {
+      throw new Error('division by 0');
+    }
+    return a.mul(b).div(denominator);
+  }
+
+  public static mulDivCeil(a: BN, b: BN, denominator: BN): BN {
+    if (denominator.eq(ZERO)) {
+      throw new Error('division by 0');
+    }
+    const numerator = a.mul(b).add(denominator.sub(ONE));
+    return numerator.div(denominator);
+  }
+
+  public static x64ToDecimal(num: BN, decimalPlaces?: number): Decimal {
+    return new Decimal(num.toString()).div(Decimal.pow(2, 64)).toDecimalPlaces(decimalPlaces);
+  }
+
+  public static decimalToX64(num: Decimal): BN {
+    return new BN(num.mul(Decimal.pow(2, 64)).floor().toFixed());
+  }
+
+  public static wrappingSubU128(n0: BN, n1: BN): BN {
+    return n0.add(Q128).sub(n1).mod(Q128);
+  }
+}

+ 97 - 0
src/lib/clmm-sdk/src/instructions/utils/models.ts

@@ -0,0 +1,97 @@
+import { PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+export interface ReturnTypeGetPriceAndTick {
+  tick: number;
+  price: Decimal;
+}
+
+export type Tick = {
+  tick: number;
+  liquidityNet: BN;
+  liquidityGross: BN;
+  feeGrowthOutsideX64A: BN;
+  feeGrowthOutsideX64B: BN;
+  rewardGrowthsOutsideX64: BN[];
+};
+
+export type TickArray = {
+  address: PublicKey;
+  poolId: PublicKey;
+  startTickIndex: number;
+  ticks: Tick[];
+  initializedTickCount: number;
+};
+
+/**
+ * Dynamic Tick Array type
+ * Uses sparse storage with a mapping table to track allocated tick positions
+ */
+export type DynTickArray = {
+  address: PublicKey;
+  poolId: PublicKey;
+  startTickIndex: number;
+  tickOffsetIndex: number[]; // Mapping table: tickOffsetIndex[offset] = position + 1 (0 = unallocated)
+  allocTickCount: number; // Number of allocated ticks
+  initializedTickCount: number; // Number of initialized ticks
+  ticks: Tick[]; // Dynamic array of tick states (length = allocTickCount)
+};
+
+/**
+ * Unified Tick Array Container
+ * Discriminated union to handle both fixed and dynamic tick arrays
+ */
+export type TickArrayContainer = { type: 'Fixed'; data: TickArray } | { type: 'Dynamic'; data: DynTickArray };
+
+/**
+ * Type guard to check if a container is a fixed tick array
+ */
+export function isFixedTickArray(container: TickArrayContainer): container is { type: 'Fixed'; data: TickArray } {
+  return container.type === 'Fixed';
+}
+
+/**
+ * Type guard to check if a container is a dynamic tick array
+ */
+export function isDynamicTickArray(
+  container: TickArrayContainer
+): container is { type: 'Dynamic'; data: DynTickArray } {
+  return container.type === 'Dynamic';
+}
+
+export type TickState = {
+  tick: number;
+  liquidityNet: BN;
+  liquidityGross: BN;
+  feeGrowthOutsideX64A: BN;
+  feeGrowthOutsideX64B: BN;
+  tickCumulativeOutside: BN;
+  secondsPerLiquidityOutsideX64: BN;
+  secondsOutside: number;
+  rewardGrowthsOutside: BN[];
+};
+
+export type TickArrayState = {
+  ammPool: PublicKey;
+  startTickIndex: number;
+  ticks: TickState[];
+  initializedTickCount: number;
+};
+
+export interface TickArrayBitmapExtensionType {
+  poolId: PublicKey;
+  exBitmapAddress: PublicKey;
+  positiveTickArrayBitmap: BN[][];
+  negativeTickArrayBitmap: BN[][];
+}
+
+export interface StepComputations {
+  sqrtPriceStartX64: BN;
+  tickNext: number;
+  initialized: boolean;
+  sqrtPriceNextX64: BN;
+  amountIn: BN;
+  amountOut: BN;
+  feeAmount: BN;
+}

+ 164 - 0
src/lib/clmm-sdk/src/instructions/utils/poolStateUtils.ts

@@ -0,0 +1,164 @@
+import { Connection, PublicKey } from '@solana/web3.js';
+
+import { RawDataUtils } from '../getRawData.js';
+import { IPoolLayout } from '../layout.js';
+
+/**
+ * Pool State Utilities for querying pool information with decay fee support
+ */
+export class PoolStateUtils {
+  /**
+   * Check if the pool has decay fee enabled
+   * @param poolInfo Pool layout information
+   * @returns boolean indicating if decay fee is enabled
+   */
+  static isDecayFeeEnabled(poolInfo: IPoolLayout): boolean {
+    return (poolInfo.decayFeeFlag & (1 << 0)) !== 0;
+  }
+
+  /**
+   * Check if decay fee is enabled for selling mint0
+   * @param poolInfo Pool layout information
+   * @returns boolean indicating if decay fee is enabled for mint0 sell
+   */
+  static isDecayFeeOnSellMint0(poolInfo: IPoolLayout): boolean {
+    return (poolInfo.decayFeeFlag & (1 << 1)) !== 0;
+  }
+
+  /**
+   * Check if decay fee is enabled for selling mint1
+   * @param poolInfo Pool layout information
+   * @returns boolean indicating if decay fee is enabled for mint1 sell
+   */
+  static isDecayFeeOnSellMint1(poolInfo: IPoolLayout): boolean {
+    return (poolInfo.decayFeeFlag & (1 << 2)) !== 0;
+  }
+
+  /**
+   * Calculate the current decay fee rate based on the current time
+   * @param poolInfo Pool layout information
+   * @param currentTimestamp Current timestamp in seconds
+   * @returns Decay fee rate in hundredths of a bip (10^-6)
+   */
+  static getDecayFeeRate(poolInfo: IPoolLayout, currentTimestamp: number): number {
+    if (!this.isDecayFeeEnabled(poolInfo)) {
+      return 0;
+    }
+
+    // Pool is not open yet
+    if (currentTimestamp < poolInfo.openTime.toNumber()) {
+      return 0;
+    }
+
+    const intervalCount = Math.floor(
+      (currentTimestamp - poolInfo.openTime.toNumber()) / poolInfo.decayFeeDecreaseInterval
+    );
+
+    const decayFeeDecreaseRate = poolInfo.decayFeeDecreaseRate * 10000; // Convert to basis points
+    const FEE_RATE_DENOMINATOR_VALUE = 1000000; // 10^6
+    const hundredthsOfABip = FEE_RATE_DENOMINATOR_VALUE;
+    let rate = hundredthsOfABip;
+
+    // Fast power calculation: (1 - x)^c
+    // where x = decayFeeDecreaseRate / 10^6, c = intervalCount
+    if (intervalCount > 0) {
+      let exp = intervalCount;
+      let base = hundredthsOfABip - decayFeeDecreaseRate;
+
+      while (exp > 0) {
+        if (exp % 2 === 1) {
+          rate = Math.ceil((rate * base) / hundredthsOfABip);
+        }
+        base = Math.ceil((base * base) / hundredthsOfABip);
+        exp = Math.floor(exp / 2);
+      }
+    }
+
+    // Apply initial fee rate (convert from percentage)
+    rate = Math.ceil((rate * poolInfo.decayFeeInitFeeRate) / 100);
+
+    return rate;
+  }
+
+  /**
+   * Get comprehensive decay fee information for a pool
+   * @param poolInfo Pool layout information
+   * @param currentTimestamp Current timestamp in seconds
+   * @returns Object containing all decay fee related information
+   */
+  static getDecayFeeInfo(poolInfo: IPoolLayout, currentTimestamp?: number) {
+    const timestamp = currentTimestamp || Math.floor(Date.now() / 1000);
+
+    return {
+      isEnabled: this.isDecayFeeEnabled(poolInfo),
+      onSellMint0: this.isDecayFeeOnSellMint0(poolInfo),
+      onSellMint1: this.isDecayFeeOnSellMint1(poolInfo),
+      initFeeRate: poolInfo.decayFeeInitFeeRate, // Percentage (1 = 1%)
+      decreaseRate: poolInfo.decayFeeDecreaseRate, // Percentage (1 = 1%)
+      decreaseInterval: poolInfo.decayFeeDecreaseInterval, // Seconds
+      currentFeeRate: this.getDecayFeeRate(poolInfo, timestamp), // In hundredths of a bip (10^-6)
+      openTime: poolInfo.openTime.toNumber(),
+    };
+  }
+
+  /**
+   * Get pool state with decay fee information from chain
+   * @param connection Solana connection
+   * @param poolId Pool address
+   * @param currentTimestamp Optional current timestamp
+   * @returns Pool information with decay fee details
+   */
+  static async getPoolStateWithDecayFee(connection: Connection, poolId: string | PublicKey, currentTimestamp?: number) {
+    const poolInfo = await RawDataUtils.getRawPoolInfoByPoolId({
+      connection,
+      poolId,
+    });
+
+    if (!poolInfo) {
+      throw new Error(`Pool not found: ${poolId}`);
+    }
+
+    const decayFeeInfo = this.getDecayFeeInfo(poolInfo, currentTimestamp);
+
+    return {
+      ...poolInfo,
+      decayFeeInfo,
+    };
+  }
+
+  /**
+   * Check if decay fee is currently active for a specific direction
+   * @param poolInfo Pool layout information
+   * @param zeroForOne True if swapping token0 for token1 (selling token0)
+   * @param currentTimestamp Current timestamp in seconds
+   * @returns Object with active status and current fee rate
+   */
+  static getDecayFeeForDirection(poolInfo: IPoolLayout, zeroForOne: boolean, currentTimestamp?: number) {
+    const timestamp = currentTimestamp || Math.floor(Date.now() / 1000);
+
+    if (!this.isDecayFeeEnabled(poolInfo)) {
+      return {
+        isActive: false,
+        feeRate: 0,
+      };
+    }
+
+    const isActiveForDirection = zeroForOne
+      ? this.isDecayFeeOnSellMint0(poolInfo)
+      : this.isDecayFeeOnSellMint1(poolInfo);
+
+    if (!isActiveForDirection) {
+      return {
+        isActive: false,
+        feeRate: 0,
+      };
+    }
+
+    const currentFeeRate = this.getDecayFeeRate(poolInfo, timestamp);
+
+    return {
+      isActive: true,
+      feeRate: currentFeeRate,
+    };
+  }
+}

+ 412 - 0
src/lib/clmm-sdk/src/instructions/utils/poolUtils.ts

@@ -0,0 +1,412 @@
+import { PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+
+import { MAX_TICK, MIN_TICK, NEGATIVE_ONE } from '../constants.js';
+import { IAmmConfigLayout } from '../layout.js';
+import { IPoolLayoutWithId } from '../models.js';
+import { getPdaTickArrayAddress } from '../pda.js';
+
+import { TickArrayContainer, TickArrayBitmapExtensionType } from './models.js';
+import { SwapMath } from './swapMath.js';
+import { TickQuery, TickUtils } from './tick.js';
+import { TickArrayBitmap, TickArrayBitmapExtensionUtils } from './tickarrayBitmap.js';
+
+export class PoolUtils {
+  // Used to check if a set of tickarray start indices exceed the boundary range of the default bitmap
+  public static isOverflowDefaultTickarrayBitmap(tickSpacing: number, tickarrayStartIndexs: number[]): boolean {
+    const { maxTickBoundary, minTickBoundary } = this._tickRange(tickSpacing);
+
+    for (const tickIndex of tickarrayStartIndexs) {
+      const tickarrayStartIndex = TickUtils.getTickArrayStartIndexByTick(tickIndex, tickSpacing);
+
+      if (tickarrayStartIndex >= maxTickBoundary || tickarrayStartIndex < minTickBoundary) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  public static _tickRange(tickSpacing: number): {
+    maxTickBoundary: number;
+    minTickBoundary: number;
+  } {
+    let maxTickBoundary = TickArrayBitmap.maxTickInTickarrayBitmap(tickSpacing);
+    let minTickBoundary = -maxTickBoundary;
+
+    if (maxTickBoundary > MAX_TICK) {
+      maxTickBoundary = TickQuery.getArrayStartIndex(MAX_TICK, tickSpacing) + TickQuery.tickCount(tickSpacing);
+    }
+    if (minTickBoundary < MIN_TICK) {
+      minTickBoundary = TickQuery.getArrayStartIndex(MIN_TICK, tickSpacing);
+    }
+    return { maxTickBoundary, minTickBoundary };
+  }
+
+  /**
+   * Calculate the maximum and minimum ticks selectable by users in the UI
+   * Unlike _tickRange, this method directly returns the available tick value range, not the tickarray boundaries
+   *
+   * @param tickSpacing tick spacing
+   * @returns Maximum and minimum tick values selectable by users
+   */
+  public static tickRange(tickSpacing: number): {
+    maxTickBoundary: number;
+    minTickBoundary: number;
+  } {
+    // Use protocol-defined hard boundaries
+    let maxTickBoundary = MAX_TICK;
+    let minTickBoundary = MIN_TICK;
+
+    // Ensure returned tick values are divisible by tickSpacing to meet UI selection requirements
+    maxTickBoundary = Math.floor(maxTickBoundary / tickSpacing) * tickSpacing;
+    minTickBoundary = Math.ceil(minTickBoundary / tickSpacing) * tickSpacing;
+
+    return { maxTickBoundary, minTickBoundary };
+  }
+
+  public static nextInitializedTickArrayStartIndex(
+    poolInfo: {
+      tickCurrent: number;
+      tickSpacing: number;
+      tickArrayBitmap: BN[];
+      exBitmapInfo: TickArrayBitmapExtensionType;
+    },
+    lastTickArrayStartIndex: number,
+    zeroForOne: boolean
+  ): { isExist: boolean; nextStartIndex: number } {
+    // Start scanning from the provided lastTickArrayStartIndex instead of recomputing from tickCurrent
+    // This avoids skipping the immediate adjacent array when tickCurrent happens to equal a start index.
+
+    while (true) {
+      const { isInit: startIsInit, tickIndex: startIndex } = TickArrayBitmap.nextInitializedTickArrayStartIndex(
+        TickUtils.mergeTickArrayBitmap(poolInfo.tickArrayBitmap),
+        lastTickArrayStartIndex,
+        poolInfo.tickSpacing,
+        zeroForOne
+      );
+      if (startIsInit) {
+        return { isExist: true, nextStartIndex: startIndex };
+      }
+      lastTickArrayStartIndex = startIndex;
+
+      const { isInit, tickIndex } = TickArrayBitmapExtensionUtils.nextInitializedTickArrayFromOneBitmap(
+        lastTickArrayStartIndex,
+        poolInfo.tickSpacing,
+        zeroForOne,
+        poolInfo.exBitmapInfo
+      );
+      if (isInit) return { isExist: true, nextStartIndex: tickIndex };
+
+      lastTickArrayStartIndex = tickIndex;
+
+      if (lastTickArrayStartIndex < MIN_TICK || lastTickArrayStartIndex > MAX_TICK)
+        return { isExist: false, nextStartIndex: 0 };
+    }
+  }
+
+  public static getFirstInitializedTickArray(
+    poolInfo: {
+      programId: PublicKey;
+      poolId: PublicKey;
+      tickCurrent: number;
+      tickSpacing: number;
+      tickArrayBitmap: BN[];
+      exBitmapInfo: TickArrayBitmapExtensionType;
+    },
+    zeroForOne: boolean
+  ):
+    | { isExist: true; startIndex: number; nextAccountMeta: PublicKey }
+    | { isExist: false; startIndex: undefined; nextAccountMeta: undefined } {
+    const { isInitialized, startIndex } = PoolUtils.isOverflowDefaultTickarrayBitmap(poolInfo.tickSpacing, [
+      poolInfo.tickCurrent,
+    ])
+      ? TickArrayBitmapExtensionUtils.checkTickArrayIsInit(
+          TickQuery.getArrayStartIndex(poolInfo.tickCurrent, poolInfo.tickSpacing),
+          poolInfo.tickSpacing,
+          poolInfo.exBitmapInfo
+        )
+      : TickUtils.checkTickArrayIsInitialized(
+          TickUtils.mergeTickArrayBitmap(poolInfo.tickArrayBitmap),
+          poolInfo.tickCurrent,
+          poolInfo.tickSpacing
+        );
+
+    if (isInitialized) {
+      const { publicKey: address } = getPdaTickArrayAddress(poolInfo.programId, poolInfo.poolId, startIndex);
+      return {
+        isExist: true,
+        startIndex,
+        nextAccountMeta: address,
+      };
+    }
+    const { isExist, nextStartIndex } = this.nextInitializedTickArrayStartIndex(
+      poolInfo,
+      TickQuery.getArrayStartIndex(poolInfo.tickCurrent, poolInfo.tickSpacing),
+      zeroForOne
+    );
+    if (isExist) {
+      const { publicKey: address } = getPdaTickArrayAddress(poolInfo.programId, poolInfo.poolId, nextStartIndex);
+      return {
+        isExist: true,
+        startIndex: nextStartIndex,
+        nextAccountMeta: address,
+      };
+    }
+    return { isExist: false, nextAccountMeta: undefined, startIndex: undefined };
+  }
+
+  /**
+   * Calculate the output amount and required account list for token swap
+   *
+   * This function is the core of CLMM (Concentrated Liquidity Market Maker) swap logic, used to calculate the expected output amount for a given input amount,
+   * and determine all tick array accounts that need to be accessed during trade execution.
+   *
+   * @param params Swap calculation parameter object
+   * @param params.poolInfo - Complete information of the liquidity pool, including current price, liquidity, tick and other states
+   * @param params.exBitmapInfo - Tick array bitmap extension information, used to handle ticks beyond the default range
+   * @param params.ammConfig - AMM configuration information, including transaction fee rates and other parameters
+   * @param params.tickArrayInfo - Loaded tick array information cache, with keys as tick array start indices
+   * @param params.inputTokenMint - Input token mint address, used to determine trade direction
+   * @param params.inputAmount - Input token amount (using minimum units)
+   * @param params.sqrtPriceLimitX64 - Optional price limit, representing the worst price for the trade (Q64.64 format)
+   * @param params.catchLiquidityInsufficient - Whether to catch liquidity insufficient situations, default false
+   *
+   * @returns Swap calculation result object
+   * @returns allTrade - Boolean value indicating whether the specified input amount can be fully traded
+   *                    true: All amount can be traded
+   *                    false: Only partial trading due to insufficient liquidity or price limit
+   * @returns expectedAmountOut - Expected output token amount to be obtained (using minimum units)
+   * @returns remainingAccounts - List of all tick array account addresses that need to be accessed during trading
+   *                             These accounts need to be passed as remaining accounts in the trade instruction
+   * @returns executionPrice - Final price after trade execution (square root price in Q64.64 format)
+   * @returns feeAmount - Total fees generated by the trade (using input token minimum units)
+   *
+   * @throws Error Throws 'Invalid tick array' error when no valid tick array is found
+   */
+  public static getOutputAmountAndRemainAccounts(params: {
+    poolInfo: IPoolLayoutWithId;
+    exBitmapInfo: TickArrayBitmapExtensionType;
+    ammConfig: IAmmConfigLayout;
+    tickArrayInfo: { [key: string]: TickArrayContainer };
+    inputTokenMint: PublicKey;
+    inputAmount: BN;
+    sqrtPriceLimitX64?: BN;
+    catchLiquidityInsufficient?: boolean;
+  }): {
+    allTrade: boolean;
+    expectedAmountOut: BN;
+    remainingAccounts: PublicKey[];
+    executionPrice: BN;
+    feeAmount: BN;
+  } {
+    const {
+      poolInfo,
+      exBitmapInfo,
+      ammConfig,
+      tickArrayInfo,
+      inputTokenMint,
+      inputAmount,
+      sqrtPriceLimitX64,
+      catchLiquidityInsufficient = false,
+    } = params;
+
+    // Step 1: Determine trade direction
+    // zeroForOne = true: means trading tokenA (token0) for tokenB (token1), price decreases
+    // zeroForOne = false: means trading tokenB (token1) for tokenA (token0), price increases
+    const zeroForOne = inputTokenMint.toBase58() === poolInfo.mintA.toBase58();
+
+    // Step 2: Initialize account list to store all tick array accounts needed during trading
+    const allNeededAccounts: PublicKey[] = [];
+
+    // Step 3: Find the first tick array that needs to be accessed
+    // Based on current tick position and trade direction, find the array containing current tick or next initialized tick
+    const {
+      isExist,
+      startIndex: firstTickArrayStartIndex,
+      nextAccountMeta,
+    } = this.getFirstInitializedTickArray(
+      {
+        programId: poolInfo.programId,
+        poolId: poolInfo.poolId,
+        tickCurrent: poolInfo.tickCurrent,
+        tickSpacing: poolInfo.tickSpacing,
+        tickArrayBitmap: poolInfo.tickArrayBitmap,
+        exBitmapInfo,
+      },
+      zeroForOne
+    );
+
+    // If no valid tick array is found, it indicates abnormal pool state or no liquidity
+    if (!isExist || firstTickArrayStartIndex === undefined || !nextAccountMeta) throw new Error('Invalid tick array');
+
+    // Step 4: Add the first tick array account to the list
+    allNeededAccounts.push(nextAccountMeta);
+
+    // Step 5: Execute the core logic of swap calculation
+    // SwapMath.swapCompute simulates the entire swap process, calculating:
+    // - Actual tradable amount
+    // - Output token amount
+    // - Other tick array accounts needed on the trade path
+    // - Final execution price and fees
+    const {
+      allTrade,
+      amountCalculated: outputAmount,
+      accounts: reaminAccounts,
+      sqrtPriceX64: executionPrice,
+      feeAmount,
+    } = SwapMath.swapCompute(
+      poolInfo.programId,
+      poolInfo.poolId,
+      tickArrayInfo,
+      poolInfo.tickArrayBitmap,
+      exBitmapInfo,
+      zeroForOne,
+      ammConfig.tradeFeeRate,
+      poolInfo.liquidity,
+      poolInfo.tickCurrent,
+      poolInfo.tickSpacing,
+      poolInfo.sqrtPriceX64,
+      inputAmount,
+      firstTickArrayStartIndex,
+      sqrtPriceLimitX64,
+      catchLiquidityInsufficient
+    );
+
+    // Step 6: Add other necessary accounts discovered during swap to the list
+    allNeededAccounts.push(...reaminAccounts);
+
+    // Step 7: Return calculation results
+    // Note: outputAmount is negative (indicating outflow), needs to be multiplied by -1 to convert to positive
+    return {
+      allTrade,
+      expectedAmountOut: outputAmount.mul(NEGATIVE_ONE),
+      remainingAccounts: allNeededAccounts,
+      executionPrice,
+      feeAmount,
+    };
+  }
+
+  /**
+   * Calculate the required input token amount and account list for exact output
+   *
+   * This function is used for "exact output" scenarios, where the user specifies the desired output token amount,
+   * and the function calculates how many tokens need to be input to obtain the specified output amount.
+   *
+   * @param params Swap calculation parameter object
+   * @param params.poolInfo - Complete information of the liquidity pool
+   * @param params.exBitmapInfo - Tick array bitmap extension information
+   * @param params.ammConfig - AMM configuration information
+   * @param params.tickArrayInfo - Loaded tick array information cache
+   * @param params.outputTokenMint - Output token mint address
+   * @param params.outputAmount - Expected output token amount (using minimum units)
+   * @param params.sqrtPriceLimitX64 - Optional price limit (Q64.64 format)
+   * @param params.catchLiquidityInsufficient - Whether to catch liquidity insufficient situations
+   *
+   * @returns Swap calculation result object
+   * @returns allTrade - Whether the specified output amount can be fully obtained
+   * @returns expectedAmountIn - Required input token amount (including fees)
+   * @returns remainingAccounts - List of tick array accounts that need to be accessed during trading
+   * @returns executionPrice - Final price after trade execution
+   * @returns feeAmount - Total fees generated by the trade
+   */
+  public static getInputAmountAndRemainAccounts(params: {
+    poolInfo: IPoolLayoutWithId;
+    exBitmapInfo: TickArrayBitmapExtensionType;
+    ammConfig: IAmmConfigLayout;
+    tickArrayInfo: { [key: string]: TickArrayContainer };
+    outputTokenMint: PublicKey;
+    outputAmount: BN;
+    sqrtPriceLimitX64?: BN;
+    catchLiquidityInsufficient?: boolean;
+  }): {
+    allTrade: boolean;
+    expectedAmountIn: BN;
+    remainingAccounts: PublicKey[];
+    executionPrice: BN;
+    feeAmount: BN;
+  } {
+    const {
+      poolInfo,
+      exBitmapInfo,
+      ammConfig,
+      tickArrayInfo,
+      outputTokenMint,
+      outputAmount,
+      sqrtPriceLimitX64,
+      catchLiquidityInsufficient = false,
+    } = params;
+
+    // 步骤1: 确定交易方向
+    // 注意:对于精确输出,交易方向的判断基于输出代币
+    // 如果输出 tokenB,则需要输入 tokenA(zeroForOne = true)
+    // 如果输出 tokenA,则需要输入 tokenB(zeroForOne = false)
+    const zeroForOne = outputTokenMint.toBase58() === poolInfo.mintB.toBase58();
+
+    // 步骤2: 初始化账户列表
+    const allNeededAccounts: PublicKey[] = [];
+
+    // 步骤3: 查找第一个需要访问的 tick 数组
+    const {
+      isExist,
+      startIndex: firstTickArrayStartIndex,
+      nextAccountMeta,
+    } = this.getFirstInitializedTickArray(
+      {
+        programId: poolInfo.programId,
+        poolId: poolInfo.poolId,
+        tickCurrent: poolInfo.tickCurrent,
+        tickSpacing: poolInfo.tickSpacing,
+        tickArrayBitmap: poolInfo.tickArrayBitmap,
+        exBitmapInfo,
+      },
+      zeroForOne
+    );
+
+    if (!isExist || firstTickArrayStartIndex === undefined || !nextAccountMeta) throw new Error('Invalid tick array');
+
+    // Step 4: Add the first tick array account to the list
+    allNeededAccounts.push(nextAccountMeta);
+
+    // 步骤5: 执行交换计算
+    // 对于精确输出,amountSpecified 需要是负数
+    const amountSpecified = outputAmount.mul(NEGATIVE_ONE);
+
+    const {
+      allTrade,
+      amountCalculated: inputAmount,
+      accounts: reaminAccounts,
+      sqrtPriceX64: executionPrice,
+      feeAmount,
+    } = SwapMath.swapCompute(
+      poolInfo.programId,
+      poolInfo.poolId,
+      tickArrayInfo,
+      poolInfo.tickArrayBitmap,
+      exBitmapInfo,
+      zeroForOne,
+      ammConfig.tradeFeeRate,
+      poolInfo.liquidity,
+      poolInfo.tickCurrent,
+      poolInfo.tickSpacing,
+      poolInfo.sqrtPriceX64,
+      amountSpecified,
+      firstTickArrayStartIndex,
+      sqrtPriceLimitX64,
+      catchLiquidityInsufficient
+    );
+
+    // 步骤6: 将其他必需账户添加到列表中
+    allNeededAccounts.push(...reaminAccounts);
+
+    // 步骤7: 返回计算结果
+    // 注意:对于精确输出,inputAmount 包含了手续费
+    return {
+      allTrade,
+      expectedAmountIn: inputAmount,
+      remainingAccounts: allNeededAccounts,
+      executionPrice,
+      feeAmount,
+    };
+  }
+}

+ 160 - 0
src/lib/clmm-sdk/src/instructions/utils/position.ts

@@ -0,0 +1,160 @@
+import { EpochInfo } from '@solana/web3.js';
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import { Q64 } from '../constants.js';
+import { IPersonalPositionLayout, IPoolLayout } from '../layout.js';
+
+import { LiquidityMath } from './liquidityMath.js';
+import { MathUtils } from './mathUtils.js';
+import { SqrtPriceMath } from './sqrtPriceMath.js';
+import { getTransferAmountFee, minExpirationTime } from './transfer.js';
+import { Tick } from './models.js';
+
+export interface GetTransferAmountFee {
+  amount: BN;
+  fee: BN | undefined;
+  expirationTime: number | undefined;
+}
+
+export interface ReturnTypeGetLiquidityAmountOut {
+  liquidity: BN;
+  amountSlippageA: GetTransferAmountFee;
+  amountSlippageB: GetTransferAmountFee;
+  amountA: GetTransferAmountFee;
+  amountB: GetTransferAmountFee;
+  expirationTime: number | undefined;
+}
+
+export class PositionUtils {
+  static getfeeGrowthInside(
+    poolInfo: Pick<IPoolLayout, 'tickCurrent' | 'feeGrowthGlobalX64A' | 'feeGrowthGlobalX64B'>,
+    tickLowerState: Tick,
+    tickUpperState: Tick
+  ): { feeGrowthInsideX64A: BN; feeGrowthInsideBX64: BN } {
+    let feeGrowthBelowX64A = new BN(0);
+    let feeGrowthBelowX64B = new BN(0);
+    if (poolInfo.tickCurrent >= tickLowerState.tick) {
+      feeGrowthBelowX64A = tickLowerState.feeGrowthOutsideX64A;
+      feeGrowthBelowX64B = tickLowerState.feeGrowthOutsideX64B;
+    } else {
+      feeGrowthBelowX64A = poolInfo.feeGrowthGlobalX64A.sub(tickLowerState.feeGrowthOutsideX64A);
+      feeGrowthBelowX64B = poolInfo.feeGrowthGlobalX64B.sub(tickLowerState.feeGrowthOutsideX64B);
+    }
+
+    let feeGrowthAboveX64A = new BN(0);
+    let feeGrowthAboveX64B = new BN(0);
+    if (poolInfo.tickCurrent < tickUpperState.tick) {
+      feeGrowthAboveX64A = tickUpperState.feeGrowthOutsideX64A;
+      feeGrowthAboveX64B = tickUpperState.feeGrowthOutsideX64B;
+    } else {
+      feeGrowthAboveX64A = poolInfo.feeGrowthGlobalX64A.sub(tickUpperState.feeGrowthOutsideX64A);
+      feeGrowthAboveX64B = poolInfo.feeGrowthGlobalX64B.sub(tickUpperState.feeGrowthOutsideX64B);
+    }
+
+    const feeGrowthInsideX64A = MathUtils.wrappingSubU128(
+      MathUtils.wrappingSubU128(poolInfo.feeGrowthGlobalX64A, feeGrowthBelowX64A),
+      feeGrowthAboveX64A
+    );
+    const feeGrowthInsideBX64 = MathUtils.wrappingSubU128(
+      MathUtils.wrappingSubU128(poolInfo.feeGrowthGlobalX64B, feeGrowthBelowX64B),
+      feeGrowthAboveX64B
+    );
+    return { feeGrowthInsideX64A, feeGrowthInsideBX64 };
+  }
+
+  static getPositionFees(
+    poolInfo: Pick<IPoolLayout, 'tickCurrent' | 'feeGrowthGlobalX64A' | 'feeGrowthGlobalX64B'>,
+    positionState: IPersonalPositionLayout,
+    tickLowerState: Tick,
+    tickUpperState: Tick
+  ): { tokenFeeAmountA: BN; tokenFeeAmountB: BN } {
+    const { feeGrowthInsideX64A, feeGrowthInsideBX64 } = this.getfeeGrowthInside(
+      poolInfo,
+      tickLowerState,
+      tickUpperState
+    );
+
+    const feeGrowthdeltaA = MathUtils.mulDivFloor(
+      MathUtils.wrappingSubU128(feeGrowthInsideX64A, positionState.feeGrowthInsideLastX64A),
+      positionState.liquidity,
+      Q64
+    );
+    const tokenFeeAmountA = positionState.tokenFeesOwedA.add(feeGrowthdeltaA);
+
+    const feeGrowthdelta1 = MathUtils.mulDivFloor(
+      MathUtils.wrappingSubU128(feeGrowthInsideBX64, positionState.feeGrowthInsideLastX64B),
+      positionState.liquidity,
+      Q64
+    );
+    const tokenFeeAmountB = positionState.tokenFeesOwedB.add(feeGrowthdelta1);
+
+    return { tokenFeeAmountA, tokenFeeAmountB };
+  }
+
+  static getAmountsFromLiquidity(params: {
+    poolInfo: IPoolLayout;
+    ownerPosition: IPersonalPositionLayout;
+    liquidity: BN;
+    slippage: number;
+    add: boolean;
+    epochInfo: EpochInfo;
+  }): ReturnTypeGetLiquidityAmountOut {
+    const { poolInfo, ownerPosition, liquidity, slippage, add, epochInfo } = params;
+    const sqrtPriceX64 = poolInfo.sqrtPriceX64;
+    const sqrtPriceX64A = SqrtPriceMath.getSqrtPriceX64FromTick(ownerPosition.tickLower);
+    const sqrtPriceX64B = SqrtPriceMath.getSqrtPriceX64FromTick(ownerPosition.tickUpper);
+
+    const coefficientRe = add ? 1 + slippage : 1 - slippage;
+
+    const amounts = LiquidityMath.getAmountsFromLiquidity(sqrtPriceX64, sqrtPriceX64A, sqrtPriceX64B, liquidity, add);
+
+    // TODO: Temporarily not considering Token 2022 transfer fee scenarios. If needed in the future, this should be added here;
+    const [amountA, amountB] = [
+      getTransferAmountFee(amounts.amountA, undefined, epochInfo, true),
+      getTransferAmountFee(amounts.amountB, undefined, epochInfo, true),
+    ];
+    const [amountSlippageA, amountSlippageB] = [
+      getTransferAmountFee(
+        new BN(new Decimal(amounts.amountA.toString()).mul(coefficientRe).toFixed(0)),
+        undefined,
+        epochInfo,
+        true
+      ),
+      getTransferAmountFee(
+        new BN(new Decimal(amounts.amountB.toString()).mul(coefficientRe).toFixed(0)),
+        undefined,
+        epochInfo,
+        true
+      ),
+    ];
+
+    // const [amountA, amountB] = [
+    //   getTransferAmountFee(amounts.amountA, poolInfo.mintA.extensions?.feeConfig, epochInfo, true),
+    //   getTransferAmountFee(amounts.amountB, poolInfo.mintB.extensions?.feeConfig, epochInfo, true),
+    // ];
+    // const [amountSlippageA, amountSlippageB] = [
+    //   getTransferAmountFee(
+    //     new BN(new Decimal(amounts.amountA.toString()).mul(coefficientRe).toFixed(0)),
+    //     poolInfo.mintA.extensions?.feeConfig,
+    //     epochInfo,
+    //     true,
+    //   ),
+    //   getTransferAmountFee(
+    //     new BN(new Decimal(amounts.amountB.toString()).mul(coefficientRe).toFixed(0)),
+    //     poolInfo.mintB.extensions?.feeConfig,
+    //     epochInfo,
+    //     true,
+    //   ),
+    // ];
+
+    return {
+      liquidity,
+      amountA,
+      amountB,
+      amountSlippageA,
+      amountSlippageB,
+      expirationTime: minExpirationTime(amountA.expirationTime, amountB.expirationTime),
+    };
+  }
+}

+ 220 - 0
src/lib/clmm-sdk/src/instructions/utils/sqrtPriceMath.test.ts

@@ -0,0 +1,220 @@
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+import { describe, test, expect } from 'vitest';
+
+import { MIN_TICK, MAX_TICK, MIN_SQRT_PRICE_X64, MAX_SQRT_PRICE_X64 } from '../constants.js';
+
+import { SqrtPriceMath } from './sqrtPriceMath.js';
+
+describe('SqrtPriceMath basic functionality test', () => {
+  test('price, sqrtPriceX64 and tick conversion', () => {
+    const price = new Decimal('1.0001');
+    const decimalsA = 6;
+    const decimalsB = 6;
+
+    // price -> tick
+    const tick = SqrtPriceMath.getTickFromPrice(price, decimalsA, decimalsB);
+
+    // tick -> sqrtPriceX64
+    const sqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tick);
+
+    // sqrtPriceX64 -> price
+    const calculatedPrice = SqrtPriceMath.sqrtPriceX64ToPrice(sqrtPriceX64, decimalsA, decimalsB);
+
+    // Verify that the converted price is close to the original price
+    expect(calculatedPrice.toNumber()).toBeCloseTo(price.toNumber(), 1);
+
+    // sqrtPriceX64 -> tick
+    const calculatedTick = SqrtPriceMath.getTickFromSqrtPriceX64(sqrtPriceX64);
+
+    // Verify that the converted tick is the same as the original tick
+    expect(calculatedTick).toBe(tick);
+  });
+});
+
+describe('SqrtPriceMath boundary condition test', () => {
+  test('MIN_TICK and MAX_TICK boundary test', () => {
+    // Test minimum tick
+    const minTickSqrtPrice = SqrtPriceMath.getSqrtPriceX64FromTick(MIN_TICK);
+    const minTickCalculated = SqrtPriceMath.getTickFromSqrtPriceX64(minTickSqrtPrice);
+    expect(minTickCalculated).toBe(MIN_TICK);
+
+    // Test maximum tick
+    const maxTickSqrtPrice = SqrtPriceMath.getSqrtPriceX64FromTick(MAX_TICK);
+    const maxTickCalculated = SqrtPriceMath.getTickFromSqrtPriceX64(maxTickSqrtPrice);
+    expect(maxTickCalculated).toBe(MAX_TICK);
+  });
+
+  test('MIN_SQRT_PRICE_X64 and MAX_SQRT_PRICE_X64 boundary test', () => {
+    // Test minimum sqrtPriceX64
+    const minTick = SqrtPriceMath.getTickFromSqrtPriceX64(MIN_SQRT_PRICE_X64);
+    const recalculatedSqrtPrice = SqrtPriceMath.getSqrtPriceX64FromTick(minTick);
+    // Check if it is within the reasonable error range
+    expect(recalculatedSqrtPrice.gte(MIN_SQRT_PRICE_X64)).toBeTruthy();
+
+    // Test maximum sqrtPriceX64
+    const maxTick = SqrtPriceMath.getTickFromSqrtPriceX64(MAX_SQRT_PRICE_X64);
+    const recalculatedMaxSqrtPrice = SqrtPriceMath.getSqrtPriceX64FromTick(maxTick);
+    // Check if it is within the reasonable error range
+    expect(recalculatedMaxSqrtPrice.lte(MAX_SQRT_PRICE_X64)).toBeTruthy();
+  });
+
+  test('Exception handling for out-of-bounds', () => {
+    // Test for less than MIN_TICK
+    expect(() => {
+      SqrtPriceMath.getSqrtPriceX64FromTick(MIN_TICK - 1);
+    }).toThrow();
+
+    // Test for greater than MAX_TICK
+    expect(() => {
+      SqrtPriceMath.getSqrtPriceX64FromTick(MAX_TICK + 1);
+    }).toThrow();
+
+    // Test for less than MIN_SQRT_PRICE_X64
+    expect(() => {
+      SqrtPriceMath.getTickFromSqrtPriceX64(MIN_SQRT_PRICE_X64.sub(new BN(1)));
+    }).toThrow();
+
+    // Test for greater than MAX_SQRT_PRICE_X64
+    expect(() => {
+      SqrtPriceMath.getTickFromSqrtPriceX64(MAX_SQRT_PRICE_X64.add(new BN(1)));
+    }).toThrow();
+  });
+});
+
+describe('SqrtPriceMath precision and rounding behavior test', () => {
+  test('tick boundary value rounding behavior_1', () => {
+    const tick = 1000;
+    const sqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tick);
+
+    // Create a slightly larger sqrtPriceX64 value than the current tick
+    const slightlyLargerSqrtPrice = sqrtPriceX64.add(new BN(1));
+    const nextTick = SqrtPriceMath.getTickFromSqrtPriceX64(slightlyLargerSqrtPrice);
+
+    expect(nextTick).toBe(tick);
+  });
+
+  test('tick boundary value rounding behavior_2', () => {
+    const tick = 1000;
+    const sqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tick);
+
+    // Create a slightly smaller sqrtPriceX64 value than the current tick
+    const slightlyLargerSqrtPrice = sqrtPriceX64.sub(new BN(1));
+    const nextTick = SqrtPriceMath.getTickFromSqrtPriceX64(slightlyLargerSqrtPrice);
+
+    expect(nextTick).toBe(tick - 1);
+  });
+
+  test('Different decimal places affect conversion', () => {
+    const price = new Decimal('8.7');
+
+    // Test different decimal place combinations
+    const decimalCombinations = [
+      { decimalsA: 6, decimalsB: 6 },
+      { decimalsA: 8, decimalsB: 6 },
+      { decimalsA: 6, decimalsB: 8 },
+      { decimalsA: 9, decimalsB: 18 },
+    ];
+
+    for (const { decimalsA, decimalsB } of decimalCombinations) {
+      // price -> sqrtPriceX64
+      const sqrtPriceX64 = SqrtPriceMath.priceToSqrtPriceX64(price, decimalsA, decimalsB);
+
+      // sqrtPriceX64 -> price
+      const calculatedPrice = SqrtPriceMath.sqrtPriceX64ToPrice(sqrtPriceX64, decimalsA, decimalsB);
+
+      // Verify that the converted price is close to the original price
+      expect(calculatedPrice.toNumber()).toBeCloseTo(price.toNumber(), 0.01);
+    }
+  });
+
+  test('Price precision boundary case', () => {
+    // Test for very small and very large price values
+    const extremePrices = [
+      new Decimal('0.0000001'), // Very small price
+      new Decimal('1000000'), // Very large price
+    ];
+
+    for (const price of extremePrices) {
+      const decimalsA = 6;
+      const decimalsB = 6;
+
+      // price -> tick -> sqrtPriceX64 -> price complete loop test
+      const tick = SqrtPriceMath.getTickFromPrice(price, decimalsA, decimalsB);
+      const sqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tick);
+      const calculatedPrice = SqrtPriceMath.sqrtPriceX64ToPrice(sqrtPriceX64, decimalsA, decimalsB);
+
+      // Verify that the relative error is within the acceptable range
+      const relativeError = price.minus(calculatedPrice).abs().div(price);
+      expect(relativeError.toNumber()).toBeLessThan(0.001); // 0.1% error
+    }
+  });
+});
+
+describe('SqrtPriceMath special case test', () => {
+  test('Non-integer tick handling', () => {
+    // Test for non-integer tick
+    expect(() => {
+      SqrtPriceMath.getSqrtPriceX64FromTick(1.5);
+    }).toThrow();
+  });
+});
+
+describe('SqrtPriceMath loop consistency test', () => {
+  test('price -> tick -> price conversion consistency', () => {
+    const testPrices = [
+      new Decimal('0.01'),
+      new Decimal('0.5'),
+      new Decimal('1'),
+      new Decimal('2'),
+      new Decimal('10'),
+      new Decimal('100'),
+    ];
+
+    const decimalsA = 6;
+    const decimalsB = 9;
+
+    for (const initialPrice of testPrices) {
+      // price -> tick
+      const tick = SqrtPriceMath.getTickFromPrice(initialPrice, decimalsA, decimalsB);
+
+      // tick -> sqrtPriceX64
+      const sqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tick);
+
+      // sqrtPriceX64 -> price
+      const finalPrice = SqrtPriceMath.sqrtPriceX64ToPrice(sqrtPriceX64, decimalsA, decimalsB);
+
+      // Verify that the relative error is within the acceptable range
+      const relativeError = initialPrice.minus(finalPrice).abs().div(initialPrice);
+      expect(relativeError.toNumber()).toBeLessThan(0.001); // 0.1% error
+    }
+  });
+
+  test('sqrtPriceX64 -> tick -> sqrtPriceX64 conversion consistency', () => {
+    // Generate a series of test sqrtPriceX64 values
+    const testValues = [
+      // Close to MIN_SQRT_PRICE_X64
+      MIN_SQRT_PRICE_X64.add(new BN('1000')),
+      // Middle value
+      new BN('79226673521066979257578248091').div(new BN(2)),
+      // Close to MAX_SQRT_PRICE_X64
+      MAX_SQRT_PRICE_X64.sub(new BN('1000')),
+    ];
+
+    for (const initialSqrtPriceX64 of testValues) {
+      // sqrtPriceX64 -> tick
+      const tick = SqrtPriceMath.getTickFromSqrtPriceX64(initialSqrtPriceX64);
+
+      // tick -> sqrtPriceX64
+      const finalSqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tick);
+
+      // Calculate relative error
+      const diff = initialSqrtPriceX64.sub(finalSqrtPriceX64).abs();
+
+      const relativeError = diff.mul(new BN(1000000)).div(initialSqrtPriceX64);
+
+      // Verify that the relative error is within the acceptable range
+      expect(relativeError.ltn(1000)).toBeTruthy(); // 0.1% error
+    }
+  });
+});

+ 194 - 0
src/lib/clmm-sdk/src/instructions/utils/sqrtPriceMath.ts

@@ -0,0 +1,194 @@
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import {
+  BIT_PRECISION,
+  LOG_B_2_X32,
+  LOG_B_P_ERR_MARGIN_LOWER_X64,
+  LOG_B_P_ERR_MARGIN_UPPER_X64,
+  MAX_SQRT_PRICE_X64,
+  MAX_TICK,
+  MaxUint128,
+  MIN_SQRT_PRICE_X64,
+  MIN_TICK,
+  ONE,
+  U64Resolution,
+  ZERO,
+} from '../constants.js';
+
+import { MathUtils } from './mathUtils.js';
+
+function mulRightShift(val: BN, mulBy: BN): BN {
+  return signedRightShift(val.mul(mulBy), 64, 256);
+}
+
+function signedLeftShift(n0: BN, shiftBy: number, bitWidth: number): BN {
+  const twosN0 = n0.toTwos(bitWidth).shln(shiftBy);
+  twosN0.imaskn(bitWidth + 1);
+  return twosN0.fromTwos(bitWidth);
+}
+
+function signedRightShift(n0: BN, shiftBy: number, bitWidth: number): BN {
+  const twoN0 = n0.toTwos(bitWidth).shrn(shiftBy);
+  twoN0.imaskn(bitWidth - shiftBy + 1);
+  return twoN0.fromTwos(bitWidth - shiftBy);
+}
+
+export class SqrtPriceMath {
+  public static sqrtPriceX64ToPrice(sqrtPriceX64: BN, decimalsA: number, decimalsB: number): Decimal {
+    return MathUtils.x64ToDecimal(sqrtPriceX64)
+      .pow(2)
+      .mul(Decimal.pow(10, decimalsA - decimalsB));
+  }
+
+  public static priceToSqrtPriceX64(price: Decimal, decimalsA: number, decimalsB: number): BN {
+    return MathUtils.decimalToX64(price.mul(Decimal.pow(10, decimalsB - decimalsA)).sqrt());
+  }
+
+  public static getNextSqrtPriceX64FromInput(sqrtPriceX64: BN, liquidity: BN, amountIn: BN, zeroForOne: boolean): BN {
+    if (!sqrtPriceX64.gt(ZERO)) {
+      throw new Error('sqrtPriceX64 must greater than 0');
+    }
+    if (!liquidity.gt(ZERO)) {
+      throw new Error('liquidity must greater than 0');
+    }
+
+    return zeroForOne
+      ? this.getNextSqrtPriceFromTokenAmountARoundingUp(sqrtPriceX64, liquidity, amountIn, true)
+      : this.getNextSqrtPriceFromTokenAmountBRoundingDown(sqrtPriceX64, liquidity, amountIn, true);
+  }
+
+  public static getNextSqrtPriceX64FromOutput(sqrtPriceX64: BN, liquidity: BN, amountOut: BN, zeroForOne: boolean): BN {
+    if (!sqrtPriceX64.gt(ZERO)) {
+      throw new Error('sqrtPriceX64 must greater than 0');
+    }
+    if (!liquidity.gt(ZERO)) {
+      throw new Error('liquidity must greater than 0');
+    }
+
+    return zeroForOne
+      ? this.getNextSqrtPriceFromTokenAmountBRoundingDown(sqrtPriceX64, liquidity, amountOut, false)
+      : this.getNextSqrtPriceFromTokenAmountARoundingUp(sqrtPriceX64, liquidity, amountOut, false);
+  }
+
+  private static getNextSqrtPriceFromTokenAmountARoundingUp(
+    sqrtPriceX64: BN,
+    liquidity: BN,
+    amount: BN,
+    add: boolean
+  ): BN {
+    if (amount.eq(ZERO)) return sqrtPriceX64;
+    const liquidityLeftShift = liquidity.shln(U64Resolution);
+
+    if (add) {
+      const numerator1 = liquidityLeftShift;
+      const denominator = liquidityLeftShift.add(amount.mul(sqrtPriceX64));
+      if (denominator.gte(numerator1)) {
+        return MathUtils.mulDivCeil(numerator1, sqrtPriceX64, denominator);
+      }
+      return MathUtils.mulDivRoundingUp(numerator1, ONE, numerator1.div(sqrtPriceX64).add(amount));
+    } else {
+      const amountMulSqrtPrice = amount.mul(sqrtPriceX64);
+      if (!liquidityLeftShift.gt(amountMulSqrtPrice)) {
+        throw new Error('getNextSqrtPriceFromTokenAmountARoundingUp,liquidityLeftShift must gt amountMulSqrtPrice');
+      }
+      const denominator = liquidityLeftShift.sub(amountMulSqrtPrice);
+      return MathUtils.mulDivCeil(liquidityLeftShift, sqrtPriceX64, denominator);
+    }
+  }
+
+  private static getNextSqrtPriceFromTokenAmountBRoundingDown(
+    sqrtPriceX64: BN,
+    liquidity: BN,
+    amount: BN,
+    add: boolean
+  ): BN {
+    const deltaY = amount.shln(U64Resolution);
+    if (add) {
+      return sqrtPriceX64.add(deltaY.div(liquidity));
+    } else {
+      const amountDivLiquidity = MathUtils.mulDivRoundingUp(deltaY, ONE, liquidity);
+      if (!sqrtPriceX64.gt(amountDivLiquidity)) {
+        throw new Error('getNextSqrtPriceFromTokenAmountBRoundingDown sqrtPriceX64 must gt amountDivLiquidity');
+      }
+      return sqrtPriceX64.sub(amountDivLiquidity);
+    }
+  }
+
+  public static getSqrtPriceX64FromTick(tick: number): BN {
+    if (!Number.isInteger(tick)) {
+      throw new Error('tick must be integer');
+    }
+    if (tick < MIN_TICK || tick > MAX_TICK) {
+      throw new Error('tick must be in MIN_TICK and MAX_TICK');
+    }
+    const tickAbs: number = tick < 0 ? tick * -1 : tick;
+
+    let ratio: BN = (tickAbs & 0x1) != 0 ? new BN('18445821805675395072') : new BN('18446744073709551616');
+    if ((tickAbs & 0x2) != 0) ratio = mulRightShift(ratio, new BN('18444899583751176192'));
+    if ((tickAbs & 0x4) != 0) ratio = mulRightShift(ratio, new BN('18443055278223355904'));
+    if ((tickAbs & 0x8) != 0) ratio = mulRightShift(ratio, new BN('18439367220385607680'));
+    if ((tickAbs & 0x10) != 0) ratio = mulRightShift(ratio, new BN('18431993317065453568'));
+    if ((tickAbs & 0x20) != 0) ratio = mulRightShift(ratio, new BN('18417254355718170624'));
+    if ((tickAbs & 0x40) != 0) ratio = mulRightShift(ratio, new BN('18387811781193609216'));
+    if ((tickAbs & 0x80) != 0) ratio = mulRightShift(ratio, new BN('18329067761203558400'));
+    if ((tickAbs & 0x100) != 0) ratio = mulRightShift(ratio, new BN('18212142134806163456'));
+    if ((tickAbs & 0x200) != 0) ratio = mulRightShift(ratio, new BN('17980523815641700352'));
+    if ((tickAbs & 0x400) != 0) ratio = mulRightShift(ratio, new BN('17526086738831433728'));
+    if ((tickAbs & 0x800) != 0) ratio = mulRightShift(ratio, new BN('16651378430235570176'));
+    if ((tickAbs & 0x1000) != 0) ratio = mulRightShift(ratio, new BN('15030750278694412288'));
+    if ((tickAbs & 0x2000) != 0) ratio = mulRightShift(ratio, new BN('12247334978884435968'));
+    if ((tickAbs & 0x4000) != 0) ratio = mulRightShift(ratio, new BN('8131365268886854656'));
+    if ((tickAbs & 0x8000) != 0) ratio = mulRightShift(ratio, new BN('3584323654725218816'));
+    if ((tickAbs & 0x10000) != 0) ratio = mulRightShift(ratio, new BN('696457651848324352'));
+    if ((tickAbs & 0x20000) != 0) ratio = mulRightShift(ratio, new BN('26294789957507116'));
+    if ((tickAbs & 0x40000) != 0) ratio = mulRightShift(ratio, new BN('37481735321082'));
+
+    if (tick > 0) ratio = MaxUint128.div(ratio);
+    return ratio;
+  }
+
+  public static getTickFromPrice(price: Decimal, decimalsA: number, decimalsB: number): number {
+    return SqrtPriceMath.getTickFromSqrtPriceX64(SqrtPriceMath.priceToSqrtPriceX64(price, decimalsA, decimalsB));
+  }
+
+  public static getTickFromSqrtPriceX64(sqrtPriceX64: BN): number {
+    if (sqrtPriceX64.gt(MAX_SQRT_PRICE_X64) || sqrtPriceX64.lt(MIN_SQRT_PRICE_X64)) {
+      throw new Error('Provided sqrtPrice is not within the supported sqrtPrice range.');
+    }
+
+    const msb = sqrtPriceX64.bitLength() - 1;
+    const adjustedMsb = new BN(msb - 64);
+    const log2pIntegerX32 = signedLeftShift(adjustedMsb, 32, 128);
+
+    let bit = new BN('8000000000000000', 'hex');
+    let precision = 0;
+    let log2pFractionX64 = new BN(0);
+
+    let r = msb >= 64 ? sqrtPriceX64.shrn(msb - 63) : sqrtPriceX64.shln(63 - msb);
+
+    while (bit.gt(new BN(0)) && precision < BIT_PRECISION) {
+      r = r.mul(r);
+      const rMoreThanTwo = r.shrn(127);
+      r = r.shrn(63 + rMoreThanTwo.toNumber());
+      log2pFractionX64 = log2pFractionX64.add(bit.mul(rMoreThanTwo));
+      bit = bit.shrn(1);
+      precision += 1;
+    }
+
+    const log2pFractionX32 = log2pFractionX64.shrn(32);
+
+    const log2pX32 = log2pIntegerX32.add(log2pFractionX32);
+    const logbpX64 = log2pX32.mul(new BN(LOG_B_2_X32));
+
+    const tickLow = signedRightShift(logbpX64.sub(new BN(LOG_B_P_ERR_MARGIN_LOWER_X64)), 64, 128).toNumber();
+    const tickHigh = signedRightShift(logbpX64.add(new BN(LOG_B_P_ERR_MARGIN_UPPER_X64)), 64, 128).toNumber();
+
+    if (tickLow == tickHigh) {
+      return tickLow;
+    } else {
+      const derivedTickHighSqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tickHigh);
+      return derivedTickHighSqrtPriceX64.lte(sqrtPriceX64) ? tickHigh : tickLow;
+    }
+  }
+}

+ 393 - 0
src/lib/clmm-sdk/src/instructions/utils/swapMath.ts

@@ -0,0 +1,393 @@
+import { PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+
+import {
+  FEE_RATE_DENOMINATOR,
+  MAX_SQRT_PRICE_X64,
+  MAX_TICK,
+  MIN_SQRT_PRICE_X64,
+  MIN_TICK,
+  NEGATIVE_ONE,
+  ONE,
+  ZERO,
+} from '../constants.js';
+import { getPdaTickArrayAddress } from '../pda.js';
+
+import { LiquidityMath } from './liquidityMath.js';
+import { MathUtils } from './mathUtils.js';
+import { StepComputations, Tick, TickArrayContainer, TickArrayBitmapExtensionType } from './models.js';
+import { PoolUtils } from './poolUtils.js';
+import { SqrtPriceMath } from './sqrtPriceMath.js';
+import { TickQuery, TickUtils } from './tick.js';
+
+type SwapStep = {
+  sqrtPriceX64Next: BN;
+  amountIn: BN;
+  amountOut: BN;
+  feeAmount: BN;
+};
+
+export abstract class SwapMath {
+  /**
+   * Calculate swap path and output amount
+   *
+   * @param programId - Public key address of the CLMM program
+   * @param poolId - Public key address of the liquidity pool
+   * @param tickArrayCache - Tick array cache with keys as tick array start indices
+   * @param tickArrayBitmap - Tick array bitmap for quickly finding initialized ticks
+   * @param tickarrayBitmapExtension - Tick array bitmap extension information
+   * @param zeroForOne - Swap direction: true means token0 to token1, false means token1 to token0
+   * @param fee - Transaction fee rate (base 1000000)
+   * @param liquidity - Current liquidity amount
+   * @param currentTick - Current tick position
+   * @param tickSpacing - Tick spacing
+   * @param currentSqrtPriceX64 - Square root of current price (Q64.64 format)
+   * @param amountSpecified - Specified swap amount (positive for input amount, negative for output amount)
+   * @param lastSavedTickArrayStartIndex - Last saved tick array start index
+   * @param sqrtPriceLimitX64 - Square root of price limit (Q64.64 format), optional parameter
+   * @param catchLiquidityInsufficient - Whether to catch liquidity insufficient errors, default false
+   *
+   * @returns Swap calculation result object
+   * @returns allTrade - Whether all trades were completed
+   * @returns amountSpecifiedRemaining - Remaining untraded amount
+   * @returns amountCalculated - Calculated amount of the other token
+   * @returns feeAmount - Total fees
+   * @returns sqrtPriceX64 - Square root of price after swap (Q64.64 format)
+   * @returns liquidity - Liquidity after swap
+   * @returns tickCurrent - Tick position after swap
+   * @returns accounts - List of tick array accounts that need to be accessed
+   */
+  public static swapCompute(
+    programId: PublicKey,
+    poolId: PublicKey,
+    tickArrayInfo: { [key: string]: TickArrayContainer },
+    tickArrayBitmap: BN[],
+    tickarrayBitmapExtension: TickArrayBitmapExtensionType,
+    zeroForOne: boolean,
+    fee: number,
+    liquidity: BN,
+    currentTick: number,
+    tickSpacing: number,
+    currentSqrtPriceX64: BN,
+    amountSpecified: BN,
+    lastSavedTickArrayStartIndex: number,
+    sqrtPriceLimitX64?: BN,
+    catchLiquidityInsufficient = false
+  ): {
+    allTrade: boolean;
+    amountSpecifiedRemaining: BN;
+    amountCalculated: BN;
+    feeAmount: BN;
+    sqrtPriceX64: BN;
+    liquidity: BN;
+    tickCurrent: number;
+    accounts: PublicKey[];
+  } {
+    if (amountSpecified.eq(ZERO)) {
+      throw new Error('amountSpecified must not be 0');
+    }
+    if (!sqrtPriceLimitX64) sqrtPriceLimitX64 = zeroForOne ? MIN_SQRT_PRICE_X64.add(ONE) : MAX_SQRT_PRICE_X64.sub(ONE);
+
+    if (zeroForOne) {
+      if (sqrtPriceLimitX64.lt(MIN_SQRT_PRICE_X64)) {
+        throw new Error('sqrtPriceX64 must greater than MIN_SQRT_PRICE_X64');
+      }
+
+      if (sqrtPriceLimitX64.gte(currentSqrtPriceX64)) {
+        throw new Error('sqrtPriceX64 must smaller than current');
+      }
+    } else {
+      if (sqrtPriceLimitX64.gt(MAX_SQRT_PRICE_X64)) {
+        throw new Error('sqrtPriceX64 must smaller than MAX_SQRT_PRICE_X64');
+      }
+
+      if (sqrtPriceLimitX64.lte(currentSqrtPriceX64)) {
+        throw new Error('sqrtPriceX64 must greater than current');
+      }
+    }
+
+    const baseInput = amountSpecified.gt(ZERO);
+
+    const state = {
+      amountSpecifiedRemaining: amountSpecified,
+      amountCalculated: ZERO,
+      sqrtPriceX64: currentSqrtPriceX64,
+      tick:
+        currentTick > lastSavedTickArrayStartIndex
+          ? Math.min(lastSavedTickArrayStartIndex + TickQuery.tickCount(tickSpacing) - 1, currentTick)
+          : lastSavedTickArrayStartIndex,
+      accounts: [] as PublicKey[],
+      liquidity,
+      feeAmount: new BN(0),
+    };
+    let tickAarrayStartIndex = lastSavedTickArrayStartIndex;
+    let tickArrayCurrent = tickArrayInfo[lastSavedTickArrayStartIndex];
+
+    // Verify if tickArrayInfo contains necessary data
+    if (!tickArrayCurrent) {
+      throw new Error(
+        `TickArray not found in cache for startIndex: ${lastSavedTickArrayStartIndex}. Please ensure tickArrayCache is properly initialized.`
+      );
+    }
+
+    let loopCount = 0;
+    let t = !zeroForOne && tickArrayCurrent.data.startTickIndex === state.tick;
+    while (
+      !state.amountSpecifiedRemaining.eq(ZERO) &&
+      !state.sqrtPriceX64.eq(sqrtPriceLimitX64)
+      // state.tick < MAX_TICK &&
+      // state.tick > MIN_TICK
+    ) {
+      if (loopCount > 10) {
+        // throw Error('liquidity limit')
+      }
+      const step: Partial<StepComputations> = {};
+      step.sqrtPriceStartX64 = state.sqrtPriceX64;
+
+      const tickState: Tick | null = TickUtils.nextInitTick(tickArrayCurrent, state.tick, tickSpacing, zeroForOne, t);
+
+      let nextInitTick: Tick | null = tickState ? tickState : null; // TickUtils.firstInitializedTick(tickArrayCurrent, zeroForOne)
+      let tickArrayAddress: null | PublicKey = null;
+
+      if (!nextInitTick?.liquidityGross.gtn(0)) {
+        const nextInitTickArrayIndex = PoolUtils.nextInitializedTickArrayStartIndex(
+          {
+            tickCurrent: state.tick,
+            tickSpacing,
+            tickArrayBitmap,
+            exBitmapInfo: tickarrayBitmapExtension,
+          },
+          tickAarrayStartIndex,
+          zeroForOne
+        );
+        if (!nextInitTickArrayIndex.isExist) {
+          if (catchLiquidityInsufficient) {
+            return {
+              allTrade: false,
+              amountSpecifiedRemaining: state.amountSpecifiedRemaining,
+              amountCalculated: state.amountCalculated,
+              feeAmount: state.feeAmount,
+              sqrtPriceX64: state.sqrtPriceX64,
+              liquidity: state.liquidity,
+              tickCurrent: state.tick,
+              accounts: state.accounts,
+            };
+          }
+          throw Error('swapCompute LiquidityInsufficient');
+        }
+        tickAarrayStartIndex = nextInitTickArrayIndex.nextStartIndex;
+
+        const { publicKey: expectedNextTickArrayAddress } = getPdaTickArrayAddress(
+          programId,
+          poolId,
+          tickAarrayStartIndex
+        );
+        tickArrayAddress = expectedNextTickArrayAddress;
+        tickArrayCurrent = tickArrayInfo[tickAarrayStartIndex];
+
+        // Verify if new tick array is in cache
+        if (!tickArrayCurrent) {
+          throw new Error(
+            `TickArray not found in cache for startIndex: ${tickAarrayStartIndex}. Missing tick array data during swap computation.`
+          );
+        }
+
+        try {
+          nextInitTick = TickUtils.firstInitializedTick(tickArrayCurrent, zeroForOne);
+        } catch (error) {
+          throw new Error(
+            `Failed to find initialized tick in array ${tickAarrayStartIndex}: ${
+              error instanceof Error ? error.message : 'Unknown error'
+            }`
+          );
+        }
+      }
+
+      step.tickNext = nextInitTick.tick;
+      step.initialized = nextInitTick.liquidityGross.gtn(0);
+      if (lastSavedTickArrayStartIndex !== tickAarrayStartIndex && tickArrayAddress) {
+        state.accounts.push(tickArrayAddress);
+        lastSavedTickArrayStartIndex = tickAarrayStartIndex;
+      }
+      if (step.tickNext < MIN_TICK) {
+        step.tickNext = MIN_TICK;
+      } else if (step.tickNext > MAX_TICK) {
+        step.tickNext = MAX_TICK;
+      }
+
+      step.sqrtPriceNextX64 = SqrtPriceMath.getSqrtPriceX64FromTick(step.tickNext);
+      let targetPrice: BN;
+      if (
+        (zeroForOne && step.sqrtPriceNextX64.lt(sqrtPriceLimitX64)) ||
+        (!zeroForOne && step.sqrtPriceNextX64.gt(sqrtPriceLimitX64))
+      ) {
+        targetPrice = sqrtPriceLimitX64;
+      } else {
+        targetPrice = step.sqrtPriceNextX64;
+      }
+      [state.sqrtPriceX64, step.amountIn, step.amountOut, step.feeAmount] = SwapMath.swapStepCompute(
+        state.sqrtPriceX64,
+        targetPrice,
+        state.liquidity,
+        state.amountSpecifiedRemaining,
+        fee,
+        zeroForOne
+      );
+
+      state.feeAmount = state.feeAmount.add(step.feeAmount);
+
+      if (baseInput) {
+        state.amountSpecifiedRemaining = state.amountSpecifiedRemaining.sub(step.amountIn.add(step.feeAmount));
+        state.amountCalculated = state.amountCalculated.sub(step.amountOut);
+      } else {
+        state.amountSpecifiedRemaining = state.amountSpecifiedRemaining.add(step.amountOut);
+        state.amountCalculated = state.amountCalculated.add(step.amountIn.add(step.feeAmount));
+      }
+      if (state.sqrtPriceX64.eq(step.sqrtPriceNextX64)) {
+        if (step.initialized) {
+          let liquidityNet = nextInitTick.liquidityNet;
+          if (zeroForOne) liquidityNet = liquidityNet.mul(NEGATIVE_ONE);
+          state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet);
+        }
+
+        t = step.tickNext != state.tick && !zeroForOne && tickArrayCurrent.data.startTickIndex === step.tickNext;
+        state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext; //
+      } else if (state.sqrtPriceX64 != step.sqrtPriceStartX64) {
+        const _T = SqrtPriceMath.getTickFromSqrtPriceX64(state.sqrtPriceX64);
+        t = _T != state.tick && !zeroForOne && tickArrayCurrent.data.startTickIndex === _T;
+        state.tick = _T;
+      }
+      ++loopCount;
+    }
+
+    try {
+      const { nextStartIndex: tickAarrayStartIndex, isExist } = TickQuery.nextInitializedTickArray(
+        state.tick,
+        tickSpacing,
+        zeroForOne,
+        tickArrayBitmap,
+        tickarrayBitmapExtension
+      );
+      if (isExist && lastSavedTickArrayStartIndex !== tickAarrayStartIndex) {
+        state.accounts.push(getPdaTickArrayAddress(programId, poolId, tickAarrayStartIndex).publicKey);
+        lastSavedTickArrayStartIndex = tickAarrayStartIndex;
+      }
+    } catch (e) {
+      /* empty */
+    }
+
+    return {
+      allTrade: true,
+      amountSpecifiedRemaining: ZERO,
+      amountCalculated: state.amountCalculated,
+      feeAmount: state.feeAmount,
+      sqrtPriceX64: state.sqrtPriceX64,
+      liquidity: state.liquidity,
+      tickCurrent: state.tick,
+      accounts: state.accounts,
+    };
+  }
+
+  private static swapStepCompute(
+    sqrtPriceX64Current: BN,
+    sqrtPriceX64Target: BN,
+    liquidity: BN,
+    amountRemaining: BN,
+    feeRate: number, // Base 1000000
+    zeroForOne: boolean
+  ): [BN, BN, BN, BN] {
+    const swapStep: SwapStep = {
+      sqrtPriceX64Next: new BN(0),
+      amountIn: new BN(0),
+      amountOut: new BN(0),
+      feeAmount: new BN(0),
+    };
+
+    const baseInput = amountRemaining.gte(ZERO);
+
+    if (baseInput) {
+      const amountRemainingSubtractFee = MathUtils.mulDivFloor(
+        amountRemaining,
+        FEE_RATE_DENOMINATOR.sub(new BN(feeRate.toString())),
+        FEE_RATE_DENOMINATOR
+      );
+      swapStep.amountIn = zeroForOne
+        ? LiquidityMath.getTokenAmountAFromLiquidity(sqrtPriceX64Target, sqrtPriceX64Current, liquidity, true)
+        : LiquidityMath.getTokenAmountBFromLiquidity(sqrtPriceX64Current, sqrtPriceX64Target, liquidity, true);
+      if (amountRemainingSubtractFee.gte(swapStep.amountIn)) {
+        swapStep.sqrtPriceX64Next = sqrtPriceX64Target;
+      } else {
+        swapStep.sqrtPriceX64Next = SqrtPriceMath.getNextSqrtPriceX64FromInput(
+          sqrtPriceX64Current,
+          liquidity,
+          amountRemainingSubtractFee,
+          zeroForOne
+        );
+      }
+    } else {
+      swapStep.amountOut = zeroForOne
+        ? LiquidityMath.getTokenAmountBFromLiquidity(sqrtPriceX64Target, sqrtPriceX64Current, liquidity, false)
+        : LiquidityMath.getTokenAmountAFromLiquidity(sqrtPriceX64Current, sqrtPriceX64Target, liquidity, false);
+      if (amountRemaining.mul(NEGATIVE_ONE).gte(swapStep.amountOut)) {
+        swapStep.sqrtPriceX64Next = sqrtPriceX64Target;
+      } else {
+        swapStep.sqrtPriceX64Next = SqrtPriceMath.getNextSqrtPriceX64FromOutput(
+          sqrtPriceX64Current,
+          liquidity,
+          amountRemaining.mul(NEGATIVE_ONE),
+          zeroForOne
+        );
+      }
+    }
+
+    const reachTargetPrice = sqrtPriceX64Target.eq(swapStep.sqrtPriceX64Next);
+
+    if (zeroForOne) {
+      if (!(reachTargetPrice && baseInput)) {
+        swapStep.amountIn = LiquidityMath.getTokenAmountAFromLiquidity(
+          swapStep.sqrtPriceX64Next,
+          sqrtPriceX64Current,
+          liquidity,
+          true
+        );
+      }
+
+      if (!(reachTargetPrice && !baseInput)) {
+        swapStep.amountOut = LiquidityMath.getTokenAmountBFromLiquidity(
+          swapStep.sqrtPriceX64Next,
+          sqrtPriceX64Current,
+          liquidity,
+          false
+        );
+      }
+    } else {
+      swapStep.amountIn =
+        reachTargetPrice && baseInput
+          ? swapStep.amountIn
+          : LiquidityMath.getTokenAmountBFromLiquidity(sqrtPriceX64Current, swapStep.sqrtPriceX64Next, liquidity, true);
+      swapStep.amountOut =
+        reachTargetPrice && !baseInput
+          ? swapStep.amountOut
+          : LiquidityMath.getTokenAmountAFromLiquidity(
+              sqrtPriceX64Current,
+              swapStep.sqrtPriceX64Next,
+              liquidity,
+              false
+            );
+    }
+
+    if (!baseInput && swapStep.amountOut.gt(amountRemaining.mul(NEGATIVE_ONE))) {
+      swapStep.amountOut = amountRemaining.mul(NEGATIVE_ONE);
+    }
+    if (baseInput && !swapStep.sqrtPriceX64Next.eq(sqrtPriceX64Target)) {
+      swapStep.feeAmount = amountRemaining.sub(swapStep.amountIn);
+    } else {
+      swapStep.feeAmount = MathUtils.mulDivCeil(
+        swapStep.amountIn,
+        new BN(feeRate),
+        FEE_RATE_DENOMINATOR.sub(new BN(feeRate))
+      );
+    }
+    return [swapStep.sqrtPriceX64Next, swapStep.amountIn, swapStep.amountOut, swapStep.feeAmount];
+  }
+}

+ 567 - 0
src/lib/clmm-sdk/src/instructions/utils/tick.ts

@@ -0,0 +1,567 @@
+import { PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import { TICK_ARRAY_SIZE, TICK_ARRAY_BITMAP_SIZE } from '../../constants.js';
+import { MAX_TICK, MIN_TICK } from '../constants.js';
+import { IPoolLayout } from '../layout.js';
+import { TickArrayBitmapExtensionType } from './models.js';
+import { TickArrayBitmapExtensionUtils } from './tickarrayBitmap.js';
+import { getPdaTickArrayAddress } from '../pda.js';
+
+import {
+  ReturnTypeGetPriceAndTick,
+  Tick,
+  TickArray,
+  TickArrayState,
+  TickState,
+  TickArrayContainer,
+  DynTickArray,
+} from './models.js';
+import { SqrtPriceMath } from './sqrtPriceMath.js';
+import { TickMath } from './tickMath.js';
+
+export class TickUtils {
+  // Calculate the address of the TickArray corresponding to the given tick index
+  public static getTickArrayAddressByTick(
+    programId: PublicKey,
+    poolId: PublicKey,
+    tickIndex: number,
+    tickSpacing: number
+  ): PublicKey {
+    const startIndex = TickUtils.getTickArrayStartIndexByTick(tickIndex, tickSpacing);
+    const { publicKey: tickArrayAddress } = getPdaTickArrayAddress(programId, poolId, startIndex);
+    return tickArrayAddress;
+  }
+
+  // Calculate the offset of the given tick index within the corresponding TickArray
+  public static getTickOffsetInArray(tickIndex: number, tickSpacing: number): number {
+    if (tickIndex % tickSpacing != 0) {
+      throw new Error('tickIndex % tickSpacing not equal 0');
+    }
+    const startTickIndex = TickUtils.getTickArrayStartIndexByTick(tickIndex, tickSpacing);
+    const offsetInArray = Math.floor((tickIndex - startTickIndex) / tickSpacing);
+    if (offsetInArray < 0 || offsetInArray >= TICK_ARRAY_SIZE) {
+      throw new Error('tick offset in array overflow');
+    }
+    return offsetInArray;
+  }
+
+  // Calculate the index position of the TickArray corresponding to the given tick index in the bitmap.
+  public static getTickArrayBitIndex(tickIndex: number, tickSpacing: number): number {
+    const ticksInArray = TickQuery.tickCount(tickSpacing);
+
+    let startIndex: number = tickIndex / ticksInArray;
+    if (tickIndex < 0 && tickIndex % ticksInArray != 0) {
+      startIndex = Math.ceil(startIndex) - 1;
+    } else {
+      startIndex = Math.floor(startIndex);
+    }
+    return startIndex;
+  }
+
+  // Calculate the start index of the TickArray corresponding to the given tick index
+  public static getTickArrayStartIndexByTick(tickIndex: number, tickSpacing: number): number {
+    return this.getTickArrayBitIndex(tickIndex, tickSpacing) * TickQuery.tickCount(tickSpacing);
+  }
+
+  // Calculate the offset of the TickArray corresponding to the given tick index in the default bitmap
+  public static getTickArrayOffsetInBitmapByTick(tick: number, tickSpacing: number): number {
+    const multiplier = tickSpacing * TICK_ARRAY_SIZE;
+    const compressed = Math.floor(tick / multiplier) + 512;
+    return Math.abs(compressed);
+  }
+
+  // Check if the TickArray corresponding to the given tick index has been initialized
+  public static checkTickArrayIsInitialized(
+    bitmap: BN,
+    tick: number,
+    tickSpacing: number
+  ): {
+    isInitialized: boolean;
+    startIndex: number;
+  } {
+    const multiplier = tickSpacing * TICK_ARRAY_SIZE;
+    const compressed = Math.floor(tick / multiplier) + 512;
+    const bitPos = Math.abs(compressed);
+    return {
+      isInitialized: bitmap.testn(bitPos),
+      startIndex: (bitPos - 512) * multiplier,
+    };
+  }
+
+  // Calculate the start index of the next TickArray
+  // Can be ignored, referenced by nextInitializedTick, but nextInitializedTick is not used
+  public static getNextTickArrayStartIndex(
+    lastTickArrayStartIndex: number,
+    tickSpacing: number,
+    zeroForOne: boolean
+  ): number {
+    return zeroForOne
+      ? lastTickArrayStartIndex - tickSpacing * TICK_ARRAY_SIZE
+      : lastTickArrayStartIndex + tickSpacing * TICK_ARRAY_SIZE;
+  }
+
+  // Merge bitmaps of multiple TickArrays
+  public static mergeTickArrayBitmap(bns: BN[]): BN {
+    let b = new BN(0);
+    for (let i = 0; i < bns.length; i++) {
+      b = b.add(bns[i].shln(64 * i));
+    }
+    return b;
+  }
+
+  /**
+   * Search for initialized TickArrays around the current position to find the starting indices of a specified number of initialized TickArrays
+   *
+   * @param tickArrayBitmap - Main tick array bitmap used to mark initialized tick arrays
+   * @param exTickArrayBitmap - Extended tick array bitmap containing positive and negative direction extension bitmap information
+   * @param tickSpacing - Tick spacing
+   * @param tickArrayStartIndex - Starting tick array index for the search
+   * @param expectedCount - Expected number of initialized TickArrays to find
+   *
+   * @returns Array of initialized TickArray starting indices
+   *
+   * @description
+   * This function is used to locate active liquidity around the current price. It searches on both sides of the given starting position:
+   * - Search right (higher tick positions)
+   * - Search left (lower tick positions)
+   * By combining the search results from both directions, it provides the liquidity distribution around the current price
+   */
+  public static getInitializedTickArrayInRange(
+    tickArrayBitmap: BN[],
+    exTickArrayBitmap: TickArrayBitmapExtensionType,
+    tickSpacing: number,
+    tickArrayStartIndex: number,
+    expectedCount: number
+  ): number[] {
+    const tickArrayOffset = Math.floor(tickArrayStartIndex / (tickSpacing * TICK_ARRAY_SIZE));
+    return [
+      // find right of currenct offset
+      ...TickUtils.searchLowBitFromStart(
+        tickArrayBitmap,
+        exTickArrayBitmap,
+        tickArrayOffset - 1,
+        expectedCount,
+        tickSpacing
+      ),
+
+      // find left of current offset
+      ...TickUtils.searchHightBitFromStart(
+        tickArrayBitmap,
+        exTickArrayBitmap,
+        tickArrayOffset,
+        expectedCount,
+        tickSpacing
+      ),
+    ];
+  }
+
+  // Get start indices of all initialized TickArrays
+  // TODO: The TICK_ARRAY_BITMAP_SIZE passed in this function may not be able to retrieve all initialized TickArrays
+  public static getAllInitializedTickArrayStartIndex(
+    tickArrayBitmap: BN[],
+    exTickArrayBitmap: TickArrayBitmapExtensionType,
+    tickSpacing: number
+  ): number[] {
+    // find from offset 0 to 1024
+    return TickUtils.searchHightBitFromStart(
+      tickArrayBitmap,
+      exTickArrayBitmap,
+      -7680,
+      TICK_ARRAY_BITMAP_SIZE,
+      tickSpacing
+    );
+  }
+
+  // Get information of all initialized TickArrays
+  public static getAllInitializedTickArrayInfo(
+    programId: PublicKey,
+    poolId: PublicKey,
+    tickArrayBitmap: BN[],
+    exTickArrayBitmap: TickArrayBitmapExtensionType,
+    tickSpacing: number
+  ): {
+    tickArrayStartIndex: number;
+    tickArrayAddress: PublicKey;
+  }[] {
+    const result: {
+      tickArrayStartIndex: number;
+      tickArrayAddress: PublicKey;
+    }[] = [];
+    const allInitializedTickArrayIndex: number[] = TickUtils.getAllInitializedTickArrayStartIndex(
+      tickArrayBitmap,
+      exTickArrayBitmap,
+      tickSpacing
+    );
+    for (const startIndex of allInitializedTickArrayIndex) {
+      const { publicKey: address } = getPdaTickArrayAddress(programId, poolId, startIndex);
+      result.push({
+        tickArrayStartIndex: startIndex,
+        tickArrayAddress: address,
+      });
+    }
+    return result;
+  }
+
+  // Get all initialized Ticks in the given TickArray
+  public static getAllInitializedTickInTickArray(tickArray: TickArrayState): TickState[] {
+    return tickArray.ticks.filter((i) => i.liquidityGross.gtn(0));
+  }
+
+  /**
+   * Search for initialized TickArrays to the left from the starting position
+   * Used to locate active liquidity near the current price
+   */
+  public static searchLowBitFromStart(
+    tickArrayBitmap: BN[],
+    exTickArrayBitmap: TickArrayBitmapExtensionType,
+    currentTickArrayBitStartIndex: number,
+    expectedCount: number,
+    tickSpacing: number
+  ): number[] {
+    const tickArrayBitmaps = [
+      ...[...exTickArrayBitmap.negativeTickArrayBitmap].reverse(),
+      tickArrayBitmap.slice(0, 8),
+      tickArrayBitmap.slice(8, 16),
+      ...exTickArrayBitmap.positiveTickArrayBitmap,
+    ].map((i) => TickUtils.mergeTickArrayBitmap(i));
+    const result: number[] = [];
+    while (currentTickArrayBitStartIndex >= -7680) {
+      const arrayIndex = Math.floor((currentTickArrayBitStartIndex + 7680) / 512);
+      const searchIndex = (currentTickArrayBitStartIndex + 7680) % 512;
+
+      if (tickArrayBitmaps[arrayIndex].testn(searchIndex)) result.push(currentTickArrayBitStartIndex);
+
+      currentTickArrayBitStartIndex--;
+      if (result.length === expectedCount) break;
+    }
+
+    const tickCount = TickQuery.tickCount(tickSpacing);
+    return result.map((i) => i * tickCount);
+  }
+
+  /**
+   * Search for initialized TickArrays to the right from the starting position
+   * Used to locate active liquidity near the current price
+   */
+  public static searchHightBitFromStart(
+    tickArrayBitmap: BN[],
+    exTickArrayBitmap: TickArrayBitmapExtensionType,
+    currentTickArrayBitStartIndex: number,
+    expectedCount: number,
+    tickSpacing: number
+  ): number[] {
+    const tickArrayBitmaps = [
+      ...[...exTickArrayBitmap.negativeTickArrayBitmap].reverse(),
+      tickArrayBitmap.slice(0, 8),
+      tickArrayBitmap.slice(8, 16),
+      ...exTickArrayBitmap.positiveTickArrayBitmap,
+    ].map((i) => TickUtils.mergeTickArrayBitmap(i));
+    const result: number[] = [];
+    while (currentTickArrayBitStartIndex < 7680) {
+      const arrayIndex = Math.floor((currentTickArrayBitStartIndex + 7680) / 512);
+      const searchIndex = (currentTickArrayBitStartIndex + 7680) % 512;
+
+      if (tickArrayBitmaps[arrayIndex].testn(searchIndex)) result.push(currentTickArrayBitStartIndex);
+
+      currentTickArrayBitStartIndex++;
+      if (result.length === expectedCount) break;
+    }
+
+    const tickCount = TickQuery.tickCount(tickSpacing);
+    return result.map((i) => i * tickCount);
+  }
+
+  // Check if the given tick index is out of bounds
+  public static checkIsOutOfBoundary(tick: number): boolean {
+    return tick < MIN_TICK || tick > MAX_TICK;
+  }
+
+  /**
+   * Get the next initialized Tick
+   * Used to locate active liquidity near the current price
+   * Now supports both fixed and dynamic tick arrays through the container pattern
+   */
+  public static nextInitTick(
+    tickArrayCurrent: TickArrayContainer,
+    currentTickIndex: number,
+    tickSpacing: number,
+    zeroForOne: boolean,
+    t: boolean
+  ): Tick | null {
+    if (tickArrayCurrent.type === 'Fixed') {
+      return this._nextInitTickFixed(tickArrayCurrent.data, currentTickIndex, tickSpacing, zeroForOne, t);
+    } else {
+      return this._nextInitTickDynamic(tickArrayCurrent.data, currentTickIndex, tickSpacing, zeroForOne, t);
+    }
+  }
+
+  /**
+   * Fixed tick array implementation (original logic)
+   */
+  private static _nextInitTickFixed(
+    tickArray: TickArray,
+    currentTickIndex: number,
+    tickSpacing: number,
+    zeroForOne: boolean,
+    t: boolean
+  ): Tick | null {
+    const currentTickArrayStartIndex = TickQuery.getArrayStartIndex(currentTickIndex, tickSpacing);
+    if (currentTickArrayStartIndex != tickArray.startTickIndex) {
+      return null;
+    }
+    let offsetInArray = Math.floor((currentTickIndex - tickArray.startTickIndex) / tickSpacing);
+
+    if (zeroForOne) {
+      while (offsetInArray >= 0) {
+        if (tickArray.ticks[offsetInArray].liquidityGross.gtn(0)) {
+          return tickArray.ticks[offsetInArray];
+        }
+        offsetInArray = offsetInArray - 1;
+      }
+    } else {
+      if (!t) offsetInArray = offsetInArray + 1;
+      while (offsetInArray < TICK_ARRAY_SIZE) {
+        if (tickArray.ticks[offsetInArray].liquidityGross.gtn(0)) {
+          return tickArray.ticks[offsetInArray];
+        }
+        offsetInArray = offsetInArray + 1;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Dynamic tick array implementation
+   * Uses the mapping table (tickOffsetIndex) to find allocated ticks
+   */
+  private static _nextInitTickDynamic(
+    dynTickArray: DynTickArray,
+    currentTickIndex: number,
+    tickSpacing: number,
+    zeroForOne: boolean,
+    t: boolean
+  ): Tick | null {
+    const currentTickArrayStartIndex = TickQuery.getArrayStartIndex(currentTickIndex, tickSpacing);
+    if (currentTickArrayStartIndex !== dynTickArray.startTickIndex) {
+      return null;
+    }
+
+    let offsetInArray = Math.floor((currentTickIndex - dynTickArray.startTickIndex) / tickSpacing);
+
+    if (zeroForOne) {
+      while (offsetInArray >= 0) {
+        // Check mapping table
+        const physicalIndex = dynTickArray.tickOffsetIndex[offsetInArray];
+
+        if (physicalIndex > 0) {
+          const tick = dynTickArray.ticks[physicalIndex - 1];
+          if (tick.liquidityGross.gtn(0)) {
+            return tick;
+          }
+        }
+
+        offsetInArray = offsetInArray - 1;
+      }
+    } else {
+      if (!t) offsetInArray = offsetInArray + 1;
+
+      while (offsetInArray < TICK_ARRAY_SIZE) {
+        const physicalIndex = dynTickArray.tickOffsetIndex[offsetInArray];
+
+        if (physicalIndex > 0) {
+          const tick = dynTickArray.ticks[physicalIndex - 1];
+          if (tick.liquidityGross.gtn(0)) {
+            return tick;
+          }
+        }
+
+        offsetInArray = offsetInArray + 1;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Find the first initialized Tick in the given TickArray, where "first" is defined based on the trading direction (zeroForOne)
+   * Now supports both fixed and dynamic tick arrays through the container pattern
+   */
+  public static firstInitializedTick(tickArrayCurrent: TickArrayContainer, zeroForOne: boolean): Tick {
+    if (tickArrayCurrent.type === 'Fixed') {
+      return this._firstInitializedTickFixed(tickArrayCurrent.data, zeroForOne);
+    } else {
+      return this._firstInitializedTickDynamic(tickArrayCurrent.data, zeroForOne);
+    }
+  }
+
+  /**
+   * Fixed tick array implementation (original logic)
+   */
+  private static _firstInitializedTickFixed(tickArray: TickArray, zeroForOne: boolean): Tick {
+    if (zeroForOne) {
+      let i = TICK_ARRAY_SIZE - 1;
+      while (i >= 0) {
+        if (tickArray.ticks[i].liquidityGross.gtn(0)) {
+          return tickArray.ticks[i];
+        }
+        i = i - 1;
+      }
+    } else {
+      let i = 0;
+      while (i < TICK_ARRAY_SIZE) {
+        if (tickArray.ticks[i].liquidityGross.gtn(0)) {
+          return tickArray.ticks[i];
+        }
+        i = i + 1;
+      }
+    }
+
+    throw Error(`firstInitializedTick check error: ${tickArray} - ${zeroForOne}`);
+  }
+
+  /**
+   * Dynamic tick array implementation
+   * Uses the mapping table (tickOffsetIndex) to find the first allocated tick
+   */
+  private static _firstInitializedTickDynamic(dynTickArray: DynTickArray, zeroForOne: boolean): Tick {
+    if (zeroForOne) {
+      // Search from right to left (highest tick index first)
+      let i = TICK_ARRAY_SIZE - 1;
+      while (i >= 0) {
+        const physicalIndex = dynTickArray.tickOffsetIndex[i];
+
+        if (physicalIndex > 0) {
+          const tick = dynTickArray.ticks[physicalIndex - 1];
+          if (tick.liquidityGross.gtn(0)) {
+            return tick;
+          }
+        }
+
+        i = i - 1;
+      }
+    } else {
+      // Search from left to right (lowest tick index first)
+      let i = 0;
+      while (i < TICK_ARRAY_SIZE) {
+        const physicalIndex = dynTickArray.tickOffsetIndex[i];
+
+        if (physicalIndex > 0) {
+          const tick = dynTickArray.ticks[physicalIndex - 1];
+          if (tick.liquidityGross.gtn(0)) {
+            return tick;
+          }
+        }
+
+        i = i + 1;
+      }
+    }
+
+    throw Error(`firstInitializedTick check error: ${dynTickArray} - ${zeroForOne}`);
+  }
+
+  public static getPriceAndTick({
+    poolInfo,
+    price,
+    baseIn,
+  }: {
+    poolInfo: IPoolLayout;
+    price: Decimal;
+    baseIn: boolean;
+  }): ReturnTypeGetPriceAndTick {
+    const _price = baseIn ? price : new Decimal(1).div(price);
+
+    const tick = TickMath.getTickWithPriceAndTickspacing(
+      _price,
+      poolInfo.tickSpacing,
+      poolInfo.mintDecimalsA,
+      poolInfo.mintDecimalsB
+    );
+    const tickSqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tick);
+    const tickPrice = SqrtPriceMath.sqrtPriceX64ToPrice(
+      tickSqrtPriceX64,
+      poolInfo.mintDecimalsA,
+      poolInfo.mintDecimalsB
+    );
+
+    return baseIn ? { tick, price: tickPrice } : { tick, price: new Decimal(1).div(tickPrice) };
+  }
+}
+
+export declare type PoolVars = {
+  key: PublicKey;
+  tokenA: PublicKey;
+  tokenB: PublicKey;
+  fee: number;
+};
+
+export class TickQuery {
+  /**
+   * Find the next initialized TickArray
+   */
+  public static nextInitializedTickArray(
+    tickIndex: number,
+    tickSpacing: number,
+    zeroForOne: boolean,
+    tickArrayBitmap: BN[],
+    exBitmapInfo: TickArrayBitmapExtensionType
+  ): {
+    isExist: boolean;
+    nextStartIndex: number;
+  } {
+    // First, include the array that contains tickIndex itself if initialized (handles boundary inclusively)
+    const startIndex = TickQuery.getArrayStartIndex(tickIndex, tickSpacing);
+    const merged = TickUtils.mergeTickArrayBitmap(tickArrayBitmap);
+    try {
+      const { isInitialized } = TickUtils.checkTickArrayIsInitialized(merged, startIndex, tickSpacing);
+      if (isInitialized) return { isExist: true, nextStartIndex: startIndex };
+    } catch {
+      /* ignore */
+    }
+    try {
+      const { isInitialized } = TickArrayBitmapExtensionUtils.checkTickArrayIsInit(
+        startIndex,
+        tickSpacing,
+        exBitmapInfo
+      );
+      if (isInitialized) return { isExist: true, nextStartIndex: startIndex };
+    } catch {
+      /* ignore */
+    }
+
+    // Otherwise, search outward in the given direction
+    const currentOffset = TickUtils.getTickArrayBitIndex(tickIndex, tickSpacing);
+    const result: number[] = zeroForOne
+      ? TickUtils.searchLowBitFromStart(tickArrayBitmap, exBitmapInfo, currentOffset - 1, 1, tickSpacing)
+      : TickUtils.searchHightBitFromStart(tickArrayBitmap, exBitmapInfo, currentOffset + 1, 1, tickSpacing);
+
+    return result.length > 0 ? { isExist: true, nextStartIndex: result[0] } : { isExist: false, nextStartIndex: 0 };
+  }
+
+  // Calculate the start index of the TickArray corresponding to the given tick index
+  public static getArrayStartIndex(tickIndex: number, tickSpacing: number): number {
+    const ticksInArray = this.tickCount(tickSpacing);
+    const start = Math.floor(tickIndex / ticksInArray);
+
+    return start * ticksInArray;
+  }
+
+  // Check if the given tick index is a valid start index
+  public static checkIsValidStartIndex(tickIndex: number, tickSpacing: number): boolean {
+    if (TickUtils.checkIsOutOfBoundary(tickIndex)) {
+      if (tickIndex > MAX_TICK) {
+        return false;
+      }
+      // In extreme scenarios, tickIndex may be less than MIN_TICK
+      const minStartIndex = TickUtils.getTickArrayStartIndexByTick(MIN_TICK, tickSpacing);
+      return tickIndex == minStartIndex;
+    }
+    return tickIndex % this.tickCount(tickSpacing) == 0;
+  }
+
+  // Calculate the number of ticks contained in one tickarray
+  public static tickCount(tickSpacing: number): number {
+    return TICK_ARRAY_SIZE * tickSpacing;
+  }
+}

+ 189 - 0
src/lib/clmm-sdk/src/instructions/utils/tickArrayUtils.ts

@@ -0,0 +1,189 @@
+import { PublicKey } from '@solana/web3.js';
+
+import { DYN_TICK_ARRAY_HEADER_LEN, TICK_STATE_LEN } from '../../constants.js';
+import { DynTickArrayLayout, TickArrayLayout, TickStateLayout } from '../layout.js';
+
+import { Tick, DynTickArray, TickArrayContainer } from './models.js';
+import { TickUtils } from './tick.js';
+import BN from 'bn.js';
+
+/**
+ * Tick Array 工具类
+ * 处理固定和动态 Tick Array 的统一操作
+ */
+export class TickArrayUtils {
+  /**
+   * Discriminator 常量
+   * 这些值通过 Anchor 框架自动生成(SHA256("account:<StructName>") 的前 8 字节)
+   */
+  private static readonly FIXED_TICK_ARRAY_DISCRIMINATOR = Buffer.from('c09b55cd31f9812a', 'hex');
+  private static readonly DYN_TICK_ARRAY_DISCRIMINATOR = Buffer.from('6a8b98247599b838', 'hex');
+
+  /**
+   * 识别 Tick Array 的类型
+   * @param accountData 账户数据
+   * @returns 'Fixed' 或 'Dynamic'
+   */
+  public static identifyTickArrayType(accountData: Buffer): 'Fixed' | 'Dynamic' {
+    if (accountData.length < 8) {
+      throw new Error('Invalid tick array data: too short to contain discriminator');
+    }
+
+    const discriminator = accountData.slice(0, 8);
+
+    if (discriminator.equals(this.FIXED_TICK_ARRAY_DISCRIMINATOR)) {
+      return 'Fixed';
+    } else if (discriminator.equals(this.DYN_TICK_ARRAY_DISCRIMINATOR)) {
+      return 'Dynamic';
+    } else {
+      throw new Error(
+        `Unknown tick array discriminator: ${discriminator.toString('hex')}. ` +
+          `Expected Fixed: ${this.FIXED_TICK_ARRAY_DISCRIMINATOR.toString('hex')} ` +
+          `or Dynamic: ${this.DYN_TICK_ARRAY_DISCRIMINATOR.toString('hex')}`,
+      );
+    }
+  }
+
+  /**
+   * 解码动态 Tick Array
+   * @param accountData 账户数据
+   * @param address 账户地址(可选)
+   * @returns DynTickArray
+   */
+  public static decodeDynTickArray(accountData: Buffer, address?: PublicKey): DynTickArray {
+    // DynTickArrayState Header 长度:8 (discriminator) + 208 (struct) = 216 bytes
+
+    if (accountData.length < DYN_TICK_ARRAY_HEADER_LEN) {
+      throw new Error(
+        `Invalid dyn tick array data: expected at least ${DYN_TICK_ARRAY_HEADER_LEN} bytes, got ${accountData.length}`,
+      );
+    }
+
+    // 1. 解析 header
+    const headerData = accountData.slice(0, DYN_TICK_ARRAY_HEADER_LEN);
+    const header = DynTickArrayLayout.decode(headerData);
+
+    // 2. 验证数据长度
+    const expectedSize = DYN_TICK_ARRAY_HEADER_LEN + header.allocTickCount * TICK_STATE_LEN;
+    if (accountData.length !== expectedSize) {
+      throw new Error(`Invalid dyn tick array data size: expected ${expectedSize} bytes, got ${accountData.length}`);
+    }
+
+    // 3. 解析 TickState 数组
+    const ticksData = accountData.slice(DYN_TICK_ARRAY_HEADER_LEN);
+    const ticks: Tick[] = [];
+
+    for (let i = 0; i < header.allocTickCount; i++) {
+      const offset = i * TICK_STATE_LEN;
+      const tickData = ticksData.slice(offset, offset + TICK_STATE_LEN);
+      const tick = TickStateLayout.decode(tickData);
+      ticks.push(tick);
+    }
+
+    // 4. 构建结果
+    return {
+      address: address || PublicKey.default,
+      poolId: header.poolId,
+      startTickIndex: header.startTickIndex,
+      tickOffsetIndex: Array.from(header.tickOffsetIndex),
+      allocTickCount: header.allocTickCount,
+      initializedTickCount: header.initializedTickCount,
+      ticks,
+    };
+  }
+
+  /**
+   * 从容器中获取 Tick State
+   * @param container TickArrayContainer
+   * @param tickIndex Tick 索引
+   * @param tickSpacing Tick 间距
+   * @returns Tick 或 null(如果未分配)
+   */
+  public static getTickStateFromContainer(
+    container: TickArrayContainer,
+    tickIndex: number,
+    tickSpacing: number,
+  ): Tick | null {
+    if (container.type === 'Fixed') {
+      // 固定 Tick Array: 直接数组访问
+      const offset = TickUtils.getTickOffsetInArray(tickIndex, tickSpacing);
+      return container.data.ticks[offset];
+    } else {
+      // 动态 Tick Array: 通过映射表访问
+      return this.getTickStateFromDynArray(container.data, tickIndex, tickSpacing);
+    }
+  }
+
+  /**
+   * 从动态 Tick Array 中获取 Tick State
+   * @param dynTickArray DynTickArray
+   * @param tickIndex Tick 索引
+   * @param tickSpacing Tick 间距
+   * @returns Tick 或 null(如果未分配)
+   */
+  private static getTickStateFromDynArray(
+    dynTickArray: DynTickArray,
+    tickIndex: number,
+    tickSpacing: number,
+  ): Tick | null {
+    // 1. 计算逻辑偏移 (0-59)
+    const logicalOffset = TickUtils.getTickOffsetInArray(tickIndex, tickSpacing);
+
+    // 2. 查映射表
+    const physicalIndexPlusOne = dynTickArray.tickOffsetIndex[logicalOffset];
+
+    // 3. 检查是否已分配
+    if (physicalIndexPlusOne === 0) {
+      return null; // 未分配
+    }
+
+    // 4. 计算实际索引 (position + 1 - 1 = position)
+    const actualIndex = physicalIndexPlusOne - 1;
+
+    // 5. 边界检查
+    if (actualIndex >= dynTickArray.ticks.length) {
+      throw new Error(
+        `Tick state index out of bounds: ${actualIndex} >= ${dynTickArray.ticks.length}. ` +
+          `This indicates corrupted tick array data.`,
+      );
+    }
+
+    return dynTickArray.ticks[actualIndex];
+  }
+
+  /**
+   * Parse tick array account data into a container
+   * Supports both fixed and dynamic tick arrays
+   * @private
+   */
+  public static parseTickArrayContainer(accountData: Buffer, address: PublicKey): TickArrayContainer {
+    const tickArrayType = this.identifyTickArrayType(accountData);
+
+    if (tickArrayType === 'Fixed') {
+      const decoded = TickArrayLayout.decode(accountData);
+      return {
+        type: 'Fixed',
+        data: {
+          address,
+          poolId: decoded.poolId,
+          startTickIndex: decoded.startTickIndex,
+          ticks: decoded.ticks.map((tick: any) => ({
+            tick: tick.tick,
+            liquidityNet: new BN(tick.liquidityNet.toString()),
+            liquidityGross: new BN(tick.liquidityGross.toString()),
+            feeGrowthOutsideX64A: new BN(tick.feeGrowthOutsideX64A.toString()),
+            feeGrowthOutsideX64B: new BN(tick.feeGrowthOutsideX64B.toString()),
+            rewardGrowthsOutsideX64: tick.rewardGrowthsOutsideX64.map((r: any) => new BN(r.toString())),
+          })),
+          initializedTickCount: decoded.initializedTickCount,
+        },
+      };
+    } else {
+      const decoded = this.decodeDynTickArray(accountData, address);
+      return {
+        type: 'Dynamic',
+        data: decoded,
+      };
+    }
+  }
+}

+ 227 - 0
src/lib/clmm-sdk/src/instructions/utils/tickMath.test.ts

@@ -0,0 +1,227 @@
+import { Decimal } from 'decimal.js';
+import { describe, test, expect } from 'vitest';
+
+import { SqrtPriceMath } from './sqrtPriceMath.js';
+import { TickMath } from './tickMath.js';
+
+describe('TickMath basic functionality test', () => {
+  test('getTickWithPriceAndTickspacing basic functionality', () => {
+    const priceList = [
+      new Decimal('0.001'),
+      new Decimal('0.01'),
+      new Decimal('0.1'),
+      new Decimal('1'),
+      new Decimal('10'),
+      new Decimal('100'),
+      new Decimal('1000'),
+      new Decimal('10000'),
+    ];
+
+    const tickSpacing = 10;
+    const decimalsA = 6;
+    const decimalsB = 6;
+
+    for (const price of priceList) {
+      const tick = TickMath.getTickWithPriceAndTickspacing(price, tickSpacing, decimalsA, decimalsB);
+
+      expect(Math.abs(tick % tickSpacing)).toBe(0);
+    }
+  });
+
+  test('getPriceFromTick basic functionality', () => {
+    const tick = 100;
+    const decimalsA = 6;
+    const decimalsB = 6;
+
+    const price = TickMath.getPriceFromTick({ tick, decimalsA, decimalsB });
+
+    // Verify that the price is positive
+    expect(price.isPositive()).toBeTruthy();
+  });
+
+  test('getTickAlignedPriceDetails returns the correct structure', () => {
+    const price = new Decimal('1.5');
+    const tickSpacing = 10;
+    const decimalsA = 6;
+    const decimalsB = 6;
+
+    const details = TickMath.getTickAlignedPriceDetails(price, tickSpacing, decimalsA, decimalsB);
+
+    // Verify that the returned object contains all necessary fields
+    expect(details).toHaveProperty('tick');
+    expect(details).toHaveProperty('sqrtPriceX64');
+    expect(details).toHaveProperty('price');
+    expect(details.tick % tickSpacing).toBe(0);
+  });
+});
+
+describe('TickMath rounding behavior test', () => {
+  test('positive tick rounding up', () => {
+    // Select a positive tick that is not a multiple of tickSpacing
+    const originalTick = 15;
+
+    const tickSpacing = 10;
+    const decimalsA = 9;
+    const decimalsB = 6;
+
+    const price = TickMath.getPriceFromTick({ tick: originalTick, decimalsA, decimalsB });
+    const alignedTick = TickMath.getTickWithPriceAndTickspacing(price, tickSpacing, decimalsA, decimalsB);
+
+    expect(alignedTick).toBe(20);
+  });
+
+  test('negative tick rounding down', () => {
+    // Similar to above, but using a negative tick
+    const originalTick = -15;
+
+    const tickSpacing = 10;
+    const decimalsA = 9;
+    const decimalsB = 6;
+
+    const price = TickMath.getPriceFromTick({ tick: originalTick, decimalsA, decimalsB });
+    const alignedTick = TickMath.getTickWithPriceAndTickspacing(price, tickSpacing, decimalsA, decimalsB);
+
+    // 负数应该向下舍入到 -20
+    expect(alignedTick).toBe(-20);
+  });
+});
+
+describe('TickMath boundary condition test', () => {
+  test('extreme price values', () => {
+    const extremePrices = [
+      new Decimal('0.0000001'), // Very small price
+      new Decimal('1000000'), // Very large price
+    ];
+
+    const tickSpacing = 60;
+    const decimalsA = 6;
+    const decimalsB = 6;
+
+    for (const price of extremePrices) {
+      const tick = TickMath.getTickWithPriceAndTickspacing(price, tickSpacing, decimalsA, decimalsB);
+
+      // Verify that the tick is a multiple of tickSpacing
+      expect(Math.abs(tick % tickSpacing)).toBe(0);
+
+      // Verify that there is no exception when converting back from tick to price
+      const details = TickMath.getTickAlignedPriceDetails(price, tickSpacing, decimalsA, decimalsB);
+      expect(details.price.isPositive()).toBeTruthy();
+    }
+  });
+
+  test('different tickSpacing values', () => {
+    const price = new Decimal('1.5');
+    const decimalsA = 6;
+    const decimalsB = 6;
+
+    const tickSpacings = [1, 10, 60, 200];
+
+    for (const spacing of tickSpacings) {
+      const tick = TickMath.getTickWithPriceAndTickspacing(price, spacing, decimalsA, decimalsB);
+
+      // Verify that the tick is a multiple of tickSpacing
+      expect(tick % spacing).toBe(0);
+
+      // Verify that the difference between the original tick and the aligned tick should not exceed tickSpacing
+      const originalTick = SqrtPriceMath.getTickFromPrice(price, decimalsA, decimalsB);
+      expect(Math.abs(tick - originalTick)).toBeLessThan(spacing);
+    }
+  });
+});
+
+describe('TickMath consistency test', () => {
+  test('price -> tick -> price conversion consistency', () => {
+    const testPrices = [new Decimal('0.01'), new Decimal('0.5'), new Decimal('1'), new Decimal('2'), new Decimal('10')];
+
+    const tickSpacing = 10;
+    const decimalsA = 6;
+    const decimalsB = 6;
+
+    for (const initialPrice of testPrices) {
+      // Get the aligned price information
+      const details = TickMath.getTickAlignedPriceDetails(initialPrice, tickSpacing, decimalsA, decimalsB);
+
+      // Convert back from tick to price
+      const recalculatedPrice = TickMath.getPriceFromTick({
+        tick: details.tick,
+        decimalsA,
+        decimalsB,
+      });
+
+      // Verify that the two price values are equal within a reasonable error range
+      expect(details.price.toString()).toBe(recalculatedPrice.toString());
+    }
+  });
+
+  test('different decimals combinations', () => {
+    const price = new Decimal('2.5');
+    const tickSpacing = 60;
+
+    const decimalCombinations = [
+      { decimalsA: 6, decimalsB: 6 },
+      { decimalsA: 8, decimalsB: 6 },
+      { decimalsA: 6, decimalsB: 8 },
+      { decimalsA: 9, decimalsB: 18 },
+    ];
+
+    for (const { decimalsA, decimalsB } of decimalCombinations) {
+      const details = TickMath.getTickAlignedPriceDetails(price, tickSpacing, decimalsA, decimalsB);
+
+      // Verify that the tick is a multiple of tickSpacing
+      expect(Math.abs(details.tick % tickSpacing)).toBe(0);
+
+      // Verify that the magnitude of the adjusted price is similar to the original price
+      const magnitudeDiff = Math.abs(details.price.div(price).toNumber() - 1);
+
+      expect(magnitudeDiff).toBeLessThan(0.01);
+    }
+  });
+});
+
+describe('TickMath and SqrtPriceMath combination test', () => {
+  test('getTickWithPriceAndTickspacing and SqrtPriceMath consistency', () => {
+    const price = new Decimal('1.2345');
+    const tickSpacing = 60;
+    const decimalsA = 6;
+    const decimalsB = 6;
+
+    // Get the original tick
+    const originalTick = SqrtPriceMath.getTickFromPrice(price, decimalsA, decimalsB);
+
+    // Get the aligned tick
+    const alignedTick = TickMath.getTickWithPriceAndTickspacing(price, tickSpacing, decimalsA, decimalsB);
+
+    // Verify that the aligned tick is the closest multiple of tickSpacing to the original tick
+    const expectedAlignedTick =
+      originalTick >= 0
+        ? Math.ceil(originalTick / tickSpacing) * tickSpacing
+        : Math.floor(originalTick / tickSpacing) * tickSpacing;
+
+    expect(alignedTick).toBe(expectedAlignedTick);
+  });
+
+  test('the effect of baseIn parameter on price calculation', () => {
+    const tick = 100;
+    const decimalsA = 6;
+    const decimalsB = 6;
+
+    // baseIn = true
+    const priceBaseIn = TickMath.getPriceFromTick({
+      tick,
+      decimalsA,
+      decimalsB,
+      baseIn: true,
+    });
+
+    // baseIn = false
+    const priceBaseOut = TickMath.getPriceFromTick({
+      tick,
+      decimalsA,
+      decimalsB,
+      baseIn: false,
+    });
+
+    // Verify that the price when baseIn = false should be the reciprocal of the price when baseIn = true
+    expect(priceBaseIn.mul(priceBaseOut).toNumber()).toBeCloseTo(1, 0.00001);
+  });
+});

+ 60 - 0
src/lib/clmm-sdk/src/instructions/utils/tickMath.ts

@@ -0,0 +1,60 @@
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import { SqrtPriceMath } from './sqrtPriceMath.js';
+
+export interface TickAlignedPriceDetails {
+  tick: number;
+  sqrtPriceX64: BN;
+  price: Decimal;
+}
+export class TickMath {
+  // Convert price to tick
+  public static getTickWithPriceAndTickspacing(
+    price: Decimal,
+    tickSpacing: number,
+    mintDecimalsA: number,
+    mintDecimalsB: number
+  ): number {
+    const tick = SqrtPriceMath.getTickFromSqrtPriceX64(
+      SqrtPriceMath.priceToSqrtPriceX64(price, mintDecimalsA, mintDecimalsB)
+    );
+    let result = tick / tickSpacing;
+    if (result < 0) {
+      result = Math.floor(result);
+    } else {
+      result = Math.ceil(result);
+    }
+    return result * tickSpacing;
+  }
+
+  public static getPriceFromTick(params: {
+    tick: number;
+    decimalsA: number;
+    decimalsB: number;
+    baseIn?: boolean;
+  }): Decimal {
+    const { tick, decimalsA, decimalsB, baseIn = true } = params;
+    const tickSqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tick);
+    const tickPrice = SqrtPriceMath.sqrtPriceX64ToPrice(tickSqrtPriceX64, decimalsA, decimalsB);
+
+    return baseIn ? tickPrice : new Decimal(1).div(tickPrice);
+  }
+
+  // Align price to the nearest valid tick and return related data
+  public static getTickAlignedPriceDetails(
+    price: Decimal,
+    tickSpacing: number,
+    mintDecimalsA: number,
+    mintDecimalsB: number
+  ): TickAlignedPriceDetails {
+    const tick = TickMath.getTickWithPriceAndTickspacing(price, tickSpacing, mintDecimalsA, mintDecimalsB);
+    const sqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tick);
+    const roundedPrice = SqrtPriceMath.sqrtPriceX64ToPrice(sqrtPriceX64, mintDecimalsA, mintDecimalsB);
+    return {
+      tick,
+      sqrtPriceX64,
+      price: roundedPrice,
+    };
+  }
+}

+ 227 - 0
src/lib/clmm-sdk/src/instructions/utils/tickarrayBitmap.ts

@@ -0,0 +1,227 @@
+import BN from 'bn.js';
+
+import { TICK_ARRAY_BITMAP_SIZE, TICK_ARRAY_SIZE } from '../../constants.js';
+import { MAX_TICK, MIN_TICK } from '../constants.js';
+import { TickArrayBitmapExtensionType } from './models.js';
+
+import { isZero, leadingZeros, leastSignificantBit, mostSignificantBit, trailingZeros } from './binaryUtils.js';
+import { TickUtils, TickQuery } from './tick.js';
+
+export class TickArrayBitmap {
+  // Calculate the maximum tick range that a single bitmap can represent
+  public static maxTickInTickarrayBitmap(tickSpacing: number): number {
+    return tickSpacing * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE;
+  }
+
+  // Calculate the bitmap boundaries corresponding to the given tick range
+  public static getBitmapTickBoundary(
+    tickarrayStartIndex: number,
+    tickSpacing: number
+  ): {
+    minValue: number;
+    maxValue: number;
+  } {
+    const ticksInOneBitmap = this.maxTickInTickarrayBitmap(tickSpacing);
+    let m = Math.floor(Math.abs(tickarrayStartIndex) / ticksInOneBitmap);
+    if (tickarrayStartIndex < 0 && Math.abs(tickarrayStartIndex) % ticksInOneBitmap != 0) m += 1;
+
+    const minValue = ticksInOneBitmap * m;
+
+    return tickarrayStartIndex < 0
+      ? { minValue: -minValue, maxValue: -minValue + ticksInOneBitmap }
+      : { minValue, maxValue: minValue + ticksInOneBitmap };
+  }
+
+  // Calculate the start index of the next initialized TickArray
+  public static nextInitializedTickArrayStartIndex(
+    bitMap: BN,
+    lastTickArrayStartIndex: number,
+    tickSpacing: number,
+    zeroForOne: boolean
+  ): { isInit: boolean; tickIndex: number } {
+    if (!TickQuery.checkIsValidStartIndex(lastTickArrayStartIndex, tickSpacing))
+      throw Error('nextInitializedTickArrayStartIndex check error');
+
+    const tickBoundary = this.maxTickInTickarrayBitmap(tickSpacing);
+    const nextTickArrayStartIndex = zeroForOne
+      ? lastTickArrayStartIndex - TickQuery.tickCount(tickSpacing)
+      : lastTickArrayStartIndex + TickQuery.tickCount(tickSpacing);
+
+    if (nextTickArrayStartIndex < -tickBoundary || nextTickArrayStartIndex >= tickBoundary) {
+      return { isInit: false, tickIndex: lastTickArrayStartIndex };
+    }
+
+    const multiplier = tickSpacing * TICK_ARRAY_SIZE;
+    let compressed = nextTickArrayStartIndex / multiplier + 512;
+
+    if (nextTickArrayStartIndex < 0 && nextTickArrayStartIndex % multiplier != 0) {
+      compressed--;
+    }
+
+    const bitPos = Math.abs(compressed);
+
+    if (zeroForOne) {
+      const offsetBitMap = bitMap.shln(1024 - bitPos - 1);
+      const nextBit = mostSignificantBit(1024, offsetBitMap);
+      if (nextBit !== null) {
+        const nextArrayStartIndex = (bitPos - nextBit - 512) * multiplier;
+        return { isInit: true, tickIndex: nextArrayStartIndex };
+      } else {
+        return { isInit: false, tickIndex: -tickBoundary };
+      }
+    } else {
+      const offsetBitMap = bitMap.shrn(bitPos);
+      const nextBit = leastSignificantBit(1024, offsetBitMap);
+      if (nextBit !== null) {
+        const nextArrayStartIndex = (bitPos + nextBit - 512) * multiplier;
+        return { isInit: true, tickIndex: nextArrayStartIndex };
+      } else {
+        return { isInit: false, tickIndex: tickBoundary - TickQuery.tickCount(tickSpacing) };
+      }
+    }
+  }
+}
+
+export class TickArrayBitmapExtensionUtils {
+  public static getBitmapOffset(tickIndex: number, tickSpacing: number): number {
+    if (!TickQuery.checkIsValidStartIndex(tickIndex, tickSpacing)) {
+      throw new Error('No enough initialized tickArray');
+    }
+
+    const { positiveTickBoundary, negativeTickBoundary } = this.extensionTickBoundary(tickSpacing);
+
+    if (tickIndex >= negativeTickBoundary && tickIndex < positiveTickBoundary) {
+      throw Error('checkExtensionBoundary -> InvalidTickArrayBoundary');
+    }
+
+    const ticksInOneBitmap = TickArrayBitmap.maxTickInTickarrayBitmap(tickSpacing);
+    let offset = Math.floor(Math.abs(tickIndex) / ticksInOneBitmap) - 1;
+
+    if (tickIndex < 0 && Math.abs(tickIndex) % ticksInOneBitmap === 0) offset--;
+    return offset;
+  }
+
+  public static getBitmap(
+    tickIndex: number,
+    tickSpacing: number,
+    tickArrayBitmapExtension: TickArrayBitmapExtensionType
+  ): { offset: number; tickarrayBitmap: BN[] } {
+    const offset = this.getBitmapOffset(tickIndex, tickSpacing);
+    if (tickIndex < 0) {
+      return { offset, tickarrayBitmap: tickArrayBitmapExtension.negativeTickArrayBitmap[offset] };
+    } else {
+      return { offset, tickarrayBitmap: tickArrayBitmapExtension.positiveTickArrayBitmap[offset] };
+    }
+  }
+
+  // Calculate the boundary values of the extended bitmap
+  public static extensionTickBoundary(tickSpacing: number): {
+    positiveTickBoundary: number;
+    negativeTickBoundary: number;
+  } {
+    const positiveTickBoundary = TickArrayBitmap.maxTickInTickarrayBitmap(tickSpacing);
+
+    const negativeTickBoundary = -positiveTickBoundary;
+
+    if (MAX_TICK <= positiveTickBoundary)
+      throw Error(`extensionTickBoundary check error: ${MAX_TICK}, ${positiveTickBoundary}`);
+    if (negativeTickBoundary <= MIN_TICK)
+      throw Error(`extensionTickBoundary check error: ${negativeTickBoundary}, ${MIN_TICK}`);
+
+    return { positiveTickBoundary, negativeTickBoundary };
+  }
+
+  // Check if a specific TickArray has been initialized
+  public static checkTickArrayIsInit(
+    tickArrayStartIndex: number,
+    tickSpacing: number,
+    tickArrayBitmapExtension: TickArrayBitmapExtensionType
+  ): { isInitialized: boolean; startIndex: number } {
+    const { tickarrayBitmap } = this.getBitmap(tickArrayStartIndex, tickSpacing, tickArrayBitmapExtension);
+
+    const tickArrayOffsetInBitmap = this.tickArrayOffsetInBitmap(tickArrayStartIndex, tickSpacing);
+
+    return {
+      isInitialized: TickUtils.mergeTickArrayBitmap(tickarrayBitmap).testn(tickArrayOffsetInBitmap),
+      startIndex: tickArrayStartIndex,
+    };
+  }
+
+  // Find the start index of the next initialized TickArray from a single bitmap
+  public static nextInitializedTickArrayFromOneBitmap(
+    lastTickArrayStartIndex: number,
+    tickSpacing: number,
+    zeroForOne: boolean,
+    tickArrayBitmapExtension: TickArrayBitmapExtensionType
+  ): {
+    isInit: boolean;
+    tickIndex: number;
+  } {
+    const multiplier = TickQuery.tickCount(tickSpacing);
+    const nextTickArrayStartIndex = zeroForOne
+      ? lastTickArrayStartIndex - multiplier
+      : lastTickArrayStartIndex + multiplier;
+    const { tickarrayBitmap } = this.getBitmap(nextTickArrayStartIndex, tickSpacing, tickArrayBitmapExtension);
+
+    return this.nextInitializedTickArrayInBitmap(tickarrayBitmap, nextTickArrayStartIndex, tickSpacing, zeroForOne);
+  }
+
+  // Find the start index of the next initialized TickArray in the bitmap
+  public static nextInitializedTickArrayInBitmap(
+    tickarrayBitmap: BN[],
+    nextTickArrayStartIndex: number,
+    tickSpacing: number,
+    zeroForOne: boolean
+  ): {
+    isInit: boolean;
+    tickIndex: number;
+  } {
+    const { minValue: bitmapMinTickBoundary, maxValue: bitmapMaxTickBoundary } = TickArrayBitmap.getBitmapTickBoundary(
+      nextTickArrayStartIndex,
+      tickSpacing
+    );
+
+    const tickArrayOffsetInBitmap = this.tickArrayOffsetInBitmap(nextTickArrayStartIndex, tickSpacing);
+    if (zeroForOne) {
+      // tick from upper to lower
+      // find from highter bits to lower bits
+      const offsetBitMap = TickUtils.mergeTickArrayBitmap(tickarrayBitmap).shln(
+        TICK_ARRAY_BITMAP_SIZE - 1 - tickArrayOffsetInBitmap
+      );
+
+      const nextBit = isZero(512, offsetBitMap) ? null : leadingZeros(512, offsetBitMap);
+
+      if (nextBit !== null) {
+        const nextArrayStartIndex = nextTickArrayStartIndex - nextBit * TickQuery.tickCount(tickSpacing);
+        return { isInit: true, tickIndex: nextArrayStartIndex };
+      } else {
+        // not found til to the end
+        return { isInit: false, tickIndex: bitmapMinTickBoundary };
+      }
+    } else {
+      // tick from lower to upper
+      // find from lower bits to highter bits
+      const offsetBitMap = TickUtils.mergeTickArrayBitmap(tickarrayBitmap).shrn(tickArrayOffsetInBitmap);
+
+      const nextBit = isZero(512, offsetBitMap) ? null : trailingZeros(512, offsetBitMap);
+
+      if (nextBit !== null) {
+        const nextArrayStartIndex = nextTickArrayStartIndex + nextBit * TickQuery.tickCount(tickSpacing);
+        return { isInit: true, tickIndex: nextArrayStartIndex };
+      } else {
+        // not found til to the end
+        return { isInit: false, tickIndex: bitmapMaxTickBoundary - TickQuery.tickCount(tickSpacing) };
+      }
+    }
+  }
+
+  // Calculate the offset position (index) of a TickArray within its belonging bitmap
+  public static tickArrayOffsetInBitmap(tickArrayStartIndex: number, tickSpacing: number): number {
+    const m = Math.abs(tickArrayStartIndex) % TickArrayBitmap.maxTickInTickarrayBitmap(tickSpacing);
+    let tickArrayOffsetInBitmap = Math.floor(m / TickQuery.tickCount(tickSpacing));
+    if (tickArrayStartIndex < 0 && m != 0) {
+      tickArrayOffsetInBitmap = TICK_ARRAY_BITMAP_SIZE - tickArrayOffsetInBitmap;
+    }
+    return tickArrayOffsetInBitmap;
+  }
+}

+ 97 - 0
src/lib/clmm-sdk/src/instructions/utils/transfer.ts

@@ -0,0 +1,97 @@
+import { TransferFee } from '@solana/spl-token';
+import { EpochInfo } from '@solana/web3.js';
+import BN from 'bn.js';
+
+import { TransferFeeDataBaseType, IGetTransferAmountFee } from '../models.js';
+
+const POINT = 10_000;
+
+// This function is a specialized utility function for calculating transfer fees and final amounts for Token-2022 token transfers. It handles the transfer fee feature introduced in the Solana Token-2022 program, calculating precise transfer amounts and fees based on the current epoch and fee configuration
+export function getTransferAmountFee(
+  amount: BN,
+  _feeConfig: TransferFeeDataBaseType | undefined,
+  epochInfo: EpochInfo,
+  addFee: boolean
+): IGetTransferAmountFee {
+  if (_feeConfig === undefined) {
+    return {
+      amount,
+      fee: undefined,
+      expirationTime: undefined,
+    };
+  }
+  const feeConfig = {
+    ..._feeConfig,
+    olderTransferFee: {
+      epoch: BigInt(_feeConfig.olderTransferFee.epoch),
+      maximumFee: BigInt(_feeConfig.olderTransferFee.maximumFee),
+      transferFeeBasisPoints: _feeConfig.olderTransferFee.transferFeeBasisPoints,
+    },
+    newerTransferFee: {
+      epoch: BigInt(_feeConfig.newerTransferFee.epoch),
+      maximumFee: BigInt(_feeConfig.newerTransferFee.maximumFee),
+      transferFeeBasisPoints: _feeConfig.newerTransferFee.transferFeeBasisPoints,
+    },
+  };
+
+  const nowFeeConfig: TransferFee =
+    epochInfo.epoch < feeConfig.newerTransferFee.epoch ? feeConfig.olderTransferFee : feeConfig.newerTransferFee;
+  const maxFee = new BN(nowFeeConfig.maximumFee.toString());
+  const expirationTime: number | undefined =
+    epochInfo.epoch < feeConfig.newerTransferFee.epoch
+      ? ((Number(feeConfig.newerTransferFee.epoch) * epochInfo.slotsInEpoch - epochInfo.absoluteSlot) * 400) / 1000
+      : undefined;
+
+  if (addFee) {
+    if (nowFeeConfig.transferFeeBasisPoints === POINT) {
+      const nowMaxFee = new BN(nowFeeConfig.maximumFee.toString());
+      return {
+        amount: amount.add(nowMaxFee),
+        fee: nowMaxFee,
+        expirationTime,
+      };
+    } else {
+      const _TAmount = BNDivCeil(amount.mul(new BN(POINT)), new BN(POINT - nowFeeConfig.transferFeeBasisPoints));
+
+      const nowMaxFee = new BN(nowFeeConfig.maximumFee.toString());
+      const TAmount = _TAmount.sub(amount).gt(nowMaxFee) ? amount.add(nowMaxFee) : _TAmount;
+
+      const _fee = BNDivCeil(TAmount.mul(new BN(nowFeeConfig.transferFeeBasisPoints)), new BN(POINT));
+      const fee = _fee.gt(maxFee) ? maxFee : _fee;
+      return {
+        amount: TAmount,
+        fee,
+        expirationTime,
+      };
+    }
+  } else {
+    const _fee = BNDivCeil(amount.mul(new BN(nowFeeConfig.transferFeeBasisPoints)), new BN(POINT));
+    const fee = _fee.gt(maxFee) ? maxFee : _fee;
+
+    return {
+      amount,
+      fee,
+      expirationTime,
+    };
+  }
+}
+
+export function minExpirationTime(
+  expirationTime1: number | undefined,
+  expirationTime2: number | undefined
+): number | undefined {
+  if (expirationTime1 === undefined) return expirationTime2;
+  if (expirationTime2 === undefined) return expirationTime1;
+
+  return Math.min(expirationTime1, expirationTime2);
+}
+
+export function BNDivCeil(bn1: BN, bn2: BN): BN {
+  const { div, mod } = bn1.divmod(bn2);
+
+  if (mod.gt(new BN(0))) {
+    return div.add(new BN(1));
+  } else {
+    return div;
+  }
+}

+ 14 - 0
src/lib/clmm-sdk/src/playgrounds/01_get_raw_position_info_list.ts

@@ -0,0 +1,14 @@
+/**
+ * Get the list of positions for a specified user (on-chain way)
+ */
+
+import { userAddress, chain } from './config.js';
+
+async function main(): Promise<void> {
+  // Get the list of positions for a specified user
+  console.log('userAddress =>', userAddress.toBase58());
+  const positionList = await chain.getRawPositionInfoListByUserAddress(userAddress);
+  console.log(positionList);
+}
+
+main();

+ 17 - 0
src/lib/clmm-sdk/src/playgrounds/02_get_raw_position_info.ts

@@ -0,0 +1,17 @@
+/**
+ * Get the position information corresponding to the NFT mint address (on-chain way)
+ */
+import { PublicKey } from '@solana/web3.js';
+
+import { chain } from './config.js';
+
+async function main(): Promise<void> {
+  // Change to your own NFT mint address
+  const nftMint = new PublicKey('2GfNC4r784awMFDW2d1RDDebTzStTY6aMr78K5Q6DGyM');
+
+  // Get the position information corresponding to the NFT mint address
+  const positionInfo = await chain.getRawPositionInfoByNftMint(nftMint);
+  console.log(positionInfo);
+}
+
+main();

+ 13 - 0
src/lib/clmm-sdk/src/playgrounds/03_get_raw_pool_info.ts

@@ -0,0 +1,13 @@
+/**
+ * Get the on-chain information of the specified pool
+ */
+
+import { chain, PoolAddress } from './config.js';
+
+async function main(): Promise<void> {
+  // Get the on-chain information of the specified pool
+  const poolInfo = await chain.getRawPoolInfoByPoolId(PoolAddress.SOL_USDC);
+  console.log(poolInfo);
+}
+
+main();

+ 42 - 0
src/lib/clmm-sdk/src/playgrounds/04_get_position_info_list.ts

@@ -0,0 +1,42 @@
+/**
+ * Get the position information corresponding to the NFT mint address
+ */
+
+import { chain, userAddress } from './config.js';
+
+async function main(): Promise<void> {
+  console.log('userAddress ==>', userAddress.toBase58());
+
+  // Get the list of positions for a specified user
+  const positionList = await chain.getRawPositionInfoListByUserAddress(userAddress);
+
+  const nftMints = positionList.map((position) => position.nftMint);
+
+  for (const nftMint of nftMints) {
+    const positionInfo = await chain.getPositionInfoByNftMint(nftMint);
+
+    if (!positionInfo) continue;
+
+    const { uiPriceLower, uiPriceUpper, tokenA, tokenB } = positionInfo;
+
+    console.log(`\n=========== Position information ===========`);
+    console.log(`NFT mint address: ${nftMint.toBase58()}`);
+
+    console.log(`Price range: ${uiPriceLower} - ${uiPriceUpper}`);
+
+    console.log(`Liquidity assets:`);
+    console.log(
+      `  TokenA(${positionInfo.tokenA.address.toBase58().slice(0, 4)}...${positionInfo.tokenA.address
+        .toBase58()
+        .slice(-4)}):`
+    );
+    console.log(`    • Amount: ${tokenA.uiAmount}`);
+    console.log(`    • Fee income: ${tokenA.uiFeeAmount}`);
+
+    console.log(`  TokenB(${tokenB.address.toBase58().slice(0, 4)}...${tokenB.address.toBase58().slice(-4)}):`);
+    console.log(`    • Amount: ${tokenB.uiAmount}`);
+    console.log(`    • Fee income: ${tokenB.uiFeeAmount}`);
+  }
+}
+
+main();

+ 23 - 0
src/lib/clmm-sdk/src/playgrounds/05_align_price.ts

@@ -0,0 +1,23 @@
+/**
+ * Round the price to the price of the corresponding tick (used when creating a position)
+ */
+
+import { Decimal } from 'decimal.js';
+
+import { chain, PoolAddress } from './config.js';
+
+async function main(): Promise<void> {
+  const poolInfo = await chain.getRawPoolInfoByPoolId(PoolAddress.SOL_USDC);
+
+  const userInputPriceLower = 120;
+  const userInputPriceUpper = 160;
+
+  // When creating a position, round the price to the price of the corresponding tick
+  const roundPriceLower = chain.alignPriceToTickPrice(new Decimal(userInputPriceLower), poolInfo);
+  const roundPriceUpper = chain.alignPriceToTickPrice(new Decimal(userInputPriceUpper), poolInfo);
+
+  console.log(`User input price: ${userInputPriceLower} - ${userInputPriceUpper}`);
+  console.log(`Aligned to tick price: ${roundPriceLower} - ${roundPriceUpper}`);
+}
+
+main();

+ 32 - 0
src/lib/clmm-sdk/src/playgrounds/06_get_amount_from_another_amount.ts

@@ -0,0 +1,32 @@
+/**
+ * Round the price to the price of the corresponding tick (used when creating a position)
+ */
+
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import { chain, PoolAddress } from './config.js';
+
+async function main(): Promise<void> {
+  const poolInfo = await chain.getRawPoolInfoByPoolId(PoolAddress.SOL_USDC);
+
+  const userInputPriceLower = 120;
+  const userInputPriceUpper = 160;
+  const userInputAmountA = new BN(1000000000); // 1 SOL
+
+  // Calculate the amount of tokenB needed to be invested in the specified price range after the specified tokenA amount has been invested
+  const amountB = chain.getAmountBFromAmountA({
+    priceLower: new Decimal(userInputPriceLower),
+    priceUpper: new Decimal(userInputPriceUpper),
+    amountA: userInputAmountA,
+    poolInfo,
+  });
+
+  // Tips: This is the amount of tokens that will be accurately invested in the vault pool;
+  console.log(
+    'Estimated amount of tokenB to be invested =>',
+    Number(amountB.toString()) / 10 ** poolInfo.mintDecimalsB
+  );
+}
+
+main();

+ 83 - 0
src/lib/clmm-sdk/src/playgrounds/07_create_position_baseA.ts

@@ -0,0 +1,83 @@
+/**
+ * Create a position
+ */
+
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import { SignerCallback } from '../client/chain/models.js';
+import { TickMath } from '../index.js';
+
+import { userKeypair, userAddress, chain, PoolAddress } from './config.js';
+
+async function main(): Promise<void> {
+  // step 1: User selects the pool
+  const poolInfo = await chain.getRawPoolInfoByPoolId(PoolAddress.USDC_USDT);
+
+  console.log('========= step 1: Select the pool =========');
+  console.log('Selected pool address:', poolInfo.poolId.toBase58());
+
+  // step 2: User inputs the price range
+  const userStartPrice = '0.998';
+  const userEndPrice = '1.002';
+
+  // Calculate the accurate tick price, and show it to the user
+  const priceInTickLower = TickMath.getTickAlignedPriceDetails(
+    new Decimal(userStartPrice),
+    poolInfo.tickSpacing,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+  const priceInTickUpper = TickMath.getTickAlignedPriceDetails(
+    new Decimal(userEndPrice),
+    poolInfo.tickSpacing,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+
+  console.log('========= step 2: User inputs the price range =========');
+  console.log(`User input price range: ${userStartPrice} - ${userEndPrice}`);
+  console.log(`Accurate price range: ${priceInTickLower.price.toNumber()} - ${priceInTickUpper.price.toNumber()}`);
+  console.log(`Accurate price tick range: ${priceInTickLower.tick} - ${priceInTickUpper.tick}`);
+
+  // step 3: User inputs the amount of TokenB and the token type
+  const base = 'MintA';
+  const baseAmount = new BN(2 * 10 ** poolInfo.mintDecimalsA);
+
+  // Calculate the amount of TokenA needed
+  const amountB = chain.getAmountBFromAmountA({
+    priceLower: priceInTickLower.price,
+    priceUpper: priceInTickUpper.price,
+    amountA: baseAmount,
+    poolInfo,
+  });
+
+  // Add a 2% slippage
+  const amountBWithSlippage = new BN(amountB).mul(new BN(10000 * (1 + 0.02))).div(new BN(10000));
+
+  console.log('========= step 3: User inputs the amount of TokenB and the token type =========');
+  console.log('Amount of TokenA to be invested =>', Number(baseAmount.toString()) / 10 ** poolInfo.mintDecimalsA);
+  console.log('Estimated amount of TokenB needed =>', Number(amountB.toString()) / 10 ** poolInfo.mintDecimalsB);
+
+  // Signer callback, later this can be implemented with a wallet plugin
+  const signerCallback: SignerCallback = async (tx) => {
+    tx.sign([userKeypair]);
+    return tx;
+  };
+
+  console.log('======= step 4: After confirming that there is no error, start creating a position =======');
+  const txid = await chain.createPosition({
+    userAddress: userAddress,
+    poolInfo,
+    tickLower: priceInTickLower.tick,
+    tickUpper: priceInTickUpper.tick,
+    base,
+    baseAmount,
+    otherAmountMax: amountBWithSlippage,
+    signerCallback,
+  });
+
+  console.log('Create position successfully, txid:', txid);
+}
+
+main();

+ 82 - 0
src/lib/clmm-sdk/src/playgrounds/07_create_position_baseB.ts

@@ -0,0 +1,82 @@
+/**
+ * Create a position
+ */
+
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import { SignerCallback } from '../client/chain/models.js';
+import { TickMath } from '../index.js';
+
+import { userKeypair, userAddress, PoolAddress, chain } from './config.js';
+
+async function main(): Promise<void> {
+  // step 1: User selects the pool
+  const poolInfo = await chain.getRawPoolInfoByPoolId(PoolAddress.USDC_USDT);
+
+  console.log('========= step 1: Select the pool =========');
+  console.log('Selected pool address:', poolInfo.poolId.toBase58());
+
+  // step 2: User inputs the price range
+  const userStartPrice = '0.998';
+  const userEndPrice = '1.002';
+
+  // Calculate the accurate tick price, and show it to the user
+  const priceInTickLower = TickMath.getTickAlignedPriceDetails(
+    new Decimal(userStartPrice),
+    poolInfo.tickSpacing,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+  const priceInTickUpper = TickMath.getTickAlignedPriceDetails(
+    new Decimal(userEndPrice),
+    poolInfo.tickSpacing,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+
+  console.log('========= step 2: User inputs the price range =========');
+  console.log(`User input price range: ${userStartPrice} - ${userEndPrice}`);
+  console.log(`Accurate price range: ${priceInTickLower.price.toNumber()} - ${priceInTickUpper.price.toNumber()}`);
+
+  // step 3: User inputs the amount of TokenB and the token type
+  const base = 'MintB';
+  const baseAmount = new BN(2 * 10 ** poolInfo.mintDecimalsB);
+
+  // Calculate the amount of TokenA needed
+  const amountA = chain.getAmountAFromAmountB({
+    priceLower: priceInTickLower.price,
+    priceUpper: priceInTickUpper.price,
+    amountB: baseAmount,
+    poolInfo,
+  });
+
+  // Add a 2% slippage
+  const amountAWithSlippage = new BN(amountA).mul(new BN(10000 * (1 + 0.02))).div(new BN(10000));
+
+  console.log('========= step 3: User inputs the amount of TokenB and the token type =========');
+  console.log('Amount of TokenB to be invested =>', Number(baseAmount.toString()) / 10 ** poolInfo.mintDecimalsB);
+  console.log('Estimated amount of TokenA needed =>', Number(amountA.toString()) / 10 ** poolInfo.mintDecimalsA);
+
+  // Signer callback, later this can be implemented with a wallet plugin
+  const signerCallback: SignerCallback = async (tx) => {
+    tx.sign([userKeypair]);
+    return tx;
+  };
+
+  console.log('======= step 4: After confirming that there is no error, start creating a position =======');
+  const txid = await chain.createPosition({
+    userAddress: userAddress,
+    poolInfo,
+    tickLower: priceInTickLower.tick,
+    tickUpper: priceInTickUpper.tick,
+    base,
+    baseAmount,
+    otherAmountMax: amountAWithSlippage,
+    signerCallback,
+  });
+
+  console.log('Create position successfully, txid:', txid);
+}
+
+main();

+ 40 - 0
src/lib/clmm-sdk/src/playgrounds/08_decrease_liquidity.ts

@@ -0,0 +1,40 @@
+/**
+ * Decrease liquidity proportionally
+ */
+
+import { PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+
+import { chain, signerCallback, userAddress } from './config.js';
+
+async function main(): Promise<void> {
+  // Change to your own NFT mint address
+  const nftMint = new PublicKey('3eunC8kdMEwBRQHd91cyQ36FVQFXDuicGk7xuVS6hFfk');
+
+  const positionInfo = await chain.getRawPositionInfoByNftMint(nftMint);
+
+  if (!positionInfo) {
+    throw new Error('Position not found');
+  }
+
+  const { liquidity } = positionInfo;
+
+  const decreasePercentage = 50; // Decrease 50% of the liquidity
+
+  const liquidityToDecrease = liquidity.mul(new BN(decreasePercentage)).div(new BN(100));
+
+  console.log('Current liquidity amount:', liquidity.toString());
+  console.log('Liquidity to decrease:', liquidityToDecrease.toString());
+
+  const txid = await chain.decreaseLiquidity({
+    userAddress,
+    nftMint,
+    // Decrease half of the liquidity
+    liquidity: liquidityToDecrease,
+    signerCallback,
+  });
+
+  console.log('Decrease liquidity successfully, txid:', txid);
+}
+
+main();

+ 82 - 0
src/lib/clmm-sdk/src/playgrounds/09_create_position_side_tokenA.ts

@@ -0,0 +1,82 @@
+/**
+ * Create a position
+ */
+
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import { SignerCallback } from '../client/chain/models.js';
+import { TickMath } from '../index.js';
+
+import { userKeypair, userAddress, PoolAddress, chain } from './config.js';
+
+async function main(): Promise<void> {
+  // step 1: User selects the pool
+  const poolInfo = await chain.getRawPoolInfoByPoolId(PoolAddress.USDC_USDT);
+
+  console.log('========= step 1: Select the pool =========');
+  console.log('Selected pool address:', poolInfo.poolId.toBase58());
+
+  // step 2: User inputs the price range
+  const userStartPrice = '1.1';
+  const userEndPrice = '1.2';
+
+  // Calculate the accurate tick price, and show it to the user
+  const priceInTickLower = TickMath.getTickAlignedPriceDetails(
+    new Decimal(userStartPrice),
+    poolInfo.tickSpacing,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+  const priceInTickUpper = TickMath.getTickAlignedPriceDetails(
+    new Decimal(userEndPrice),
+    poolInfo.tickSpacing,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+
+  console.log('========= step 2: User inputs the price range =========');
+  console.log(`User input price range: ${userStartPrice} - ${userEndPrice}`);
+  console.log(`Accurate price range: ${priceInTickLower.price.toNumber()} - ${priceInTickUpper.price.toNumber()}`);
+
+  // step 3: User inputs the amount of TokenA and the token type
+  const base = 'MintA';
+  const baseAmount = new BN(2 * 10 ** poolInfo.mintDecimalsA);
+
+  // Calculate the amount of TokenB needed
+  const amountB = chain.getAmountBFromAmountA({
+    priceLower: priceInTickLower.price,
+    priceUpper: priceInTickUpper.price,
+    amountA: baseAmount,
+    poolInfo,
+  });
+
+  // Add a 2% slippage
+  const amountBWithSlippage = new BN(amountB).mul(new BN(10000 * (1 + 0.02))).div(new BN(10000));
+
+  console.log('========= step 3: User inputs the amount of TokenA and the token type =========');
+  console.log('Amount of TokenA to be invested =>', Number(baseAmount.toString()) / 10 ** poolInfo.mintDecimalsA);
+  console.log('Estimated amount of TokenB needed =>', Number(amountB.toString()) / 10 ** poolInfo.mintDecimalsB);
+
+  // Signer callback, later this can be implemented with a wallet plugin
+  const signerCallback: SignerCallback = async (tx) => {
+    tx.sign([userKeypair]);
+    return tx;
+  };
+
+  console.log('======= step 4: After confirming that there is no error, start creating a position =======');
+  const txid = await chain.createPosition({
+    userAddress: userAddress,
+    poolInfo,
+    tickLower: priceInTickLower.tick,
+    tickUpper: priceInTickUpper.tick,
+    base,
+    baseAmount,
+    otherAmountMax: amountBWithSlippage,
+    signerCallback,
+  });
+
+  console.log('Create position successfully, txid:', txid);
+}
+
+main();

+ 82 - 0
src/lib/clmm-sdk/src/playgrounds/10_create_position_side_tokenB.ts

@@ -0,0 +1,82 @@
+/**
+ * Create a position
+ */
+
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import { SignerCallback } from '../client/chain/models.js';
+import { TickMath } from '../index.js';
+
+import { userKeypair, userAddress, PoolAddress, chain } from './config.js';
+
+async function main(): Promise<void> {
+  // step 1: User selects the pool
+  const poolInfo = await chain.getRawPoolInfoByPoolId(PoolAddress.USDC_USDT);
+
+  console.log('========= step 1: Select the pool =========');
+  console.log('Selected pool address:', poolInfo.poolId.toBase58());
+
+  // step 2: User inputs the price range
+  const userStartPrice = '0.8';
+  const userEndPrice = '0.9';
+
+  // Calculate the accurate tick price, and show it to the user
+  const priceInTickLower = TickMath.getTickAlignedPriceDetails(
+    new Decimal(userStartPrice),
+    poolInfo.tickSpacing,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+  const priceInTickUpper = TickMath.getTickAlignedPriceDetails(
+    new Decimal(userEndPrice),
+    poolInfo.tickSpacing,
+    poolInfo.mintDecimalsA,
+    poolInfo.mintDecimalsB
+  );
+
+  console.log('========= step 2: User inputs the price range =========');
+  console.log(`User input price range: ${userStartPrice} - ${userEndPrice}`);
+  console.log(`Accurate price range: ${priceInTickLower.price.toNumber()} - ${priceInTickUpper.price.toNumber()}`);
+
+  // step 3: User inputs the amount of TokenB and the token type
+  const base = 'MintB';
+  const baseAmount = new BN(2 * 10 ** poolInfo.mintDecimalsB);
+
+  // Calculate the amount of TokenA needed
+  const amountA = chain.getAmountAFromAmountB({
+    priceLower: priceInTickLower.price,
+    priceUpper: priceInTickUpper.price,
+    amountB: baseAmount,
+    poolInfo,
+  });
+
+  // Add a 2% slippage
+  const amountAWithSlippage = new BN(amountA).mul(new BN(10000 * (1 + 0.02))).div(new BN(10000));
+
+  console.log('========= step 3: User inputs the amount of TokenB and the token type =========');
+  console.log('Amount of TokenB to be invested =>', Number(baseAmount.toString()) / 10 ** poolInfo.mintDecimalsB);
+  console.log('Estimated amount of TokenA needed =>', Number(amountA.toString()) / 10 ** poolInfo.mintDecimalsA);
+
+  // Signer callback, later this can be implemented with a wallet plugin
+  const signerCallback: SignerCallback = async (tx) => {
+    tx.sign([userKeypair]);
+    return tx;
+  };
+
+  console.log('======= step 4: After confirming that there is no error, start creating a position =======');
+  const txid = await chain.createPosition({
+    userAddress: userAddress,
+    poolInfo,
+    tickLower: priceInTickLower.tick,
+    tickUpper: priceInTickUpper.tick,
+    base,
+    baseAmount,
+    otherAmountMax: amountAWithSlippage,
+    signerCallback,
+  });
+
+  console.log('Create position successfully, txid:', txid);
+}
+
+main();

+ 69 - 0
src/lib/clmm-sdk/src/playgrounds/11_decrease_liquidity_simulate_amount.ts

@@ -0,0 +1,69 @@
+/**
+ * Decrease liquidity proportionally, and display the estimated tokenA and tokenB amounts
+ */
+
+import { PublicKey } from '@solana/web3.js';
+import BN from 'bn.js';
+import { Decimal } from 'decimal.js';
+
+import { LiquidityMath, SqrtPriceMath } from '../index.js';
+
+import { chain } from './config.js';
+
+async function main(): Promise<void> {
+  // Change to your own NFT mint address
+  const nftMint = new PublicKey('CjouQkvVP5XkABWYQYzCAJMpB3g8yJhADtRcRtEZTj8M');
+
+  const positionInfo = await chain.getRawPositionInfoByNftMint(nftMint);
+
+  if (!positionInfo) {
+    throw new Error('Position not found');
+  }
+
+  const { liquidity, tickLower, tickUpper } = positionInfo;
+
+  const decreasePercentage = 50; // decrease 50% of the liquidity
+
+  const liquidityToDecrease = liquidity.mul(new BN(decreasePercentage)).div(new BN(100));
+
+  console.log('Current liquidity amount:', liquidity.toString());
+  console.log('Liquidity to decrease:', liquidityToDecrease.toString());
+
+  const sqrtPriceLowerX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tickLower);
+  const sqrtPriceUpperX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tickUpper);
+
+  const poolInfo = await chain.getRawPoolInfoByPoolId(positionInfo.poolId);
+
+  const amounts = LiquidityMath.getAmountsFromLiquidity(
+    poolInfo.sqrtPriceX64,
+    sqrtPriceLowerX64,
+    sqrtPriceUpperX64,
+    liquidityToDecrease,
+    true
+  );
+
+  console.log(
+    'Estimated tokenA amount:',
+    new Decimal(amounts.amountA.toString())
+      .div(new Decimal(10 ** poolInfo.mintDecimalsA))
+      .toFixed(poolInfo.mintDecimalsA)
+  );
+  console.log(
+    'Estimated tokenB amount:',
+    new Decimal(amounts.amountB.toString())
+      .div(new Decimal(10 ** poolInfo.mintDecimalsB))
+      .toFixed(poolInfo.mintDecimalsB)
+  );
+
+  // const txid = chain.decreaseLiquidity({
+  //   userAddress,
+  //   nftMint,
+  //   // Decrease half of the liquidity
+  //   liquidity: liquidityToDecrease,
+  //   signerCallback,
+  // });
+
+  // console.log(txid);
+}
+
+main();

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott