Swap bundles 400 on `stringsInstructions` 128-char limit, making 39 of 186 GalaSwap pools unswappable
- Dominant language
- TypeScript
- Stars
- 138
- Forks
- 44
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 22
Description
## Summary
Since ~2026-08-31, GalaSwap `POST /bundle` rejects `Swap` submissions for any pool whose token composite keys are long enough that a derived `stringsInstructions` entry exceeds 128 characters:
```
(400) each value in stringsInstructions must be shorter than or equal to 128 characters
```
This is not a caller error. The offending strings are constructed deterministically from the pool's own token class keys — by `gswap-sdk` itself — so the limit makes a subset of GalaSwap's own live pools structurally unswappable. **39 of 186 pools (21%) currently returned by `explore/pools` cannot be swapped**, including pools holding real TVL.
## The strings are derived, not caller-supplied
`gswap-sdk/src/classes/swaps.ts:115-128` builds all five entries from the pool identity:
```ts
const poolString = `$pool$${token0StringKey}$${token1StringKey}$${fee}`;
const tokenBalance0 = `$tokenBalance$${token0StringKey}$${walletAddress}`;
const tokenBalance1 = `$tokenBalance$${token1StringKey}$${walletAddress}`;
const tokenBalance0Pool = `$tokenBalance$${token0StringKey}$${poolString}`;
const tokenBalance1Pool = `$tokenBalance$${token1StringKey}$${poolString}`;
```
`tokenBalance{0,1}Pool` embed a token key *and* `poolString`, which already contains both token keys — so the longest entry grows as `2 × len(token0) + len(token1) + 23`. A caller has no way to shorten it.
Concrete, using only public data (`GALA/MOON`, 1% fee):
```
$tokenBalance$Token$Unit$MOON$eth:321485B12C6b7aEd3888D240Df3e3E8Ee79e7393$$pool$GALA$Unit$none$none$Token$Unit$MOON$eth:321485B12C6b7aEd3888D240Df3e3E8Ee79e7393$10000
```
167 characters. Rejected.
The trigger is launchpad/bridged tokens whose composite keys carry a creator suffix. `GALA$Unit$none$none` is 19 chars; `Token$Unit$MOON$eth:321485B12C6b7aEd3888D240Df3e3E8Ee79e7393` is 60. Two ordinary keys fit comfortably; one long key does not.
## Scope
Computed over all 186 pools from `GET /explore/pools`, using each pool's own `token0CompositeKey` / `token1CompositeKey` / `fee` and a standard 45-char `eth|…` wallet address:
- **39 of 186 pools (21.0%)** produce at least one instruction over 128 chars
- Worst case: `FIM/SMOOV` at **209 chars**
- Affected pairs include `GALA/MOON` (167), `GALA/BENE` (141/140), `GALA/DKP` (164), `GALA/FGC` (139), `GALA/TSP` (139), `GALA/STU` (165), `GALA/FIM` (165), `GALA/SMOOV` (169), `GALA/GRILLZ` (170), `GALA/ALIEN` (168), `GALA/PUMPX` (168), `GUSDC/MOON`, `GUSDT/MOON`, `GUSDC/BENE`, `GUSDT/BENE`, `BENE/MOON` (195)
These pools accept liquidity and are listed as tradeable, but every swap against them 400s.
## Timeline
These pools swapped successfully as recently as **2026-08-11** (confirmed against our own execution history, 19/19 successful swaps through `GALA/MOON` and `GALA/BENE` pools). First `stringsInstructions` rejection observed **2026-09-01 02:52:48 UTC**. 401 rejections since, 100% of attempts. No change on our side in that window.
## Suspected cause — hypothesis, not a diagnosis
I can't see where the validator is declared (`stringsInstructions` doesn't appear in this repo — it's the bundler request envelope), so this may belong on the bundler or `GalaChain/dex` instead; happy to move it.
But the timing lines up with the `#811` strict-whitelist rollout:
| when (UTC) | what |
|---|---|
| 2026-08-26 10:33 | `v3.2.0` — #811 *"Reject unknown DTO properties during validation"* |
| 2026-08-26 11:55 | `GalaChain/dex` #89 *"Mark optional liquidity DTO fields callers already send"* |
| 2026-08-27 17:05 | `GalaChain/dex` v1.1.4 pins `@gala-chain/api` 3.2.0 |
| 2026-08-27 09:59 | `v3.2.1` — #819 mark optional oracle DTO filters for whitelist |
| 2026-08-31 14:30 | `v3.2.2` — #821 add back legacy balance field |
| 2026-08-31 18:39 | `v3.2.3` — #823 remove validation on balance responses |
| **2026-09-01 02:52** | **first `stringsInstructions` 128-char rejection** |
| 2026-09-02 13:39 | #825 merged — *"the third face of the #811 strict-whitelist rollout"* |
#825 describes the mechanism precisely: *"Whitelist validation only recognizes properties that carry at least one validator decorator, so an undecorated property is 'unknown' even on its own class."*
If `stringsInstructions` was previously undecorated, #811 would have made it fail whitelist validation, and the fix would be to add a decorator. My guess is that the decorator added was `@MaxLength(128, { each: true })` — the error text is that decorator's verbatim `class-validator` output, and the `each value in` prefix is exactly how `class-validator` renders `{ each: true }`. If so, 128 was a default reached for rather than a deliberate protocol limit, and this would be the fourth face of the same rollout.
## Expected behaviour
Either:
1. The cap is raised or removed for `stringsInstructions` — the entries are protocol-derived state keys whose length is a function of token composite keys the platform itself issues, so no fixed cap that is shorter than the longest constructible key is safe; or
2. If a cap is genuinely intended, GalaSwap should not list pools whose swaps can never validate — and `gswap-sdk` should surface the constraint at quote/route time rather than as a 400 at submit.
## Environment
- Bundler: `https://bundle-backend-prod1.defi.gala.com/bundle`
- Backend: `https://dex-backend-prod1.defi.gala.com`
- Client: a Go implementation constructing the identical five `stringsInstructions` entries as `gswap-sdk/src/classes/swaps.ts`
- `@gala-chain/dex` on `main` is v1.1.4, pinning `@gala-chain/api` 3.2.0
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with gswap-sdk/src/classes/swaps.ts:115-128 and reproduce the 400 using the documented long-key pool data. Then trace the bundler request envelope and the validation changes in #811, #819, #821, #823, and #825; done means affected pools no longer fail because derived stringsInstructions entries exceed the enforced limit, or the constraint is surfaced before submission.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100