Repository navigation
Feat/ts v4.15.0 - #1025
Open
gummy789j wants to merge 15 commits into
Open
Feat/ts v4.15.0#1025gummy789j wants to merge 15 commits into
gummy789j wants to merge 15 commits into
Conversation
Seventeen commands: eleven under `sunswap` — price, token-list, token-search, pool-list, pool-search, position-list, position-info, swap, add-liquidity, remove-liquidity, collect-fees — and six under `sunpump` — buy, sell, launch, token-list, token-info, token-search. Liquidity covers V2, V3 mint and increase, and V4 mint and increase; withdrawal and fee collection cover V2, V3 and V4 as each supports them. Everything that decides an amount was measured against a live chain rather than reasoned about, and the measurements are in the code beside what they justify. Four of them changed the implementation: A V4 deposit is bounded from ABOVE where every withdrawal is bounded from below, which means the same approximation is conservative on one protocol and fatal on the other. Sizing from the tick's own price rather than the pool's cost a reverted mint — the contract wanted 172953 where we offered 170007 — and the same imprecision sat harmlessly in V3 only because V3 bounds the other way. A V4 withdrawal settles accrued fees alongside the principal, so the receipt splits them. The spec says the opposite; on the transaction that proved it, 95% of what arrived was fees, and reporting only the principal would have sent the caller looking for money already in their account. A native V4 deposit carrying a Permit2 grant was sent with no TRX value, because the multicall computes none of its own and every native deposit carries a grant. The earlier live tests happened to use a token-only pool, which is exactly the branch where it could not bite. The create endpoint reports time in plain seconds, not the unit the spec names. Rescaling dated a token made that minute to the year 58709, while the query path read the same token back correctly — one field, two paths, both answering success. The market and curve services answer an unknown parameter by IGNORING it: a misspelled search returns the whole catalogue with HTTP 200, and an unknown sort field returns a plausible list in some other order while echoing the field name back. So every parameter name and sort field is a verified whitelist, and a value nobody measured is refused rather than forwarded. Permit2 grants are bounded to the trade and the hour, never unlimited, and are checked against what they were meant to authorize before a signature exists and by recovery afterwards. The Universal Router's calldata is decoded and checked against the same figures, because the floor, the recipient and the deadline live in the ABI words and nowhere a caller can read. Amounts are decimal strings and bigint throughout, every published amount carries its scale, and a figure that could not be read publishes nothing rather than a zero — on these commands a zero is a claim.
The packages went up as `@sun-protocol/sun-sdk-*` at 0.1.0-beta.0 on 2026-09-28. Until now they came from a GitLab registry on a private network (10.90.14.25), which a GitHub-hosted runner cannot reach by design — CI never got past `npm ci`, and npm's own message for that failure reads as a proxy problem and sent the last reader after the wrong thing. So `.npmrc` is gone, and with it `strict-ssl=false`. Forty-four references across the source and the dependency-cruiser rules are renamed, and the lockfile now resolves every package from registry.npmjs.org. Verified against the PUBLISHED beta rather than the local checkout, which says 1.0.0 and is not the same artefact: all 47 symbols this codebase imports across eight packages are present. Then the whole gate, and — because `v4-contracts.ts` passes almost everything into the SDK through `as never`, so a changed parameter shape would compile and fail at runtime — two real Nile transactions on a native pair, the branch where that would show: an increase (liquidity 602323 → 1635794, 1 TRX deposited) and a withdrawal with the fee split intact. CI keeps a preflight, now generalised: it fails with the actual reason if any dependency stops resolving from the public registry, rather than leaving the next person to rediscover it from a timeout.
A V4 pool key has five parts — currency0, currency1, fee, tickSpacing, hooks — where V3's has three. PM 6.1 carried V3's `--token0` / `--token1` / `--fee` across to V4, which cannot name a pool there, and the gap was plugged with a `--pool` id that the spec never had. That flag is gone. A V4 deposit now names its pool by the key itself, with the same flags `--create-pool` already used, so the two paths differ only by a starting price. `--tick-spacing` is REQUIRED on V4 and has no default, because on V4 it is part of the pool's identity rather than something the fee tier implies. Measured on Nile: TRX/USDT at fee 500 exists TWICE, once at spacing 10 and once at spacing 12, as two distinct pools. A default would have silently picked one of them for anyone who did not know the other existed. With no default, the caller chooses, and a key that names no pool is refused as `pool_not_found` with a message that points at `--tick-spacing` first. `pool-list` now publishes each V4 pool's decoded `tickSpacing` and its `hooks` — "none" rather than the zero address, which on TRON is also the native TRX marker. Before this, the spacing sat only inside a raw parameters word and hooks were not published at all, so a caller had no way to learn what the required flags should say. Also fixed on the way: `--create-pool` now resolves token symbols as the existing-pool path always did (`--token0 TRX` used to fail there), and the tick-range help no longer claims to be V3-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A confirmed V4 mint returned no position id. Every later command names the position by that id — increase, withdraw, collect, position-info — and on Nile `position-list` cannot recover it, so the only record of a new position was a transaction log the caller would have had to decode by hand. Found by minting for real: position 178 confirmed with nothing to say it was 178. V3 already read the id from the ERC-721 `Transfer` the position manager emits from the zero address. A V4 mint emits the same event — measured, on the transaction that created 178 — so the decoder is now shared and told only which manager's log to trust. Confirmed after the fix: the next mint on Nile prints `Position #179`. An id that cannot be read is omitted and warned, never guessed: the deposit is already on chain, so failing would misreport a success, and inventing an id would send the caller to act on a position that is not theirs. The test mock already returned an id for this call; nothing asserted it was used. Mutation-checked: putting the empty branch back turns two cases red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found while writing the command reference against the built CLI, each by comparing what a command printed with what it did. A V4 withdrawal pays accrued fees out alongside the principal, as V3's does, but the dry-run warning and the help said so only for V3 — so a V4 dry run quoted the principal and was silent about the rest. `position-info` cut a price bound's exponent off with its fraction: `2.939544628365392e-39` printed as `2.939544`, a full-range position's lower bound shown 39 orders of magnitude too high and looking entirely plausible. The shared formatter now splits the exponent off first. A V4 deposit's dry run gave each Permit2 grant the transaction's 30-minute deadline as its expiry, while signing a one-hour grant. It now previews the same `now + V4_PERMIT_TTL_SECONDS` the signing path asserts. A V4 deposit published `amountMinimum: "0"` on each side. V4 is bounded from above and has no floor, so that zero told an agent reading the JSON the deposit accepted any amount. It is dropped at publication only: V2 and V3 genuinely need a minimum when they send, so the internal type keeps it. Each has a test, and each test was mutation-checked by putting the old behaviour back and watching it fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This directory ships in the npm package, and it was behind the code. Three pages still said V4 was unsupported: add-liquidity, remove-liquidity and collect-fees. They now cover V4, including that a V4 pool is named by its full key — `--tick-spacing` required, because TRX/USDT at fee 500 exists twice on Nile at spacings 10 and 12 — that a V4 deposit is capped from above where V2 and V3 are floored from below, and that a V4 withdrawal pays accrued fees out with the principal. Five commands had no page at all: sunswap position-info, and sunpump launch, token-info, token-list and token-search. The launch page opens with the fact a reader most needs: the token it creates is not theirs. Every page was written from the built CLI's own `--help` and from examples actually run — read-only commands on mainnet, anything that writes as `--dry-run` only — rather than from the spec, which the shipped behaviour departs from in recorded ways. The group and top-level indexes list exactly what ships. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`sunswap position-info` already turned the revert an unminted id causes into `position_not_found`, but add-liquidity, remove-liquidity and collect-fees read the same position without that step, so the same wrong id surfaced as `execution_reverted: TRON constant call reverted` (V3 `Invalid token ID`, V4 `NOT_MINTED` on Nile). A caller could not tell a mistyped id from a contract failure. The reinterpretation moves out of position-info into a shared `readPosition`, which every ownership check now goes through. It still narrows only reverts: a timeout or transport failure keeps its own code. Fixes BUG-V415-R5-004. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The creating path sizes a deposit at the caller's --sqrt-price and never read the chain, so on a live pool it planned amounts for a price the pool does not have and published `poolCreated: true` for a pool nobody created. Measured on Nile: TRX/USDT at fee 500, tick spacing 10. It now reads the pool's existence (not its price) and refuses with a new `pool_already_exists` code that points at depositing without --create-pool. machine-interface.md also gains the `position_not_found` row it lacked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`sunswap swap TRX USDT 0 --quote` reached the route service, whose "INVALID AMOUNT" came back as provider_error (exit 1) — a service fault for what is the caller's own input. Zero is now `invalid_amount` (exit 2) before either market is consulted. Fixes BUG-V415-R5-003. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`--wait` is global, so `sunswap swap --quote --wait` and `sunpump launch --wait` accepted it and ignored it, reading as having waited for a confirmation that does not exist. - A command can now declare `rejectsWait` with a reason, mirroring `rejectsAccount`; `sunpump launch` does, since its token is created server-side and no transaction comes back. - `sunswap swap` refuses --wait with --quote, as `tx send` already does with --dry-run. Read-only commands that ignore --wait are unchanged. Fixes BUG-V415-R5-002. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Initialize V4 pools atomically, preserve approval transaction IDs on failures, and verify received amounts from transaction receipts. Correct allowance and Permit2 planning, transaction expiry handling, Ledger cancellation and error mapping, and CLI previews and validation. Keep V4 fee and tick spacing explicit for new positions. Add regression coverage and update command and Ledger documentation.
The Node 22 and 24 CI jobs failed Prettier checks on these three fixtures. Normalize their formatting without changing the parsed JSON data.
Fix the defects found by re-running the v4.15.0 test cases against mainnet and Nile. SunPump - Stop subtracting the platform fee a second time: the contract's getTrxAmountBySaleWithFee already returns the seller's net proceeds. This corrects trxOutExpected, the slippage floor and platformFeePercent. - Derive the minimum sale from getExactTrxAmountForSaleWithFee(token, 1). - Refuse --slippage, --min-out, --wait and --wait-timeout under --quote. - Report account_not_active for an account that is not activated. - Report a failed approval as failed on chain instead of as a confirmed approval that left no allowance. SunSwap swap - Check balances on the router path before planning any approval or Permit2 grant, in every mode. - Route the router through the timed, rate-limit-aware fetch: 429 is reported as provider_rate_limited, and --timeout and the response cap now apply. - Resolve symbols through the signer's token book, and refuse ambiguous symbols with ambiguous_token_symbol. SunSwap liquidity - Keep domain error codes: invalid_option for --sqrt-price without --create-pool, and same_token for a V4 pair of one token. - Publish liquidityExpected in dry-run, and positionManager as the V3/V4 contract key. - Refuse collect-fees with nothing to collect, consistently across modes. - Reject an invalid --recipient as invalid_address. - Print "Create pool" as one line with a decimal price. - Make "No minimum set" the last dry-run line. - Report short position data as invalid_node_response. SunSwap queries - Reject --account on the read-only market queries. - position-list now selects its address with --account, like account balance, and drops --owner. - Add pool-list --min-tvl: filter first, then paginate, within the market API's 1000-row window, warning when that window truncates the result. - Refuse --offset + --limit beyond 1000 locally on the five list commands. Tests - Give the golden project its own sequence group, so `npm test` runs both projects under vitest 4.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(ts): add the SunSwap and SunPump command groups
Seventeen commands: eleven under
sunswap— price, token-list, token-search,pool-list, pool-search, position-list, position-info, swap, add-liquidity,
remove-liquidity, collect-fees — and six under
sunpump— buy, sell, launch,token-list, token-info, token-search. Liquidity covers V2, V3 mint and
increase, and V4 mint and increase; withdrawal and fee collection cover V2,
V3 and V4 as each supports them.
Everything that decides an amount was measured against a live chain rather
than reasoned about, and the measurements are in the code beside what they
justify. Four of them changed the implementation:
A V4 deposit is bounded from ABOVE where every withdrawal is bounded from
below, which means the same approximation is conservative on one protocol and
fatal on the other. Sizing from the tick's own price rather than the pool's
cost a reverted mint — the contract wanted 172953 where we offered 170007 —
and the same imprecision sat harmlessly in V3 only because V3 bounds the
other way.
A V4 withdrawal settles accrued fees alongside the principal, so the receipt
splits them. The spec says the opposite; on the transaction that proved it,
95% of what arrived was fees, and reporting only the principal would have
sent the caller looking for money already in their account.
A native V4 deposit carrying a Permit2 grant was sent with no TRX value,
because the multicall computes none of its own and every native deposit
carries a grant. The earlier live tests happened to use a token-only pool,
which is exactly the branch where it could not bite.
The create endpoint reports time in plain seconds, not the unit the spec
names. Rescaling dated a token made that minute to the year 58709, while the
query path read the same token back correctly — one field, two paths, both
answering success.
The market and curve services answer an unknown parameter by IGNORING it: a
misspelled search returns the whole catalogue with HTTP 200, and an unknown
sort field returns a plausible list in some other order while echoing the
field name back. So every parameter name and sort field is a verified
whitelist, and a value nobody measured is refused rather than forwarded.
Permit2 grants are bounded to the trade and the hour, never unlimited, and
are checked against what they were meant to authorize before a signature
exists and by recovery afterwards. The Universal Router's calldata is decoded
and checked against the same figures, because the floor, the recipient and
the deadline live in the ABI words and nowhere a caller can read.
Amounts are decimal strings and bigint throughout, every published amount
carries its scale, and a figure that could not be read publishes nothing
rather than a zero — on these commands a zero is a claim.