Repository navigation
Feat/ts v4.15.0 - #1025
Open
gummy789j wants to merge 35 commits into
Open
Feat/ts v4.15.0#1025gummy789j wants to merge 35 commits into
gummy789j wants to merge 35 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.
Approvals and other contract calls need "Custom contracts"; Permit2 grants and oversized transactions need "Sign by Hash". The help named only the latter, and on three of the six write commands. Share one help line across all six, add a TRON app settings section to the Ledger guide, and point every command doc at it.
…only wallet-cli kept its own copy of addresses the SDK already ships: V2 router, V3 position manager and WTRX in the builtins, the launchpad, a V4 position manager record cross-checked against the SDK, an unread V4 pool manager, and a config override for the Universal Router. The V4 and router paths already took theirs from the SDK, so two sources co-existed and every SDK bump would have needed a matching builtin edit. All addresses now come from @sun-protocol/sun-sdk-chains inside the outbound adapters. The use cases ask LiquidityPort.contracts() instead of reading the network descriptor. Values are unchanged; a unit test pins them. Availability is now an explicit switch rather than the presence of an address, because the SDK also knows Nile's launchpad and the release must keep the curve off there: - sunswap.liquidity: true -> liquidity commands and position-info - sunpump.curve: true -> sunpump buy/sell and the curve branch of swap Mainnet ships both; Nile ships liquidity only. Released behaviour is the same. config.yaml now rejects the retired sunswap.contracts and sunpump.launchpad fields with a pointer to the switch, and validates the sunpump block (apiBaseUrl must be http(s)), which was previously unchecked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments and test titles cited section numbers of planning documents that are not part of the repository (PM/PRD sections, decision-log IDs, ADR and review-finding numbers, plan task numbers, QA notes), and some narrated release history instead of explaining the code. Rewrite them to state the behaviour and its rationale directly, keep measured evidence (dated Nile/mainnet observations), and translate the few remaining non-English comments. No code or runtime output changes; only comments, test titles and one dependency-cruiser rule message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the deprecated `ZodIssue` alias with `z.core.$ZodIssue` in the sunpump and sunswap schema tests, and resolve the launch logo fixture with `fileURLToPath` instead of `URL.pathname`, which percent-encodes spaces and yields `/C:/...` on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When an account already holds a Permit2 grant covering the trade, the planner answers already-approved and produces no permit. The router swap refused this with permit_mismatch, on the belief that the router call must carry a permit. It does not: the SDK's own swap planner encodes the call without one, and the router pulls through Permit2 either way. Accounts used with other SDK-built clients, whose planner grants MAX_UINT160 for thirty days, could not swap that token. Send the call bare in that case, sign nothing, and still check the token's allowance to Permit2, which is a separate layer. A dry run can now price the swap itself, and its note no longer blames a signature when only the approval is pending. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
V4 mint and increase took --deadline verbatim, so a past or fractional value was signed and broadcast, then reverted on chain at the caller's expense. Route both through resolveDeadline like every other liquidity path. The increase tests pinned a deadline that has since passed; move it far into the future. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owed-fees read is a static `collect`, which the position manager only lets the owner or an approved address call. It was made from the default zero-address reader, which passed only because an unset `getApproved` is also zero. A token with a per-token approval reverted with `Not approved`, breaking collect-fees (dry run included) and dropping the fee figures from remove-liquidity and position-info. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preserve V4 position integrity errors, build pending approvals without checking them on chain, bound numeric expansion and token precision, and match V3 Collect events by token ID. Covers CR-003, CR-007, CR-019, CR-020, CR-030, and CR-031.
Several pages were written before later fixes landed and drifted from the code: - Availability: describe the sunswap.liquidity / sunpump.curve switches and the service URLs instead of configured contract addresses, and say that swap is mainnet only. - position-list takes --account and publishes positionType; the sunswap overview no longer claims every query runs without a wallet. - Dry-run fees: document the all / approvals / none cases for swap and V4 deposits, fix the V4 examples, and add "none" to the feeCovers table in machine-interface. - Name both unlimited-approval paths (V4 Permit2 allowance and selling into the SunPump curve) and fix the broken cross-reference. - Correct remove-liquidity, position-info, pool-list, pool-search and the sunpump pages (fee rate, approvalTxIds, launch refusals, exit codes), and list the sunswap/sunpump writes in the global options. - Move sections appended after "See also" in machine-interface and the Ledger guide back into the body; their wording is unchanged. - CLAUDE.md: update the per-family binding counts (TRON 96, EVM 30). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The --quote table labelled every router trading fee as TRX scaled by six decimals, but tradingFee is in base units of the input token, so a USDT or 18-decimal input printed the wrong unit and scale. Keep TRX only for the SunPump platform fee. Identify native TRX by address rather than by symbol when attaching the platform-fee note, so a token that calls itself TRX is not treated as the native coin. The failed-swap note no longer says the TRC20 approval expires; only the Permit2 grant does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pin every @sun-protocol/sun-sdk-* package to 0.1.0 instead of 0.1.0-beta.0. The published tarballs are code-identical to the beta; only internal dependency versions and bundler chunk ordering differ. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zerodevblock-cyber
approved these changes
Oct 8, 2026
A user's sunswap or sunpump block used to replace the builtin one wholesale. An overlay that set only a Nile routerApiBaseUrl therefore dropped the builtin liquidity switch, and one that overrode the mainnet router dropped the market API and liquidity with it — features lost to a config file that said nothing about them. Both blocks now merge field by field: a field the user writes overrides the builtin, a field they leave out keeps its builtin value, and turning a feature off takes an explicit false. A block that is not a mapping is still rejected, and so are the retired contracts/launchpad fields. The gasfree block keeps replace semantics: its fields describe one deployment and must not be mixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
baonian253-a11y
approved these changes
Oct 8, 2026
SunPump deploys the token before it answers the create call, so a call that times out has not failed: R11 QA hit a 20s --timeout, got a plain `timeout` (retry "same"), and the token was already on chain. A caller that obeyed the retry hint would have created a second, permanent token. Once the request may have reached the service, only an explicit refusal (a non-zero envelope code, an HTTP 4xx, a rate limit) or a usage error raised before sending passes through unchanged. Every other failure -- timeout, 5xx, dropped connection, unparseable or token-less reply -- is now `launch_outcome_unknown` (exit 1, retry "never"), with the name and symbol in details and a `sunpump token-search <symbol>` pointer. The old hint pointed at `token-list --owner`, which the caller cannot use: the owner is chosen by SunPump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
V2 and V3 deposits refused an uncovered side up front, but the shared V4 sizing (mint and increase) did not. Measured on Nile through a Ledger: with 9 USDT against a 60.74 USDT side, the dry run passed, the device signed the Permit2 grant, and only the main call's estimate then reverted with a bare execution_reverted. A V4 dry run cannot estimate the main call, so this check is its only signal. Each TRC20 side is now checked against its ceiling (what Permit2 may pull), not the deposit, before approvals are planned or anything is signed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.