GalaChain / GalaChain/sdk

Swap bundles 400 on `stringsInstructions` 128-char limit, making 39 of 186 GalaSwap pools unswappable

Open
#828 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.