Skip to content

Repository files navigation

scallop

GitHub release GitHub licence

Scallop TypeScript SDK

TypeScript SDK for integrating with the Scallop lending protocol on Sui.

Current package: @scallop-io/sui-scallop-sdk v5.x. ESM package, Node >=22, peer dependencies @mysten/sui@>=2.22.0, @scallop-io/sui-kit@~2.2.0, @tanstack/query-core@>=5.95.2.

Install

pnpm add @scallop-io/sui-scallop-sdk @mysten/sui @scallop-io/sui-kit @tanstack/query-core

Public Entry Points

Root export:

import {
  Scallop,
  ScallopClient,
  ScallopBuilder,
  ScallopQuery,
  ScallopUtils,
  ScallopConstants,
  ScallopAddress,
} from '@scallop-io/sui-scallop-sdk';

Subpath exports:

import { ScallopClient } from '@scallop-io/sui-scallop-sdk/client';
import { ScallopBuilder } from '@scallop-io/sui-scallop-sdk/builder';
import { ScallopQuery } from '@scallop-io/sui-scallop-sdk/query';
import type { ScallopTxBlock } from '@scallop-io/sui-scallop-sdk/types';
import { ScallopError } from '@scallop-io/sui-scallop-sdk/errors';
import { consoleLogger } from '@scallop-io/sui-scallop-sdk/logger';

Supported subpaths:

  • @scallop-io/sui-scallop-sdk
  • @scallop-io/sui-scallop-sdk/client
  • @scallop-io/sui-scallop-sdk/query
  • @scallop-io/sui-scallop-sdk/builder
  • @scallop-io/sui-scallop-sdk/errors
  • @scallop-io/sui-scallop-sdk/logger
  • @scallop-io/sui-scallop-sdk/types

Main Models

Scallop is the convenience factory. It owns one initialized ScallopClient and exposes factory methods for the other facades.

Scallop
  -> ScallopClient        write facade; signs/sends user actions
      -> ScallopBuilder   tx-block builder; owns SuiKit + TransactionExecutor
          -> ScallopQuery read facade; delegates to repositories
              -> ScallopUtils
                  -> ScallopConstants
                      -> ScallopAddress

Important details:

  • ScallopIndexer model was removed. Query/indexer access is internal to repositories.
  • ScallopConstants composes ScallopAddress; use constants.address for the address adapter.
  • Back-compatible address forwarders remain on constants: get, set, getAddresses, getAllAddresses, switchCurrentAddresses. read and isSeeded are not forwarded — call them on constants.address.
  • Write-path signer/executor lives on builder.executor; raw SuiKit lives on builder.suiKit.

Create SDK

Mainnet example:

gRPC

import { Scallop } from '@scallop-io/sui-scallop-sdk';

const sdk = new Scallop({
  addressId: '695fcdc084f790c04eb068dc',
  network: 'mainnet',
  fullnodeUrl: 'https://fullnode.mainnet.sui.io:443',
  secretKey: process.env.SECRET_KEY,
  pythEndpoints: ['https://pyth.dourolabs.app/hermes'],
});

const client = await sdk.createScallopClient();
const query = await sdk.createScallopQuery();
const builder = await sdk.createScallopBuilder();
const utils = await sdk.createScallopUtils();
const constants = await sdk.getScallopConstants();

Read-only example:

const sdk = new Scallop({
  addressId: '695fcdc084f790c04eb068dc',
  network: 'mainnet',
  fullnodeUrl: 'https://fullnode.mainnet.sui.io:443',
  walletAddress: '0x...',
  pythEndpoints: ['https://pyth.dourolabs.app/hermes'],
});

const query = await sdk.createScallopQuery();
const pools = await query.getMarketPools();

GraphQL

readTransport: 'graphql' does not replace gRPC — Core reads and all writes still go over gRPC. fullnodeUrl (a gRPC fullnode) is optional here and defaults to the mainnet fullnode; set it to point Core reads and writes at your own node. The flag only makes balance reads and the heavy dynamic-field repos prefer native GraphQL queries. Point GraphQL at its own endpoint with graphqlUrl (defaults to mainnet), or inject a preconfigured graphqlClient:

const sdk = new Scallop({
  addressId: '695fcdc084f790c04eb068dc',
  network: 'mainnet',
  readTransport: 'graphql',
  fullnodeUrl: 'https://fullnode.mainnet.sui.io:443', // gRPC node for Core reads + writes (optional; defaults to mainnet)
  graphqlUrl: 'https://graphql.mainnet.sui.io/graphql', // GraphQL endpoint (optional; defaults to mainnet)
  walletAddress: '0x...',
  pythEndpoints: ['https://pyth.dourolabs.app/hermes'],
});

The transport options are mutually exclusive at the type level: with readTransport: 'grpc' (or omitted — grpc is the default) graphqlUrl / graphqlClient are rejected, and with readTransport: 'graphql' an injected gRPC suiClient is rejected.

Manual construction is supported. Call .init() before use:

import { ScallopQuery } from '@scallop-io/sui-scallop-sdk/query';

const query = new ScallopQuery({
  addressId: '695fcdc084f790c04eb068dc',
  network: 'mainnet',
  fullnodeUrl: 'https://fullnode.mainnet.sui.io:443',
  walletAddress: '0x...',
});

await query.init();

Constructor Options

Common required options:

  • addressId: Scallop API address config id.
  • network: Sui network, usually 'mainnet'.
  • fullnodeUrl: Sui RPC URL.
  • walletAddress: required for read-only/wallet-scoped queries when no signer is supplied.
  • secretKey or mnemonics: required for signing via ScallopClient.

Common optional options:

  • readTransport: read transport, 'grpc' (default) or 'graphql'. The Core read path and all writes always use gRPC (via fullnodeUrl, which defaults to mainnet) regardless of this setting. 'graphql' only makes the GraphQL-capable reads prefer GraphQL: balance reads via GraphQLDataSource, plus some heavy dynamic-field reads (e.g. pool addresses, xOracle, veSCA) that use single nested GraphQL queries instead of the gRPC multi-call fan-out. The transport is selected strictly — there is no automatic fallback between GraphQL and gRPC, so a failing read on the selected transport propagates its error (fail-loud) rather than silently degrading.
  • graphqlUrl / graphqlClient: Sui GraphQL endpoint / preconfigured SuiGraphQLClient. Only accepted with readTransport: 'graphql' (rejected at the type level otherwise); graphqlClient takes precedence over graphqlUrl, and the endpoint defaults to mainnet when neither is given.
  • pythEndpoints: Pyth Hermes endpoints for price-update flows. Default https://pyth.dourolabs.app/hermes.
  • pythApiKey: Pyth (Hermes) API access token. The hosted Pyth endpoint now requires a key. When set, Pyth coin prices are read directly from the Pyth API (sent as the Hermes accessToken); when omitted, prices are read from the Scallop indexer instead. Either way, getPythCoinPrice(s) falls back to on-chain feed objects if the API source fails.
  • queryClient / queryClientConfig: custom @tanstack/query-core cache. Wallet-balance reads are memoised through this cache with a 5s TTL (staleTime/gcTime). On the gRPC transport, coin-amount readers share one listBalances snapshot per node + address, so a balance read within ~5s of a supply/withdraw can return the pre-write amount. If you need read-your-write freshness right after a mutation, invalidate the getAllCoinBalances (and per-coin getCoinBalance) query-key prefixes on your queryClient, or shorten the TTL via queryClientConfig.
  • priceTimeout: cache lifetime (ms) for the full Pyth price-feed list. Default 5_000. Within this window, single/subset price reads are served from one cached full-list fetch instead of re-hitting the Pyth API; a longer value cuts API traffic at the cost of price staleness.
  • logger: SDK logger. Default is silent noopLogger; pass consoleLogger to opt into console output.
  • strictInit: when true, init() throws ScallopConfigError if required config is missing.
  • tokensPerSecond: RPC read rate limit.
  • usePythPullModel, useOnChainXOracleList, sponsoredFeeds: tx-builder oracle behavior.

Overriding the underlying clients

The SDK builds its own transport clients by default, but you can inject your own:

  • suiClient (ClientWithCoreApi from @mysten/sui): overrides the Sui RPC client used for on-chain reads. When omitted, the SDK builds a SuiGrpcClient from network + fullnodeUrl. Accepted by ScallopUtils / ScallopQuery / ScallopBuilder / ScallopClient / Scallop.
  • httpClient (AxiosInstance): overrides the HTTP client used for Scallop API / address-config fetches. When omitted, the SDK creates an Axios instance from the API url + timeout. Accepted anywhere ScallopAddress config flows (Scallop, ScallopConstants, ScallopAddress).
  • client (ScallopClient): only on the top-level Scallop constructor — reuse an already-built ScallopClient instead of constructing a new one. Unrelated to the two transport clients above.
import { Scallop } from '@scallop-io/sui-scallop-sdk';
import { SuiClient } from '@mysten/sui/client';
import axios from 'axios';

const sdk = new Scallop({
  addressId: '695fcdc084f790c04eb068dc',
  network: 'mainnet',
  fullnodeUrl: 'https://fullnode.mainnet.sui.io:443',
  walletAddress: '0x...',
  suiClient: new SuiClient({ url: 'https://fullnode.mainnet.sui.io:443' }), // custom Sui RPC client
  httpClient: axios.create({ timeout: 15_000 }), // custom HTTP client for API/address fetches
});

Note: prior to v4.3.0 these were all named client and collided into an unusable intersection type. Use suiClient / httpClient (and the top-level client) on v4.3.0+.

Query Examples

const marketPools = await query.getMarketPools();
const suiPool = await query.getMarketPool('sui');
const collaterals = await query.getMarketCollaterals();

const obligations = await query.getObligations('0xOwner');
const obligation = await query.queryObligation('0xObligationId');
const portfolio = await query.getUserPortfolio({ walletAddress: '0xOwner' });

const prices = await query.getPythCoinPrices({ coinNames: ['sui', 'usdc'] });
const allPrices = await query.getAllCoinPrices();

const stakeAccounts = await query.getAllStakeAccounts('0xOwner');
const tvl = await query.getTvl();

Read source selection is available on supported facade methods via legacy-compatible flags:

await query.getMarketPools(undefined, { source: 'rpc' }); // on-chain RPC
await query.getMarketPools(undefined, { source: 'indexer' }); // API/indexer only
await query.getMarketPools(undefined, { source: 'indexer-first' }); // API/indexer with RPC fallback
await query.getMarketPools(undefined, { indexer: true }); // same as indexer-first

Repository internals normalize those values to onchain, api, or api-first.

Client Examples

ScallopClient methods sign and execute by default. Pass false where supported to receive an unsigned transaction instead.

const result = await client.openObligation();

await client.supply('sui', 1_000_000_000);
await client.depositCollateral('sui', 1_000_000_000);
await client.withdraw('sui', 1_000_000_000);
await client.withdrawCollateral('sui', 1_000_000_000);

await client.borrow(
  'usdc',
  1_000_000,
  true,
  '0xObligationId',
  '0xObligationKey'
);
await client.repay(
  'usdc',
  1_000_000,
  true,
  '0xObligationId',
  '0xObligationKey'
);

const tx = await client.supply('sui', 1_000_000_000, false);

Other write helpers include:

  • lending: supply, withdraw, flashLoan
  • collateral: depositCollateral, withdrawCollateral
  • borrow: openObligation, borrow, repay
  • spool: createStakeAccount, stake, unstake, claim, supplyAndStake, unstakeAndWithdraw
  • veSCA / incentives: stakeObligation, unstakeObligation, claimBorrowIncentive, claimAllUnlockedSca
  • migration/test helpers: migrateAllMarketCoin, mintTestCoin

Transaction Builder

Use ScallopBuilder for custom transaction composition.

const tx = builder.createTxBlock();

await tx.supplyQuick('sui', 1_000_000_000);
await tx.depositCollateralQuick('sui', 1_000_000_000);

const result = await builder.executor.signAndSendTxn(tx);

ScallopTxBlock exposes both flat methods and module-grouped methods. References are identity-equal:

tx.supplyQuick === tx.core.supplyQuick; // true
tx.stake === tx.spool.stake; // true

Modules:

  • tx.core: lending, collateral, borrow, liquidations, flash loans
  • tx.spool: staking market coins
  • tx.vesca: veSCA lock/split/merge/redeem flows
  • tx.borrowIncentive: obligation staking and incentive claims
  • tx.referral: referral binding/revenue flows
  • tx.loyalty: loyalty reward claims
  • tx.sCoin: sCoin mint/burn

Method conventions:

  • normal methods are synchronous Move-call wrappers and return TransactionResult.
  • *Quick methods are async helpers that fetch required coins/objects/oracle updates, call normal methods, and return leftovers where needed.
  • canonical lending names are supply / supplyQuick / depositCollateral / depositCollateralQuick.
  • legacy deposit* and addCollateral* names are deprecated.

Constants And Addresses

const constants = await sdk.getScallopConstants();

const corePackage = constants.get('core.packages.protocol.id');
const allAddresses = constants.getAddresses();

const addressAdapter = constants.address;
const addressId = addressAdapter.addressId;

constants.whitelist and constants.poolAddresses are frozen snapshots after init().

Errors And Logging

SDK internals throw typed errors:

  • ScallopRpcError: Sui RPC / gRPC failures
  • ScallopIndexerError: Scallop API/indexer HTTP failures
  • ScallopParseError: invalid or unexpected payload
  • ScallopConfigError: config validation failure
  • ScallopTransactionBuildError: tx construction failure
import { ScallopError } from '@scallop-io/sui-scallop-sdk/errors';
import { consoleLogger } from '@scallop-io/sui-scallop-sdk/logger';

const sdk = new Scallop({
  addressId: '695fcdc084f790c04eb068dc',
  network: 'mainnet',
  fullnodeUrl: 'https://fullnode.mainnet.sui.io:443',
  walletAddress: '0x...',
  logger: consoleLogger,
});

try {
  await query.getMarketPools();
} catch (error) {
  if (error instanceof ScallopError) {
    // error.cause and error.context are available where provided
  }
}

Local Development

pnpm install
pnpm run build
pnpm run test:typecheck
pnpm run test:unit

Useful scripts:

pnpm run build             # production build
pnpm run build:dev         # development build
pnpm run test              # typecheck + all tests
pnpm run test:typecheck    # TypeScript checks for tests
pnpm run test:no-console   # no console.* in SDK internals
pnpm run test:unit         # network-free unit tests
pnpm run test:repo         # repository unit tests only
pnpm run test:integration  # integration tests; needs network + local env setup
pnpm run test:watch        # watch mode
pnpm run lint:fix
pnpm run format:fix
pnpm run doc               # generate typedoc output into docs/

Integration/query/full test runs require local environment variables such as SECRET_KEY. Do not commit secrets.

More Docs

License

Apache-2.0

Ask DeepWiki

About

This is the typescript sdk for the scallop lending platform on SUI network

Topics

Resources

Stars

56 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages