Agent guide: gala-core-sdk
This page is for an AI agent (or a developer working with one) that writes code against
@gala-chain/gala-core-sdk. 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 | Install |
|---|---|---|
| Sign a payload, transfer GALA or a token, read a balance, bridge, wrap, connect a wallet | @gala-chain/gala-core-sdk | npm install @gala-chain/gala-core-sdk@next |
| Quote, swap and manage liquidity on the GSwap DEX | @gala-chain/gswap-sdk (core is its peer) | npm install @gala-chain/gswap-sdk@next |
| Launchpad pools, bonding-curve buy and sell, token creation | @gala-chain/launchpad-sdk (core is its peer) | npm install @gala-chain/launchpad-sdk@next |
| Give an MCP client (Claude, Cursor) the same abilities as tools | @gala-chain/gala-mcp-server | npm install -g @gala-chain/gala-mcp-server@next |
Never install a second copy of core next to a product SDK: two copies split instanceof, the signer
identity and the error class. The product SDKs declare core as a peer dependency so one copy is shared.
Recipes
Sign a payload
import { PrivateKeySigner } from '@gala-chain/gala-core-sdk';
const signer = new PrivateKeySigner(process.env.GALACHAIN_PRIVATE_KEY!);
// The signer signs exactly the object it is handed: pass the bare DTO of the method.
const signed = await signer.signObject('TransferToken', { quantity: '1', uniqueKey: 'k-1' });
console.log(signed);
Transfer GALA
import { GALA_CHAIN_ENVIRONMENTS, GatewayWrites, PrivateKeySigner, getEthereumAddressFromPrivateKey } from '@gala-chain/gala-core-sdk';
const privateKey = process.env.GALACHAIN_PRIVATE_KEY!;
const writes = new GatewayWrites({
gatewayBaseUrl: GALA_CHAIN_ENVIRONMENTS.stage.gatewayBaseUrl,
signer: new PrivateKeySigner(privateKey),
walletAddress: getEthereumAddressFromPrivateKey(privateKey),
});
// `to` may be eth|<EIP-55 digits>, 0x<40 hex> or a client| alias; quantity is whole GALA as a string.
const receipt = await writes.transferGala({ to: 'client|5f1b0c0a1b2c3d4e5f6a7b8c', quantity: '1.5' });
console.log(receipt.transactionId, receipt.uniqueKey);
Transfer another token
import { GALA_CHAIN_ENVIRONMENTS, GatewayWrites, PrivateKeySigner, getEthereumAddressFromPrivateKey } from '@gala-chain/gala-core-sdk';
const privateKey = process.env.GALACHAIN_PRIVATE_KEY!;
const writes = new GatewayWrites({
gatewayBaseUrl: GALA_CHAIN_ENVIRONMENTS.stage.gatewayBaseUrl,
signer: new PrivateKeySigner(privateKey),
walletAddress: getEthereumAddressFromPrivateKey(privateKey),
});
// A token is named by its four-part class key; a bare symbol is never a key.
await writes.transferToken({
to: 'client|5f1b0c0a1b2c3d4e5f6a7b8c',
tokenClassKey: 'GUSDC|Unit|none|none',
quantity: '10',
});
Read a balance
import { GALA_CHAIN_ENVIRONMENTS, GatewayReads } from '@gala-chain/gala-core-sdk';
const reads = new GatewayReads({ gatewayBaseUrl: GALA_CHAIN_ENVIRONMENTS.stage.gatewayBaseUrl });
const balance = await reads.fetchTokenBalance({
owner: 'client|5f1b0c0a1b2c3d4e5f6a7b8c',
tokenClassKey: 'GALA|Unit|none|none',
});
console.log(balance?.quantity);
Recover from an unknown write outcome
A write that times out or loses its connection may have been applied. The error carries the write's
uniqueKey: check that key before sending anything again, never re-send under a fresh key.
import { GALA_CHAIN_ENVIRONMENTS, GatewayWrites, PrivateKeySigner, getEthereumAddressFromPrivateKey, isGalaError } from '@gala-chain/gala-core-sdk';
const privateKey = process.env.GALACHAIN_PRIVATE_KEY!;
const writes = new GatewayWrites({
gatewayBaseUrl: GALA_CHAIN_ENVIRONMENTS.stage.gatewayBaseUrl,
signer: new PrivateKeySigner(privateKey),
walletAddress: getEthereumAddressFromPrivateKey(privateKey),
});
try {
await writes.transferGala({ to: 'client|5f1b0c0a1b2c3d4e5f6a7b8c', quantity: '1' });
} catch (error) {
if (isGalaError(error, 'GATEWAY_WRITE_OUTCOME_UNKNOWN')) {
console.log('check this write before retrying:', error.uniqueKey, error.params.reason);
} else {
throw error;
}
}
For a swap, a token launch or a buy and sell, use the product SDK: each has its own agent guide
(/gswap-sdk/agents.md, /launchpad-sdk/agents.md). The bundler write path belongs to
@gala-chain/launchpad-sdk.
Invariants
- Sign the bare DTO. The signer is handed exactly the object of the method; the personal-sign path
never hashes an embedded
prefixfield into the message (the "phantom signer" bug), and the signature recovers to the signing wallet. Enforced bypackages/gala-core-sdk/tests/chain_dto_signing.test.ts("never hashes the embedded prefix field into the message (the phantom-signer bug)") and bypackages/gala-core-sdk/tests/signing-golden.test.ts("serializes and natively signs byte-for-byte"). - A write sends what the signer returned and adds nothing. Enforced by
packages/gala-core-sdk/tests/writes.test.ts("hands the signer the bare DTO for the write method and sends exactly what it returned"). - A user reference is
eth|<EIP-55 checksummed 40 hex digits>or aclient|alias. Every Ethereum spelling is converted to the checksummedeth|form; a bare0x<40 hex>is converted, a wrong mixed-case checksum is refused. Enforced bypackages/gala-core-sdk/tests/writes.test.ts("sends every Ethereum account spelling as eth|, a client| alias as written"), packages/gala-core-sdk/tests/writes.test.ts("accepts one-case digits and refuses a mixed case with a wrong checksum") and the goldenpackages/gala-core-sdk/tests/fixtures/writes-signing-golden.json("vectors.transferGala 0x recipient is sent as checksummed eth|"). eth|0x<hex>is not an address form. It is REFUSED withINVALID_ETH_USER_REF_PREFIXbefore anything is signed or sent, for a write recipient, the writer identity, a lock authority and a read owner. Enforced bypackages/gala-core-sdk/tests/writes.test.ts("refuses eth|0xfor each, before anything is signed or sent") and packages/gala-core-sdk/tests/reads.test.ts("refuses eth|0xowners as INVALID_ETH_USER_REF_PREFIX before anything is sent"). - Amounts are whole tokens as a decimal string, signed exactly as written. A JS number, an exponent,
a sign, hex or a zero quantity is refused with
INVALID_QUANTITY. Enforced bypackages/gala-core-sdk/tests/writes.test.ts("signs quantities exactly as written and refuses any other spelling"). - A token is named by a four-part class key (
collection|category|type|additionalKey) or bytokenNamethrough a resolver; a bare symbol is never a class key and none is derived from it. Enforced bypackages/gala-core-sdk/tests/writes.test.ts("names a token by class key (pipe with exactly four parts, $ from the right, object) or by name"). - A minted
uniqueKeyis at most 64 characters with no|; a key you supply is 1 to 128 characters ofA-Za-z0-9._:-and is refused before signing otherwise. Enforced bypackages/gala-core-sdk/tests/writes.test.ts("mints: at most 64 characters, no "|", a fresh key per write") and packages/gala-core-sdk/tests/writes.test.ts("keeps a caller key and refuses an invalid one before signing"). - A write whose outcome is unknown is never retried under a fresh key: the error carries the
uniqueKeyto check. Enforced bypackages/gala-core-sdk/tests/writes.test.ts("a timeout is GATEWAY_WRITE_OUTCOME_UNKNOWN (timeout) carrying the uniqueKey, never a retryable REQUEST_TIMEOUT"). - The signing helpers work on a private copy of the key bytes and zero it when the signing action
finishes, on success and when it throws; a
PrivateKeySigneritself holds its key for its lifetime, so keep it out of logs and tool arguments. Enforced bypackages/gala-core-sdk/tests/chain_dto_signing.test.ts("zeroes its private copy of the key, on success and when the action throws").
Error codes
Branch on error.code (stable), never on error.message. isGalaError(error, 'CODE') narrows the
error and its params.
| Code | Meaning | Fix |
|---|---|---|
INVALID_ETH_USER_REF_PREFIX | An address was written eth|0x<hex>. | Write eth|<EIP-55 digits> or the bare 0x<hex>; error.params.expected is the correct spelling. |
INVALID_WALLET_ADDRESS | A wallet address, recipient or owner is not an accepted form; params.field names it. | Use eth|<EIP-55>, a checksummed 0x<hex> or a client| alias. |
INVALID_QUANTITY | A write quantity is not a plain positive decimal string. | Pass whole tokens as a string such as '1.5'. |
INVALID_TOKEN_REFERENCE | A token is named badly (missing, ambiguous, a bare symbol). | Pass the four-part class key, or tokenName with a resolver. |
INVALID_UNIQUE_KEY | A uniqueKey outside 1 to 128 characters of A-Za-z0-9._:-. | Omit it to mint one, or mint with createUniqueKey. |
SIGNING_FAILED | The signer threw before anything was sent (a locked wallet, a declined prompt). | Nothing was sent; retry once the wallet is unlocked or approved. |
INVALID_PRIVATE_KEY | A key is empty, the wrong length, mis-encoded or off the curve; params.reason says which. | Supply 32 bytes as 64 hex digits. |
GATEWAY_WRITE_OUTCOME_UNKNOWN | The write may have been applied (timeout or lost connection). | Check error.uniqueKey on chain before any retry. |
GATEWAY_WRITE_REJECTED | GalaChain refused the write (Status other than 1). | Read error.body for the chain's reason; fix the input. |
REQUEST_TIMEOUT | A read exceeded its timeout. | Retry the read; reads are safe to repeat. |
TRANSPORT_ERROR | A read failed before any answer (offline, DNS, CORS). | Retry the read after connectivity returns. |
CSPRNG_UNAVAILABLE | No crypto.getRandomValues (React Native without the polyfill). | Import react-native-get-random-values first. |