> ## Documentation Index
> Fetch the complete documentation index at: https://docs.powfi.alephium.org/llms.txt
> Use this file to discover all available pages before exploring further.

# 在 CPMM 上兑换

> 在本地报价、展示价格影响，并在滑点保护下执行精确输入或精确输出兑换。

export const VersionBanner = ({network = 'mainnet', products = [], lang = 'en'}) => {
  const SDK_VERSION = '1.0.2';
  const ADDRESSES = {
    mainnet: {
      cpmm: {
        Router: 'ze15rnAwTyVCPnDhN7J8An3SvNeWBsZHAVMeBx1ehXeP',
        TokenPairFactory: '24CXRLvj1QiDH6riwr1EPERG7BcduHKZtktygbq97MfYb'
      },
      clmm: {
        PoolFactory: '24gqd4DMQXVzjm5QDAGxUtYupx3WM2PSu3vZUseuBCV9h',
        PositionManager: 'vFyKSqnJHS9MicrojBcLgztyRFqhS4hB349Ci3LJCELw'
      },
      staking: {
        XAlphToken: '225WevmFp5ZgzPsyVJTvyp2v2uyKrvp329HfrVmzffnWj',
        RewardFeeCollector: '22CE7vG6u64zjjsLZLxeJqQuPDydjPvbP9iG1d861j2G7'
      }
    },
    testnet: {
      cpmm: {
        Router: 'xaueT8kPMvpnaFJEfHv6CJLgqsSPpoNmHjF1QgTxoF9R',
        TokenPairFactory: '29hdd9b9Gp7oXuamwHKfUqBaRXP487HoeTcutS3RhZJEj'
      },
      clmm: {
        PoolFactory: 'z2QFT7qBQrfm8UwAYG3VHWoHs7B56ZDFNdSCcDu5j53m',
        PositionManager: '21p6egW8d6FEyVVuH17WqvHCARsCsD61GcBmeCr75XdGT'
      },
      staking: {
        XAlphToken: '23psDicimC5VdpM56wrgqgCzQzcyXWGCkQGSYWbtZAPRq',
        RewardFeeCollector: '29qQ1LPJ5uqawRviXLVDMeo19JUygRrNNTQy5RUBh817D'
      }
    }
  };
  const EXPLORERS = {
    mainnet: 'https://explorer.alephium.org/addresses/',
    testnet: 'https://testnet.alephium.org/addresses/'
  };
  const LABELS = {
    en: {
      sdk: 'SDK',
      network: 'Network',
      contracts: 'Contracts',
      all: 'All addresses',
      allNetworks: 'mainnet · testnet · devnet',
      devnet: 'Local deployment (addresses vary per devnet)'
    },
    zh: {
      sdk: 'SDK 版本',
      network: '网络',
      contracts: '合约',
      all: '全部地址',
      allNetworks: 'mainnet · testnet · devnet',
      devnet: '本地部署（每个 devnet 的地址不同）'
    },
    fr: {
      sdk: 'SDK',
      network: 'Réseau',
      contracts: 'Contrats',
      all: 'Toutes les adresses',
      allNetworks: 'mainnet · testnet · devnet',
      devnet: 'Déploiement local (adresses propres à chaque devnet)'
    }
  };
  const t = LABELS[lang] ?? LABELS.en;
  const contractsPage = lang === 'en' ? '/reference/contracts' : `/${lang}/reference/contracts`;
  const short = a => `${a.slice(0, 6)}…${a.slice(-4)}`;
  const entries = [];
  const addrs = ADDRESSES[network];
  if (addrs) {
    for (const p of products) {
      for (const [name, address] of Object.entries(addrs[p] ?? ({}))) entries.push({
        name,
        address
      });
    }
  }
  let contractsCell;
  if (network === 'devnet' && products.length > 0) {
    contractsCell = <span>{t.devnet}</span>;
  } else if (entries.length > 0) {
    contractsCell = entries.map(({name, address}, i) => <span key={name}>
        {i > 0 && <span className="mx-1 opacity-50">·</span>}
        {name}{' '}
        <a href={EXPLORERS[network] + address} target="_blank" rel="noreferrer" title={address}>
          <code>{short(address)}</code>
        </a>
      </span>);
  }
  const row = (label, value) => <div className="flex flex-wrap gap-x-2">
      <span className="font-semibold min-w-[5.5rem]">{label}</span>
      <span className="flex-1 min-w-0 break-words">{value}</span>
    </div>;
  return <div className="not-prose mb-6 rounded-xl border border-zinc-950/10 dark:border-white/10 bg-zinc-50 dark:bg-white/5 px-4 py-3 text-sm leading-6 text-zinc-700 dark:text-zinc-300">
      {row(t.sdk, <code>@alephium/powfi-sdk@{SDK_VERSION}</code>)}
      {row(t.network, network === 'all' ? t.allNetworks : network)}
      {contractsCell && row(t.contracts, <span>
            {contractsCell}
            <span className="mx-1 opacity-50">·</span>
            <a href={contractsPage}>{t.all} →</a>
          </span>)}
    </div>;
};

<VersionBanner network="mainnet" products={['cpmm']} lang="zh" />

## 1. 加载并获取池

```ts theme={null}
import { Powfi, CpmmModule, PoolNotFoundError } from '@alephium/powfi-sdk'
import { ALPH_TOKEN_ID } from '@alephium/web3'

const powfi = Powfi.load({ networkId: 'mainnet', signer })
const tokenIn = ALPH_TOKEN_ID
const tokenOut = '<token id>'

let pool
try {
  pool = await powfi.cpmm.getPoolState(tokenIn, tokenOut)
} catch (e) {
  if (e instanceof PoolNotFoundError) throw new Error('No CPMM pool for this pair')
  throw e
}
```

## 2. 报价（离线）

<Tabs>
  <Tab title="精确输入">
    ```ts theme={null}
    const quote = CpmmModule.computeSwapAmount({
      state: pool,
      tokenInId: tokenIn,
      tokenOutId: tokenOut,
      amountIn: 5n * 10n ** 18n, // sell exactly 5 ALPH
      slippageBps: 50n
    })

    quote.tokenOutAmount        // expected output
    quote.minimalTokenOutAmount // guaranteed minimum
    quote.priceImpact           // percent
    ```
  </Tab>

  <Tab title="精确输出">
    ```ts theme={null}
    const quote = CpmmModule.computeSwapAmount({
      state: pool,
      tokenInId: tokenIn,
      tokenOutId: tokenOut,
      amountOut: 100n * 10n ** 6n, // buy exactly 100 units of a 6-decimal token
      slippageBps: 50n
    })

    quote.tokenInAmount        // expected input
    quote.maximalTokenInAmount // worst-case input
    ```
  </Tab>
</Tabs>

根据原始数量和精度显示成交价格：

```ts theme={null}
import Decimal from 'decimal.js'
import { NumericUtils } from '@alephium/powfi-sdk'

const inHuman = NumericUtils.scaleDecimal(quote.tokenInAmount, quote.tokenInInfo.decimals)
const outHuman = NumericUtils.scaleDecimal(quote.tokenOutAmount, quote.tokenOutInfo.decimals)
const price = outHuman.div(inHuman) // tokenOut per tokenIn
```

## 3. 执行

```ts theme={null}
const { address } = await signer.getSelectedAccount()

// Optional: pre-check balances to fail fast with a readable error
const bal = await powfi.nodeProvider.addresses.getAddressesAddressBalance(address)
const balances = new Map<string, bigint>([[ALPH_TOKEN_ID, BigInt(bal.balance)]])
for (const t of bal.tokenBalances ?? []) balances.set(t.id, BigInt(t.amount))

const result = await powfi.cpmm.swap(
  {
    tokenInId: tokenIn,
    tokenOutId: tokenOut,
    amountIn: 5n * 10n ** 18n,
    slippageBps: 50n,
    sender: address,
    ttlMinutes: 10
  },
  balances
)
console.log(result.txId)
```

`swap` 在签名前会重新获取池状态并重新报价，所以它强制执行的最小值基于发送时的状态，而不是你展示给用户时的状态。

<Note>
  ALPH 余额检查只和兑换输入作比较。交易还需要几个 `DUST_AMOUNT` 的 ALPH 用于输出，外加 gas。当输入代币是 ALPH 时，请保留一些余量。
</Note>

## 4. 处理错误

| 错误 | 向用户展示 |
| - | - |
| `PriceImpactTooHighError` | "价格影响超过 5%，请减少数量。" |
| `InsufficientBalanceError` | 消息中已包含所需和可用数量。 |
| `InsufficientLiquidityError` | 精确输出数量超过了池的储备量。 |
| 提到路由合约的节点错误 | 触发了滑点或截止时间，请重新报价。 |

## 把价格推到目标值

用于套利或再平衡时，`swapTo` 会计算把池价格推到目标价格所需的交易（目标价格为排序后顺序中每个 token0 值多少 token1，人类可读单位）：

```ts theme={null}
await powfi.cpmm.swapTo({ tokenA: tokenIn, tokenB: tokenOut, targetPrice: 0.052, sender: address, slippageBps: 30n })
```

`swapTo` 会跳过 5% 的价格影响保护。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.