npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gaian-network/sdk

v2.0.0

Published

TypeScript SDK for the Gaian Network v2 API — cross-border crypto-to-fiat payments

Readme

@gaian-network/sdk

TypeScript SDK for the Gaian Network v2 API — a single signed gateway for user onboarding, KYC, wallets, and crypto-to-fiat payments across Vietnam, Thailand, and other supported markets.

Installation

npm install @gaian-network/sdk

Quick start

import { GaianClient } from '@gaian-network/sdk';

const client = new GaianClient({
  apiKey: process.env.GAIAN_API_KEY!,
  secretKey: process.env.GAIAN_API_SECRET!,
});

const resp = await client.createUser();
const userId = resp.data!.userId;

Get your API key and secret from the Client Admin Portal. Both are shown only once at creation — store them securely, server-side only.

Every method hangs directly off the client — there are no per-resource sub-clients (client.createUser, client.quote, client.placeOrder, ...), not client.users.register(...).


Environments

| Environment | Base URL | |---|---| | Sandbox (default) | https://sandbox.gaian.network | | Production | https://api.gaian.network |

const client = new GaianClient({
  apiKey: process.env.GAIAN_API_KEY!,
  secretKey: process.env.GAIAN_API_SECRET!,
  environment: 'production', // defaults to 'sandbox'
});

Authentication

Every request is HMAC-signed — there's no static API-key header. Each key has a public key (apiKey) and a secret (secretKey); the client signs every outgoing request automatically:

message   = apiKey + method + path + query + canonicalBody + timestamp
signature = base64url( HMAC_SHA256(secretKey, message) )

where canonicalBody is the JSON body with top-level keys sorted alphabetically and compact-serialized (empty string for GET/no body). This is implemented in signer.ts — you never need to call it directly, GaianClient handles signing for you.

Never hardcode the API key/secret or expose them in client-side code. Signatures must be generated on your backend.


Payment flows

Standard flow (on-chain signing required)

quote (or parseQr first) → placeOrder → sign & broadcast the returned transaction
→ verifyOrder → getOrderStatus (poll until terminal)

Prefunded flow (no on-chain signing, settles from tenant balance)

quotePrefund → placePrefundedOrder → getOrderStatus (poll)

Direct bank-transfer flow (production only, no QR code)

listBanks → verifyAccount (recommended) → quoteDirect (or quoteDirectPrefund)
→ placeOrder (or placePrefundedOrder) → verifyOrder (on-chain variant only) → getOrderStatus

A quoteDirect/quote quote must be consumed by placeOrder; a quoteDirectPrefund/quotePrefund quote must be consumed by placePrefundedOrder — cross-use is rejected by the API. Quotes are single-use with a short TTL (~120s).


Usage

Users

Create a user

const resp = await client.createUser();
const userId = resp.data!.userId;

No wallet or KYC data is attached at creation time — link a wallet and submit KYC afterward using the returned userId.

Get a user by ID or wallet

const resp = await client.getUserById({ userId });

const resp2 = await client.getUserByWallet({ walletAddress: '0xWalletAddress' });

Check market access

const resp = await client.getMarkets({ userId });
if (resp.data!.markets['VN']?.status !== 'APPROVED') {
  console.log('Vietnam market not available');
}

| MarketStatus | Description | |---|---| | MarketApproved | Approved for payments in this market | | MarketPending | KYC/verification in progress | | MarketNotStarted | Nothing submitted yet for this market | | MarketRejected | Rejected — check rejectReason (populated for PH/BR only) |

List / get orders

const resp = await client.listUserOrders({
  userId,
  page: 1,
  pageSize: 20,
  status: 'completed', // optional filter
});
for (const o of resp.data) {
  console.log(o.orderId, o.status, o.fiatAmount, o.fiatCurrency);
}
console.log('total:', resp.pagination.totalCount);

const order = await client.getUserOrder({ userId, orderId });

Wallets

const wallet = await client.createWallet({
  userId,
  address: '0xWalletAddress',
  chain: 'base',
});
// the first wallet added for a user becomes primary automatically

const resp = await client.listWallets({ userId });
for (const w of resp.data ?? []) {
  console.log(w.address, w.chain, w.isPrimary);
}

KYC

Hosted KYC link

The returned URL is time-limited. Generate it on demand — do not cache.

const resp = await client.generateKycLink({
  userId,
  callbackUrl: 'https://myapp.com/kyc/callback', // optional
});
// Redirect the user to resp.data!.url

Submit KYC information directly

const resp = await client.submitKyc({
  userId,
  firstName: 'Nguyen',
  lastName: 'Van A',
  email: '[email protected]',
  gender: 'male',
  dateOfBirth: '1990-01-15',
  nationality: 'VN',
  nationalId: '012345678901',
  type: 'ID_CARD',
  expiryDate: '2025-01-01',
  addressLine1: '123 Le Loi',
  city: 'Ho Chi Minh City',
  countryOfResidence: 'VN',
  occupation: 'OCC9',
  phoneCountryCode: '+84',
  phoneNumber: '912345678',
  frontIdImage: '<base64>',
  backIdImage: '<base64>',
  holdIdImage: '<base64>',
});
console.log(resp.data!.kycStatus);

frontIdImage/backIdImage/holdIdImage are optional — omit them entirely if the flow doesn't require ID images at submission time.

Update KYC information

All fields are optional — only the fields you set are sent/updated.

const resp = await client.updateKyc({ userId, email: '[email protected]' });

Organization

const resp = await client.getOrganizationBalance();
console.log(`${resp.data!.availableBalance} ${resp.data!.currency} available`);

Restricted to tenants on the prefund billing model.


Payments

Parse a QR code

const resp = await client.parseQr({ qrString });
// country is an optional ISO-3166-1 alpha-2 hint:
const resp2 = await client.parseQr({ qrString, country: 'VN' });

console.log(resp.data!.bankBin, resp.data!.accountNumber);

Create a quote

const resp = await client.quote({
  qrString,
  amount: 500000, // fiat amount
  chainId: ChainSolana,
  settlementCurrency: 'USDC',
  userId,
});
const quoteId = resp.data!.quoteId;

| ChainID | Chain | |---|---| | ChainEthereum | Ethereum (1) | | ChainOptimism | Optimism (10) | | ChainBSC | BSC (56) | | ChainPolygon | Polygon (137) | | ChainBase | Base (8453) | | ChainArbitrum | Arbitrum (42161) | | ChainSolana | Solana (101) |

Prefunded quote (no on-chain step, no chainId):

const resp = await client.quotePrefund({
  qrString,
  amount: 500000,
  settlementCurrency: 'USDC',
  userId,
});

Direct bank-transfer quote (no QR code, production only — see listBanks/verifyAccount below):

const resp = await client.quoteDirect({
  accountNumber: '0123456789',
  code: bankCode, // from listBanks
  amount: 500000,
  country: 'VN',
  chainId: ChainSolana,
  settlementCurrency: 'USDC',
  userId, // exactly one of userId/walletAddress/email
});

// or, prefunded:
const resp2 = await client.quoteDirectPrefund({ /* same, minus chainId */ });

Place an order

const resp = await client.placeOrder({ quoteId });

// resp.data!.encodedTransaction is what you sign and broadcast on-chain.
const txHash = await broadcastTransaction(resp.data!.encodedTransaction);

Prefunded order (no on-chain step — go straight to polling getOrderStatus):

const resp = await client.placePrefundedOrder({ quoteId });

Verify the on-chain transaction

const resp = await client.verifyOrder({ orderId, transactionHash: txHash });

The API returns HTTP 200 even when verification did not succeed. A resolved promise does not mean the payment is confirmed — always branch on resp.data!.status:

switch (resp.data!.status) {
  case VerifyVerified:
    // confirmed — poll getOrderStatus for settlement
    break;
  case VerifyPending:
    // transaction hasn't propagated on-chain yet — resubmit the same transactionHash shortly
    break;
  case VerifyFailed:
    console.error('verification failed:', resp.data!.message);
    break;
}

Poll order status

for (;;) {
  const resp = await client.getOrderStatus({ orderId });
  if (isTerminal(resp.data!.status)) {
    console.log('final status:', resp.data!.statusLabel);
    break;
  }
  await new Promise((r) => setTimeout(r, 3000));
}

| OrderStatus | Label | Terminal? | |---|---|---| | OrderPending (0) | pending | no | | OrderAwaitingDeposit (1) | awaiting_deposit | no | | OrderPaymentReceived (2) | payment_received | no | | OrderQueued (3) | queued | no | | OrderProcessing (4) | processing | no | | OrderCompleted (10) | completed | yes | | OrderFailed (20) | failed | yes | | OrderCancelled (21) | cancelled | yes | | OrderExpired (22) | expired | yes | | OrderUnknown (99) | unknown | no |

VerifyStatus (returned by verifyOrder) is a separate type from OrderStatus — their integer values overlap (both have a 1) but mean different things. Don't compare across the two.


Banks (direct bank transfer)

Production only — not available in sandbox.

const resp = await client.listBanks({ country: 'VN' });
for (const b of resp.data!.banks) {
  console.log(b.code, b.name); // b.code is what quoteDirect/verifyAccount expect
}

const verify = await client.verifyAccount({
  accountNumber: '0123456789',
  code: bankCode,
  country: 'VN',
});

A failed verification (bad account number) is not an error — it returns HTTP 200 with valid: false and accountName: null. Check resp.data!.valid, not just whether the call threw.


Response envelopes

Every method returns one of two generic wrappers (response.ts):

  • UserResponse<T> — {data, requestId} — used by User/KYC/Wallet/Organization endpoints.
  • PaymentResponse<T> — {success, requestId, data} — used by Payment endpoints (parseQr, quote*, placeOrder*, verifyOrder, getOrderStatus, listBanks, verifyAccount).

listUserOrders is the one exception — its response has data/pagination/requestId as siblings at the top level, so it returns ListUserOrdersResponse directly instead of a wrapped envelope.

Check each feature file's doc comment if you're unsure which shape applies.


Error handling

All errors thrown are instances of GaianError:

import { GaianError } from '@gaian-network/sdk';

try {
  await client.getUserById({ userId: 'does-not-exist' });
} catch (err) {
  if (err instanceof GaianError) {
    console.log(err.code);       // 'NOT_FOUND' | 'UNAUTHORIZED' | 'BAD_REQUEST' | ...
    console.log(err.statusCode); // 404
    console.log(err.message);    // API error message
    console.log(err.details);    // raw response body
  }
}

v2 has two distinct error body shapes depending on which backing service handled the request — the SDK handles both transparently:

  • User/KYC/Wallet/Organization endpoints: flat {message}.
  • Payment endpoints: {success:false, error:{code,message,details}}.

| code | HTTP status | When | |--------|------------|------| | UNAUTHORIZED | 401 | Missing or invalid signature/API key | | FORBIDDEN | 403 | Valid key, insufficient permissions | | NOT_FOUND | 404 | Resource does not exist | | BAD_REQUEST | 400 | Invalid request parameters | | CONFLICT | 409 | Duplicate resource or conflicting state, e.g. an already-consumed quote | | SERVER_ERROR | 5xx | Gaian server error — retry with backoff | | NETWORK_ERROR | — | Network timeout or connection failure |


Configuration

const client = new GaianClient({
  apiKey: process.env.GAIAN_API_KEY!,
  secretKey: process.env.GAIAN_API_SECRET!,
  environment: 'sandbox',    // or 'production'
  baseUrl: 'http://localhost:4000', // override, e.g. for local proxies or tests
  fetch: myFetchImpl,        // custom fetch — how you inject timeouts/proxies/mocks
  logger: myLogger,          // optional: implement { info, error, debug }
  debug: true,               // log request/response via the logger
});

Requirements

  • Node.js ≥ 18 (native fetch, crypto.randomUUID, crypto.createHmac)
  • TypeScript ≥ 5.0 (for exactOptionalPropertyTypes)

License

MIT