@floorlaunchpad/carpenter-sdk

中文文档
0.1.1发布于 2026/10/14Apache-2.0TypeScriptES Module

Carpenter, the AMM on Solana: the carpenter_amm IDL and Codama client, pool / position / tick-array layouts and PDAs, swap and liquidity builders, lookup tables, tick math, the protocol fee, the hook interface, plain-pool quotes.

此软件包已被标记为弃用

No longer maintained. Use @floorlaunchpad/sdk.

原始使用文档
文档目录7 节
  1. Install
  2. Read a pool
  3. Quote and swap a plain pool
  4. Create a pool and add liquidity
  5. Wallet prompts per action
  6. Develop
  7. License

@floorlaunchpad/carpenter-sdk

TypeScript SDK for Carpenter, a concentrated-liquidity AMM on Solana with Uniswap-v4-style hooks: pools keyed by (mint0, mint1, fee, tickSpacing, hook), outside LPs, and an allowlist of approved hook programs. Pure TypeScript over @solana/kit 8.3.0; nothing here signs or sends without a wallet you pass in.

It covers reading, quoting and trading:

  • the carpenter_amm IDL and its Codama client (carpenterAmm);
  • account layouts and decoders (AmmConfig, Pool, TickArray, Position, the hook allowlist, the quote roster), PDAs, and read helpers that tag every read with its slot (stateView);
  • transaction builders: swap, swap-from-balance, add / remove liquidity and collect fees (buildModifyLiquidity), close a position, tick arrays, initialize a pool, and planCreatePool (create a pool and its first position);
  • v0 messages through address lookup tables, with the 1,232-byte packet checked before a wallet sees anything;
  • sendSolanaTransactions: one wallet prompt for every transaction of one action;
  • tick math, the protocol-fee arithmetic, event decoders, and plain-pool quotes through the bundled wasm quoter (wasm/carpenter_quote_bg.wasm, the program's own quoting crate compiled to wasm).

Governance and admin instructions (config setters, hook approval, pausing, fee withdrawal) are in the IDL and the generated client but have no builders here.

Install

npm install @floorlaunchpad/carpenter-sdk @solana/[email protected]

@solana/kit is a peer dependency.

Read a pool

import { createSolanaRpc } from '@solana/kit';
import { deploymentFor, fromKitRpc, MAINNET_CHAIN_KEY, programIdsFromDeployment, stateView } from '@floorlaunchpad/carpenter-sdk';

const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const src = fromKitRpc(rpc);
const ids = programIdsFromDeployment(deploymentFor(MAINNET_CHAIN_KEY)); // DEVNET_CHAIN_KEY for devnet

const { slot, value: pool } = await stateView.getPool(src, poolAddress, ids);

Program ids come from deployments.json (deploymentFor), never from a constant in an app; DEFAULT_PROGRAM_IDS exists for tests and scripts.

Quote and swap a plain pool

import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { buildSwap, compileSendable, decodePool, readSwapQuoteAccounts, sendSolanaTransactions, swapTickArrays } from '@floorlaunchpad/carpenter-sdk';
import { initQuote } from '@floorlaunchpad/carpenter-sdk/quote/wasm';
import { getBase64EncodedWireTransaction } from '@solana/kit';

// Node: read the bundled wasm; a browser fetches `@floorlaunchpad/carpenter-sdk/carpenter_quote_bg.wasm` and passes the bytes.
const quoter = await initQuote(readFileSync(createRequire(import.meta.url).resolve('@floorlaunchpad/carpenter-sdk/carpenter_quote_bg.wasm')));
const { value: v } = await readSwapQuoteAccounts(src, poolAddress, { radius: 4, ids });
const q = quoter.quoteSwap({ ...v, zeroForOne: true, amountSpecified: -1_000_000n }); // exact in: negative
const { starts } = await swapTickArrays(poolAddress, q, ids);

const tx = await buildSwap({
  trader, pool: poolAddress, poolState: decodePool(v.pool), zeroForOne: true,
  amountSpecified: -1_000_000n, amountLimit: (q.amountOut * 99n) / 100n, tickArrayStarts: starts, ids,
});
const { value: bh } = await rpc.getLatestBlockhash().send();
const { transaction } = compileSendable('swap', { feePayer: trader, instructions: tx.instructions, lifetime: bh });
await sendSolanaTransactions(wallet, [getBase64EncodedWireTransaction(transaction)], rpc, { lastValidBlockHeight: bh.lastValidBlockHeight });

The quoter is the program's own quoting crate (carpenter-quote) compiled to wasm and shipped in this package (wasm/, WASM_SHA256); every number a quote returns is the crate's. It quotes pools with no swap hook: a pool whose hook takes part in swaps is refused (QuoteHookNotQuotable) and is quoted by its hook's SDK (@floorlaunchpad/sdk-solana for FLOOR pools). A paused pool is refused PoolPaused; a guardian pause (Pool.guardianPausedUntil, a unix time) is not judged by the quoter, which reads no clock: check poolPausedAt(pool, nowUnix) against the cluster's clock. simulateTransaction stays the final check before signing.

Slippage: an exact-in swap's amountLimit is the minimum out and is required (a 0 is refused unless allowNoMinimum: true); derive it from the quote (above: 1 %). buildModifyLiquidity refuses a removal with neither amount0Min nor amount1Min, and a deposit whose maximum is u64::MAX, under the same opt-out.

Compute: CU_LIMITS (swapPlain for a plain swap, 90,000) are estimates, not measurements of your transaction. Simulate first and set the limit from the simulation's unitsConsumed when it matters.

Native SOL: a wSOL side is moved through a temporary wSOL account that the builder creates, funds and closes inside the same transaction (tempWsol, a random seed under the trader's key). The trader's own wSOL account, if any, is neither used nor closed, so wSOL already held is left as it is. Name an account (traderTokenIn / traderTokenOut, ownerToken0 / ownerToken1) to use it instead (it is topped up for an input, never closed), or pass wrapSol: false to use the wSOL ATA with no wrapping at all.

A hooked pool needs its hook's accounts (hookSlice) and compute (cuUnits): the hook's own SDK provides them (@floorlaunchpad/sdk-solana for FLOOR pools). readSwapQuoteAccounts refuses a hooked pool by name.

FLOOR pools use the four-account FLOOR hook slice, which the mainnet and devnet deployments this release lists carry (@floorlaunchpad/sdk-solana of the same release builds it). A hook that settles (HOOK_FLAGS.SETTLES) is called under the ["hook_settle", pool] signer, which leads its slice (findHookSettle); pass writableMints when it mints into a vault during the swap.

Create a pool and add liquidity

import { fromKitRpc, planCreatePool, sendSolanaTransactions, NO_HOOK } from '@floorlaunchpad/carpenter-sdk';

const plan = await planCreatePool({
  payer, mintA, mintB, tokenProgramA, tokenProgramB, decimalsA: 6, decimalsB: 9,
  fee: 3_000, tickSpacing: 60, hook: NO_HOOK, sqrtPriceX64,
  position: { tickLower: -600, tickUpper: 600, liquidity, amountAMax, amountBMax },
  lifetime: bh, ids,
});
await sendSolanaTransactions(wallet, plan.transactions, rpc, {
  steps: plan.stepNames,
  lastValidBlockHeight: bh.lastValidBlockHeight,
  beforeSend: plan.beforeSend(fromKitRpc(rpc)), // stop if the new pool's price moved before the deposit
});

plan.transactions is [initializePool, addLiquidity]: two transactions (together they do not fit one packet), signed with one prompt and sent in order. One wallet signs both, so the position's owner is the payer (owner / initializer other than payer are refused).

Between the two steps the pool exists and is empty, and anyone can move an empty pool's price for free. plan.beforeSend(src, { maxPriceMoveBps }) reads the pool just before the deposit is sent and stops the run (reason guard, PoolPriceMovedError) when its price moved from the initialized price by more than maxPriceMoveBps (default 0: exact). The price can still move between that read and the deposit, so amountAMax / amountBMax are the deposit's only hard bound: set them to what the position needs at the initialized price plus the slippage you accept, never larger.

Wallet prompts per action

sendSolanaTransactions(wallet, steps, rpc, opts?) signs every transaction of one action together, then sends them in order, waiting for each confirmation before the next; the first failure stops the run and names the step (SolanaSendError: index, step, reason, the signatures that landed).

ActionTransactionsPrompts (batch-signing wallet)
Swap11
Add / remove liquidity, collect fees11
Create a pool with its first position (planCreatePool)21

How the wallet is asked, in order of preference: the Wallet Standard solana:signTransaction feature with every transaction in one call (wallet.standard), a legacy signAllTransactions, else signTransaction once per step. The result says which (path) and how many prompts it took.

Caveats:

  • Signing the transactions one by one costs one prompt each; a wallet without a batch call falls back to that.
  • Every transaction the wallet returns is checked before anything is sent: the message bytes must equal the planned message, and every required signature (the expected signer's included: opts.signer, else the Wallet Standard account, else the fee payer) must verify. Anything else stops the run with reason sign.
  • A step is never sent twice. A send that errors without a preflight refusal (a timeout, a dropped connection) may have delivered the transaction, so the step keeps waiting on its signature. A step counts as expired only when its signature is absent, the block height has passed the blockhash's lastValidBlockHeight (pass it, or the sender asks the node), two more blocks have passed, a searchTransactionHistory re-check still finds nothing and the node calls the blockhash invalid. "Blockhash not found" alone is not expiry.
  • After a failure, re-plan from chain state: the steps that landed changed it. A timeout may still land: check its signature before acting again.
  • The transactions share one recent blockhash, valid for roughly 60–90 seconds. Pass opts.replan to rebuild the steps that have not landed when it expires (one more prompt); without it the run stops with reason expired.
  • A wallet that simulates each transaction before signing cannot simulate a step that depends on an account an earlier step creates (the pool in planCreatePool, a lookup table): it shows that step as failing, and it lands once the earlier step has.

Develop

idl/carpenter_amm.json is the program's Anchor IDL with every docs field removed and a few error msg strings shortened (the error names and codes are unchanged): the shipped IDL, and the generated client built from it, carry no doc comments. IDL_SHA256 is the hash of that file as shipped.

npm run generate   # idl/ + deployments.json → src/generated/ (Codama client)
npx tsc -p tsconfig.json --noEmit
npx vitest run
npm run build      # dist/
npm run sync-wasm  # dist/wasm (from `bash wasm/build.sh` at the repository root) → wasm/ and WASM_SHA256

The wasm is built from wasm/carpenter-quote-wasm by wasm/build.sh at the repository root: rustc 1.98.1 (wasm32-unknown-unknown), wasm-bindgen-cli 0.2.128, --target web, on a copy of the sources at a fixed path with paths remapped and the crate's doc comments removed, so the same sources give the same bytes. test/wasm.test.ts replays fixtures/quote-vectors/plain.json and parity-plain.json through the shipped wasm.

The programs are upgradeable through a timelock on mainnet-beta (a 10-minute delay that can only be raised, proposed by the deployer key) and by the deployer key directly on devnet, and pausable by the deployer key; this package makes no claim that they are audited or immutable.

License

Apache-2.0.

版本与发布记录

@floorlaunchpad/carpenter-sdk 共发布 3 个版本,首次发布于 2026/10/14;过去 12 个月发布了 3 个版本。

版本发布日期距今
0.1.1latest2026/10/1420 天前
0.1.02026/10/1420 天前
0.0.0-stage2026/10/1420 天前

关于 @floorlaunchpad/carpenter-sdk 的常见问题

@floorlaunchpad/carpenter-sdk 是什么?

Carpenter, Solana上的AMM: carpenter_amm IDL和Codama客户端,池/位置/期权数组布局和pda,交换和流动性生成器,查找表,期权数学,协议费用,挂钩接口,纯池报价。注意:该包已被作者标记为弃用——No longer maintained. Use @floorlaunchpad/sdk.

如何安装 @floorlaunchpad/carpenter-sdk?

在项目目录运行 npm install @floorlaunchpad/carpenter-sdk,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@solana/kit(^8.3.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@floorlaunchpad/carpenter-sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@floorlaunchpad/carpenter-sdk 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@floorlaunchpad/carpenter-sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@floorlaunchpad/carpenter-sdk 可以商用吗?

@floorlaunchpad/carpenter-sdk 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@floorlaunchpad/carpenter-sdk 还在维护吗?

最新版本 0.1.1 发布于 2026/10/14(20 天前);至今共发布 3 个版本,其中过去 12 个月发布了 3 个;首次发布于 2026/10/14。作者已将其标记为弃用,新项目建议寻找替代方案。