Skip to main content

Exit-Only Pools

Overview​

astroport-pair-exit-only is the code a pool is migrated to when it must only allow balanced withdrawals. It was written for the six Neutron pools drained in the 2026-09-22 incident. Their balances had gone to zero while their LP supply was unchanged, so a small deposit would have minted almost all LP tokens and taken the refill, and swaps would have traded the refill at stale prices. The pools were migrated to this code, then refilled with the returned assets, so their LPs can withdraw their share.

An exit-only pool:

  • pays an LP its share of every asset the pool holds, rounded down, and burns the LP tokens;
  • refuses deposits, swaps, config and ownership changes, and any other CW20 hook;
  • answers the pair, pool, share and simulate_withdraw queries; every other query returns an exit-only error.

The migration doesn't write anything: the pool keeps its storage and its cw2 version, so a later build of its original contract that accepts that version could take it back.

NOTE

LP tokens staked in the Incentives contract must be unstaked before they can be withdrawn.

Pools running this code (Neutron)​

PoolAddressPool contract before the migrationLP token
dATOM/USDCneutron1nfns3ck2ykrs0fknckrzd9728cyf77devuzernhwcwrdxw7ssk2s3tjf8rpair-concentrated-duality 4.3.1native
NTRN/USDCneutron18c8qejysp4hgcfuxdpj4wf29mevzwllz5yh8uayjxamwtrs0n9fshq9vtvpair-concentrated-duality 4.3.1native
dATOM/ATOMneutron1yem82r0wf837lfkwvcu2zxlyds5qrzwkz8alvmg0apyrjthk64gqeq2e98pair-concentrated 4.0.2native
USDC/DYDXneutron1j4xpv03fw664mvntlhqnzp5hjqk2nfw00vrgx9qlq97rxc9fu3lqvmszl2pair-concentrated 1.2.13CW20
wstETH/WETHneutron1yw0a7nxa8jdgzmdsme4gwxhj76n44z305qgwrzvlfavgna9epcys3k9m2fpair-concentrated 1.2.15CW20
axlUSDC/USDCneutron1ns2tcunrlrk5yk62fpl74ycazanceyfmmq7dlj6sq8n0rnkuvk7szstkyxpair-transmuter 1.0.0CW20

InstantiateMsg​

None. The code can't be instantiated; pools can only be migrated to it.

MigrateMsg​

The migrate message is empty. Supported pool contracts are astroport-pair-concentrated (1.x and 4.x), astroport-pair-concentrated-duality and astroport-pair-transmuter. Migration is refused for other contracts, for a config that names another address, and for a pool already in pair_concentrated 4.3's exit-only mode (which withholds an asset). Migrating again changes nothing.

json
contract.rs
Copy

_1
{}

ExecuteMsg​

withdraw_liquidity​

For pools with a native (tokenfactory) LP token. Attach the LP tokens as funds, for example factory/neutron18c8qejysp4hgcfuxdpj4wf29mevzwllz5yh8uayjxamwtrs0n9fshq9vtv/astroport/share for NTRN/USDC. The examples below use that pool. assets must be empty, and the call fails if the share would receive nothing.

json
pair.rs
Copy

_23
{
_23
"withdraw_liquidity": {
_23
"assets": [],
_23
"min_assets_to_receive": [
_23
{
_23
"info": {
_23
"native_token": {
_23
"denom": "untrn"
_23
}
_23
},
_23
"amount": "990000"
_23
},
_23
{
_23
"info": {
_23
"native_token": {
_23
"denom": "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81"
_23
}
_23
},
_23
"amount": "1200000"
_23
}
_23
]
_23
}
_23
}

ParamsTypeDescription
assetsVec<Asset>Must be empty. One-sided withdrawals are refused
min_assets_to_receiveOption<Vec<Asset>>Fails the withdrawal if less than these amounts would be returned

receive​

For pools with a CW20 LP token (USDC/DYDX, wstETH/WETH and axlUSDC/USDC). Execute send on the LP token contract with the pool as contract and a base64-encoded withdraw_liquidity hook. The hook takes the same fields as above, and any other hook is refused.

json
Copy

_7
{
_7
"send": {
_7
"contract": "neutron1j4xpv03fw664mvntlhqnzp5hjqk2nfw00vrgx9qlq97rxc9fu3lqvmszl2",
_7
"amount": "1000000",
_7
"msg": "eyJ3aXRoZHJhd19saXF1aWRpdHkiOnt9fQ=="
_7
}
_7
}

ParamsTypeDescription
contractStringThe pool address (here USDC/DYDX)
amountUint128The amount of LP tokens to withdraw
msgBinaryBase64 of {"withdraw_liquidity":{}}

QueryMsg​

pair​

Returns the pool's PairInfo, unchanged by the migration.

json
Copy

_3
{
_3
"pair": {}
_3
}

pool​

Returns the assets the pool holds and the LP supply, as a PoolResponse.

json
Copy

_3
{
_3
"pool": {}
_3
}

json
Copy

_21
{
_21
"assets": [
_21
{
_21
"info": {
_21
"native_token": {
_21
"denom": "untrn"
_21
}
_21
},
_21
"amount": "1000000000"
_21
},
_21
{
_21
"info": {
_21
"native_token": {
_21
"denom": "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81"
_21
}
_21
},
_21
"amount": "1200000000"
_21
}
_21
],
_21
"total_share": "500000000"
_21
}

ParamsTypeDescription
assetsVec<Asset>The pool's balance of each of its assets
total_shareUint128The LP tokens in circulation

share and simulate_withdraw​

Both return what a given amount of LP tokens would be withdrawn for, as a Vec<Asset>.

json
Copy

_5
{
_5
"simulate_withdraw": {
_5
"lp_amount": "1000000"
_5
}
_5
}

ParamsTypeDescription
lp_amountUint128The amount of LP tokens (amount for share)