Agent guide: gswap-sdk
This page is for an AI agent (or a developer working with one) that writes code against
@gala-chain/gswap-sdk, the client of the GSwap DEX. Every recipe compiles against the package's
built types (the docs build fails otherwise), and every invariant names the test or golden that
enforces it.
Which package for which job
| Job | Package |
|---|---|
| Quote, swap, manage liquidity, read pools, positions and market data on GSwap | @gala-chain/gswap-sdk (this package) |
| Signers, wallet providers, plain token transfers and balance reads | @gala-chain/gala-core-sdk (the peer dependency of this package) |
| Buy and sell a bonding-curve token before it graduates, launch a token | @gala-chain/launchpad-sdk |
| The same abilities as MCP tools | @gala-chain/gala-mcp-server |
Install with npm install @gala-chain/gswap-sdk and the core peer next to it (range ^1.0.0).
The recipes run in an ES module (top-level await). GSwap defaults to env: 'prod'; every recipe
passes env: 'stage', so keep it until you mean to trade for real.
Recipes
Create the client
getEthereumAddressFromPrivateKey accepts only a key written 0x followed by 64 hex digits (the
PrivateKeySigner also reads bare hex and base64). With another key format, pass walletAddress
yourself. A queued swap works without walletAddress; confirming a liquidity write needs it.
import { GSwap } from '@gala-chain/gswap-sdk';
import { PrivateKeySigner, getEthereumAddressFromPrivateKey } from '@gala-chain/gala-core-sdk';
const privateKey = process.env.GALACHAIN_PRIVATE_KEY!;
const gSwap = new GSwap({
env: 'stage',
signer: new PrivateKeySigner(privateKey),
walletAddress: getEthereumAddressFromPrivateKey(privateKey),
});
console.log(gSwap.pools !== undefined);
get ethereum address from private key signer wallet
Quote a swap
A quote is a read: it signs nothing and spends nothing.
import { GSwap } from '@gala-chain/gswap-sdk';
const gSwap = new GSwap({ env: 'stage' });
const quote = await gSwap.quoting.quoteExactInput('GALA|Unit|none|none', 'GUSDC|Unit|none|none', '100');
console.log(quote.feeTier, quote.amountOut);
Swap and confirm
A swap is queued. The acknowledgement means the chain accepted the request, not that it executed:
confirm() reads the applied trade or throws the chain's rejection.
amountOutMinimum is the least you accept, not the quote: the swap is applied a few seconds after
the quote, and a bound equal to the quote is rejected (CHAIN_REJECTED) whenever the price moves in
between.
import BigNumber from 'bignumber.js';
import { GSwap } from '@gala-chain/gswap-sdk';
import { PrivateKeySigner, getEthereumAddressFromPrivateKey } from '@gala-chain/gala-core-sdk';
const privateKey = process.env.GALACHAIN_PRIVATE_KEY!;
const gSwap = new GSwap({
env: 'stage',
signer: new PrivateKeySigner(privateKey),
walletAddress: getEthereumAddressFromPrivateKey(privateKey),
});
const quote = await gSwap.quoting.quoteExactInput('GALA|Unit|none|none', 'GUSDC|Unit|none|none', '100');
const submitted = await gSwap.swaps.swap('GALA|Unit|none|none', 'GUSDC|Unit|none|none', quote.feeTier, {
exactIn: '100',
amountOutMinimum: new BigNumber(quote.amountOut).multipliedBy('0.99').toFixed(), // 1% tolerance
});
console.log(submitted.mode, submitted.uniqueKey);
const applied = await submitted.confirm();
console.log(applied.transactionId, applied.result);
amount out minimum chain rejected
Add liquidity at a tick range
A range that does not contain the pool's current tick takes only one token and earns no fees, so
derive the ticks from the pool (getSlot0) instead of hard-coding them. 60 is the tick spacing
of fee tier 3000.
import { GSwap } from '@gala-chain/gswap-sdk';
import { PrivateKeySigner, getEthereumAddressFromPrivateKey } from '@gala-chain/gala-core-sdk';
const privateKey = process.env.GALACHAIN_PRIVATE_KEY!;
const gSwap = new GSwap({
env: 'stage',
signer: new PrivateKeySigner(privateKey),
walletAddress: getEthereumAddressFromPrivateKey(privateKey),
});
const slot0 = await gSwap.pools.getSlot0('GALA', 'GUSDC', 3000);
const tickLower = Math.floor((slot0.tick - 6000) / 60) * 60;
const tickUpper = Math.ceil((slot0.tick + 6000) / 60) * 60;
const added = await gSwap.positions.addLiquidityByTicks({
token0: 'GALA',
token1: 'GUSDC',
fee: 3000,
tickLower,
tickUpper,
amount: '100',
amountIsToken0: true,
});
await added.confirm();
get slot0
Recover from an unknown submission
import { GSwap } from '@gala-chain/gswap-sdk';
import { PrivateKeySigner, getEthereumAddressFromPrivateKey, isGalaError } from '@gala-chain/gala-core-sdk';
const privateKey = process.env.GALACHAIN_PRIVATE_KEY!;
const gSwap = new GSwap({
env: 'stage',
signer: new PrivateKeySigner(privateKey),
walletAddress: getEthereumAddressFromPrivateKey(privateKey),
});
try {
await gSwap.swaps.swap('GALA|Unit|none|none', 'GUSDC|Unit|none|none', 3000, { exactIn: '100', amountOutMinimum: '1' });
} catch (error) {
if (isGalaError(error) && error.code === 'SUBMISSION_OUTCOME_UNKNOWN' && error.uniqueKey !== undefined) {
// The write may have been queued: read its outcome by key, never submit a second swap.
const applied = await gSwap.swaps.confirmSwap(error.uniqueKey, { timeoutMs: 60_000 });
console.log(applied.result);
} else {
throw error;
}
}
Invariants
- The SDK signs through core's signers, and its DTO builders produce exactly the DTOs (key order
included) of the pinned golden builder cases. Enforced by
packages/gswap-sdk/tests/builder-parity.test.ts("DTO builder parity with the golden builders") and the goldenpackages/gala-core-sdk/tests/fixtures/frontend-signing-golden.json("builders.0"). - A swap is queued: the acknowledgement is not execution.
confirm()reads the applied trade by itsuniqueKeywithout resubmitting, and surfaces a chain rejection. Enforced bypackages/gswap-sdk/tests/swaps.test.ts("confirms a swap by uniqueKey from apply-results without resubmitting") andpackages/gswap-sdk/tests/swaps.test.ts("surfaces the chain rejection when confirming a rejected swap by uniqueKey"). - Slippage bounds travel as the decimal strings you gave (non-negative), and a swap with a
non-positive quantity is refused. Enforced by
packages/gswap-sdk/tests/swaps.test.ts("preserves non-negative slippage bounds as decimal strings and appends the signer result") andpackages/gswap-sdk/tests/swaps.test.ts("builds exact-output slippage and rejects non-positive quantities"). - Amounts are decimal strings: a JavaScript number is refused, naming the field. Enforced by
packages/gswap-sdk/tests/validation.test.ts("should reject a JavaScript number, naming the field"). - A user reference is
eth|<EIP-55 checksummed 40 hex digits>or aclient|alias: every Ethereum spelling is converted, a wrong mixed-case checksum is refused. Enforced bypackages/gswap-sdk/tests/validation.test.ts("sends every spelling of an Ethereum account as eth|") and packages/gswap-sdk/tests/validation.test.ts("refuses a wrong-checksum mixed-case account and anything else, naming the field"). eth|0x<hex>is not an address form and is REFUSED with core'sINVALID_ETH_USER_REF_PREFIX, naming the right spelling. Enforced bypackages/gswap-sdk/tests/validation.test.ts("refuses eth|0xas core's INVALID_ETH_USER_REF_PREFIX (not an address form), naming the right one"). - A class key carries no symbol: it is parsed from the right and a symbol is looked up in the
registry, never derived from a key. Enforced by
packages/gswap-sdk/tests/symbol-key-independence.test.ts("class keys carry no symbol"). - A write POST that gets no answer (a timeout, or a connection that failed) is
SUBMISSION_OUTCOME_UNKNOWNwith its reason and theuniqueKey; a failed read keepsTRANSPORT_ERROR. Enforced bypackages/gswap-sdk/tests/submission_outcome_unknown.test.ts("a write POST that gets no answer is SUBMISSION_OUTCOME_UNKNOWN, with the reason") andpackages/gswap-sdk/tests/submission_outcome_unknown.test.ts("reads keep TRANSPORT_ERROR: only a state-changing submit is an unknown outcome"). - Every attempt mints a fresh
uniqueKey; a custom prefix that would make the key unreadable by apply-results is refused. Enforced bypackages/gswap-sdk/tests/builder-parity.test.ts("mints a fresh uniqueKey per attempt, with the default prefix") andpackages/gswap-sdk/tests/builder-parity.test.ts("rejects a uniqueKeyPrefix that would make the key unreadable by apply-results").
unique key invalid eth user ref prefix submission outcome unknown transport error
Error codes
Branch on error.code; a GSwapSDKError also carries core's codes. The full lists are at
/errors.json (every SDK) and in the error handling page of this package.
| Code | Meaning | Fix |
|---|---|---|
SUBMISSION_OUTCOME_UNKNOWN | The write POST got no answer (timeout or lost connection). | Confirm by error.uniqueKey before any retry. |
CONFIRMATION_TIMEOUT | confirm() did not see the apply result in time. | Call confirm() again with the same uniqueKey; do not resubmit. |
CHAIN_REJECTED | The chain applied the request and rejected it. | Read error.params.errorKey and error.params.serverMessage; fix the input and submit a new swap. |
INSUFFICIENT_LIQUIDITY | The pool cannot fill the amount. | Quote a smaller amount or another fee tier. |
NO_POOL_AVAILABLE | No pool exists for the pair. | Check the pair and fee tier with gSwap.pools. |
NO_SIGNER | A write was attempted on a client built without a signer. | Pass signer to the constructor. |
NO_WALLET_ADDRESS | A liquidity write was confirmed on a client built without walletAddress. | Pass walletAddress to the constructor. |
AMOUNT_IS_JS_NUMBER | A JavaScript number was given as an amount. | Pass the amount as a decimal string. |
TICKS_NOT_ALIGNED | A tick is not divisible by the fee tier's spacing. | Align the ticks, or use addLiquidityByPrice. |
RATE_LIMITED | The backend asked the client to slow down. | Wait error.retryAfterMs and retry the read. |
UPDATE_REQUIRED | The backend refuses this client version (HTTP 426). | Upgrade the SDK. |
INVALID_ETH_USER_REF_PREFIX | An address was written eth|0x<hex>. | Write eth|<EIP-55 digits> or the bare 0x<hex>. |
swap sdk error submission outcome unknown unique key confirmation timeout chain rejected server message insufficient liquidity no pool available signer wallet address amount is js number ticks not aligned add by price rate limited retry after ms update required invalid eth user ref prefix