Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions apps/amm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,16 +215,16 @@ wallet/runbook display them) or hex — the app normalizes both to hex.

The Pools view is config-driven the same way: it reads a flat JSON list from the
`AMM_POOLS_CONFIG` environment variable (absolute path) and renders one row per
entry. `tokenA`/`tokenB` are the display symbols and `feeBps` the fee tier;
entry. `tokenA`/`tokenB` are the display symbols;
`poolId`/`tokenADefinitionId`/`tokenBDefinitionId` identify the pool on-chain.
Adding more pairs is purely a config edit — no app change:
The swap fee is not a pool field — it is instance-wide (`AmmConfig.swapFeeBps`),
read from the config. Adding more pairs is purely a config edit — no app change:

```json
[
{
"tokenA": "TKA",
"tokenB": "TKB",
"feeBps": 1,
"poolId": "9qbX…",
"tokenADefinitionId": "4T69…",
"tokenBDefinitionId": "7Zc2…"
Expand All @@ -239,9 +239,9 @@ cp apps/amm/amm-pools.json.example apps/amm/amm-pools.json # then replace the
```

If `AMM_POOLS_CONFIG` is unset, unreadable, or not a valid JSON array, the Pools
list shows its empty state. Entries missing `tokenA`, `tokenB`, or a numeric
`feeBps` are skipped individually. The AMM testnet setup script writes this file
for the pool(s) it seeds (see below).
list shows its empty state. Entries missing `tokenA` or `tokenB` are skipped
individually. The AMM testnet setup script writes this file for the pool(s) it
seeds (see below).

The **Pool** tab in the nav bar is a dropdown with two entries. *Create pool*
opens the new-position / add-liquidity form. *View positions* lists the wallet's
Expand All @@ -250,7 +250,7 @@ definition id cannot be reversed back to its pool, so the app resolves every
pool in this config and matches each pool's `lpDefinitionId` against the
wallet's token holdings. A pool that is not in the config therefore cannot
appear, however many LP tokens the wallet holds for it. Each row shows the pair,
fee tier, the wallet's claim on both reserves (`reserve × lpBalance / lpSupply`,
the instance swap fee, the wallet's claim on both reserves (`reserve × lpBalance / lpSupply`,
floored like the program's own payout), and its share of the pool. The list
needs an open wallet.

Expand All @@ -265,7 +265,7 @@ holds two LP accounts for one pool; a burn names a single account, so the sheet
draws on the largest and says so when the position spans more than one.

Clicking a row in the Pools list opens the pool detail view, which reads the live pool through
`resolvePoolAccount` and shows the reserve split, spot price, fee tier, LP
`resolvePoolAccount` and shows the reserve split, spot price, the instance swap fee, LP
supply, an estimate of the fees accrued into the reserves, and the pool's
account ids. Its **Swap** and **Add liquidity** buttons switch tabs with the
pair preselected. Both the detail view and the preselection need
Expand Down
4 changes: 2 additions & 2 deletions apps/amm/VALIDATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ transaction ID is displayed.
## Acceptance Checklist

- Context exposes Wallet-Scoped Holdings and configured token definitions.
- Unsupported fee tiers are disabled and explain why.
- Active pool fee tier is fixed to the stored pool fee.
- No fee selection in the form — the swap fee is instance-wide (set at
`initialize`, stored in the AMM config) and applies to every pool.
- Missing pool flow accepts an editable `X Token A = Y Token B` ratio and
scales either deposit from the minimum that mints more than
`MINIMUM_LIQUIDITY`.
Expand Down
1 change: 0 additions & 1 deletion apps/amm/amm-pools.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
{
"tokenA": "TKA",
"tokenB": "TKB",
"feeBps": 1,
"poolId": "REPLACE_WITH_POOL_PDA",
"tokenADefinitionId": "REPLACE_WITH_TOKEN_A_DEFINITION_ID",
"tokenBDefinitionId": "REPLACE_WITH_TOKEN_B_DEFINITION_ID"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ ColumnLayout {
value: root.actionText()
}

SummaryRow {
Layout.fillWidth: true
label: qsTr("Fee")
value: root.snapshot.feeText || "-"
}

SummaryRow {
Layout.fillWidth: true
label: qsTr("Deposit")
Expand Down
157 changes: 1 addition & 156 deletions apps/amm/qml/components/liquidity/NewPositionForm.qml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ AmmActionCard {
? String(root.activePoolQuote.lpDefinitionId || "") : ""
property string selectedTokenAId: ""
property string selectedTokenBId: ""
property int selectedFeeBps: 30
property int slippageBps: 50
property string amountA: ""
property string amountB: ""
Expand Down Expand Up @@ -85,13 +84,6 @@ AmmActionCard {
// Whether the wallet session is ready (from the flow); gates funding/selection like the
// old context "ready"/"no_wallet" status did, minus the network envelope.
property bool walletReady: false
// Supported fee tiers as raw bps, injected from backend.feeTiers() (amm_core's
// SUPPORTED_FEE_TIERS). The selector's delegate wants { feeBps } rows, so wrap
// each int; labels are derived locally via feeLabel().
property var feeTiers: []
readonly property var feeTierModel: (root.feeTiers || []).map(function(bps) {
return { "feeBps": Number(bps) }
})
readonly property var tokenA: root.tokenById(root.selectedTokenAId)
readonly property var tokenB: root.tokenById(root.selectedTokenBId)
readonly property int decimalsA: 0
Expand All @@ -111,7 +103,6 @@ AmmActionCard {
// undefined ⇒ not resolved yet (neither branch shown).
readonly property bool activePool: root.flowState.poolExists === true
readonly property bool missingPool: root.flowState.poolExists === false
readonly property int poolFeeBps: root.knownPoolFeeBps()
readonly property bool compact: root.width < 420
readonly property bool hasPair: root.selectedTokenAId.length > 0
&& root.selectedTokenBId.length > 0
Expand Down Expand Up @@ -388,87 +379,6 @@ AmmActionCard {
color: root.theme.colors.divider
}

ColumnLayout {
Layout.fillWidth: true
spacing: 8
visible: !root.contextLoading

Text {
text: qsTr("Fee tier")
color: root.theme.colors.textPrimary
font.pixelSize: 13
font.weight: Font.Medium
}

GridLayout {
Layout.fillWidth: true
columns: root.compact ? 2 : 4
columnSpacing: 8
rowSpacing: 8

Repeater {
model: root.feeTierModel

Item {
id: feeTierOption

required property var modelData
readonly property string disabledReason: root.feeDisabledReason(modelData)
readonly property bool invalid: root.fieldHasError("feeBps")
&& feeTierButton.checked
Layout.fillWidth: true
implicitHeight: 40

Button {
id: feeTierButton

anchors.fill: parent
text: parent.modelData.label || root.feeLabel(parent.modelData.feeBps)
checkable: true
checked: root.selectedFeeBps === parent.modelData.feeBps
enabled: parent.disabledReason.length === 0 && !root.submitting
onClicked: root.selectFee(parent.modelData.feeBps)

contentItem: Text {
text: feeTierButton.text
color: feeTierButton.enabled
? root.theme.colors.textPrimary
: root.theme.colors.textPlaceholder
font.pixelSize: 12
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}

background: Rectangle {
radius: 6
color: feeTierButton.checked
? root.theme.colors.selection
: root.theme.colors.inputBg
border.color: feeTierOption.invalid
? root.theme.colors.error
: feeTierButton.checked
? root.theme.colors.ctaBg
: root.theme.colors.borderStrong
border.width: 1
}
}

MouseArea {
id: disabledFeeHover
anchors.fill: parent
enabled: parent.disabledReason.length > 0
hoverEnabled: true
acceptedButtons: Qt.NoButton
}

ToolTip.visible: disabledFeeHover.containsMouse
ToolTip.text: disabledReason
}
}
}
}

RowLayout {
Layout.fillWidth: true
spacing: 10
Expand Down Expand Up @@ -851,7 +761,6 @@ AmmActionCard {
function resetAll() {
root.selectedTokenAId = ""
root.selectedTokenBId = ""
root.selectedFeeBps = 30
root.slippageBps = 50
root.resolvingTokenId = ""
root.resolvingTokenSide = ""
Expand Down Expand Up @@ -896,30 +805,6 @@ AmmActionCard {
}
}

function knownPoolFeeBps() {
var direct = root.feeBpsFromQuote(root.quotePayload)
if (direct > 0)
return direct
if (root.quoteMatchesSelectedPair(root.activePoolQuote))
return Number(root.activePoolQuote.poolFeeBps || 0)
return 0
}

function feeBpsFromQuote(quote) {
if (!root.quoteMatchesSelectedPair(quote))
return 0
var direct = Number(quote.poolFeeBps || 0)
if (direct > 0)
return direct
var errors = quote.errors || []
for (var i = 0; i < errors.length; ++i) {
var value = Number(errors[i].details ? errors[i].details.poolFeeBps : 0)
if (value > 0)
return value
}
return 0
}

function quoteMatchesPair() {
return root.quoteMatchesSelectedPair(root.quotePayload)
}
Expand All @@ -932,33 +817,6 @@ AmmActionCard {
|| (tokenAId === root.selectedTokenBId && tokenBId === root.selectedTokenAId))
}

function selectFee(feeBps) {
root.selectedFeeBps = feeBps
root.noteDraftChanged()
root.requestQuote(true)
}

function feeDisabledReason(tier) {
if (tier.enabled === false)
return tier.disabledReason || qsTr("This fee tier is unavailable.")
if (root.poolFeeBps > 0 && Number(tier.feeBps) !== root.poolFeeBps)
return qsTr("Existing pool uses %1. Fee tier is fixed for this pair.")
.arg(root.feeLabel(root.poolFeeBps))
return ""
}

function feeLabel(feeBps) {
if (feeBps === 1)
return "0.01%"
if (feeBps === 5)
return "0.05%"
if (feeBps === 30)
return "0.30%"
if (feeBps === 100)
return "1.00%"
return root.formatBps(feeBps)
}

function buildQuoteRequest() {
var errors = []
if (!root.hasPair) {
Expand Down Expand Up @@ -1063,8 +921,7 @@ AmmActionCard {
"tokenAId": root.displayIsCanonical
? root.selectedTokenAId : root.selectedTokenBId,
"tokenBId": root.displayIsCanonical
? root.selectedTokenBId : root.selectedTokenAId,
"feeBps": root.selectedFeeBps
? root.selectedTokenBId : root.selectedTokenAId
}
}

Expand Down Expand Up @@ -1205,7 +1062,6 @@ AmmActionCard {
"deposit_ratio_mismatch": qsTr("Deposit amounts must match the initial price."),
"minimum_lp_zero": qsTr("Slippage leaves no minimum LP output."),
"invalid_slippage": qsTr("Slippage must be between 0% and 50%."),
"fee_tier_mismatch": qsTr("Select the existing pool fee tier."),
"no_wallet": qsTr("Connect a wallet to submit this position."),
"wallet_unavailable": qsTr("Wallet is unavailable."),
"wallet_submission_failed": qsTr("Wallet submission failed. Review and retry manually."),
Expand Down Expand Up @@ -1327,16 +1183,6 @@ AmmActionCard {
function applyQuoteSideEffects() {
if (root.quoteStale)
return
if (root.poolFeeBps > 0 && root.selectedFeeBps !== root.poolFeeBps) {
root.selectedFeeBps = root.poolFeeBps
root.localErrors = []
root.quoteRequested(true, {
"ok": true,
"errors": [],
"request": root.poolProbeRequest(root.pairRequest())
})
return
}

if (root.quotePayload.status !== "ok")
return
Expand Down Expand Up @@ -1455,7 +1301,6 @@ AmmActionCard {
// taken from the active-pool quote; ignored by the create path.
"minLp": String(root.quotePayload.minimumLp || ""),
"pairText": qsTr("%1 / %2").arg(root.shortTokenName(root.tokenA)).arg(root.shortTokenName(root.tokenB)),
"feeText": root.feeLabel(root.selectedFeeBps),
"depositAText": root.quoteAmount("actualAmountA", "actualAmountB", "A"),
"depositBText": root.quoteAmount("actualAmountA", "actualAmountB", "B"),
"expectedLpText": root.rawLpText(root.quotePayload.expectedLp),
Expand Down
22 changes: 4 additions & 18 deletions apps/amm/qml/pages/LiquidityPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ Item {
// account selectors; refetched when the wallet opens.
property var holdings: []

// The AMM's supported fee tiers (backend.feeTiers()) feeding the fee selector.
// Program-derived and wallet-independent, so it's fetched once when the backend
// becomes available.
property var feeTiers: []

// The liquidity token selector rows (backend.resolveTokens()): the app-owned union of
// configured tokens and persisted-custom tokens. Refetched when the wallet opens/closes
// (holdingId/balance change) and after a custom token is added.
Expand Down Expand Up @@ -96,14 +91,6 @@ Item {
function(err) { console.warn("tokenHoldings error:", err) })
}

function refreshFeeTiers() {
if (!root.backend || root.runtime === null || root.feeTiers.length > 0)
return
root.runtime.watch(root.backend.feeTiers(),
function(list) { root.feeTiers = list },
function(err) { console.warn("feeTiers error:", err) })
}

function refreshTokens() {
if (!root.backend || root.runtime === null)
return
Expand Down Expand Up @@ -149,9 +136,9 @@ Item {
})
}

onBackendChanged: { root.refreshHoldings(); root.refreshFeeTiers(); root.refreshTokens() }
onRuntimeChanged: { root.refreshHoldings(); root.refreshFeeTiers(); root.refreshTokens() }
Component.onCompleted: { root.refreshHoldings(); root.refreshFeeTiers(); root.refreshTokens() }
onBackendChanged: { root.refreshHoldings(); root.refreshTokens() }
onRuntimeChanged: { root.refreshHoldings(); root.refreshTokens() }
Component.onCompleted: { root.refreshHoldings(); root.refreshTokens() }

Connections {
target: root.backend
Expand Down Expand Up @@ -356,10 +343,9 @@ onBackendChanged: { root.refreshHoldings(); root.refreshFeeTiers(); root.refresh
headingText: form.hasPair ? qsTr("Deposit tokens") : qsTr("Select pair")
headingDetail: form.hasPair
? qsTr("Specify the token amounts for your liquidity contribution.")
: qsTr("Choose two tokens and a fee tier for this position.")
: qsTr("Choose two tokens for this position.")
showRefreshAction: false
holdings: root.holdings
feeTiers: root.feeTiers
tokens: root.resolvedTokens
loadingTokens: root.tokensLoading
walletReady: newPositionFlow.walletStateReady
Expand Down
Loading
Loading