Skip to content

Scallop Python SDK

PyPI version CI Python License

A Python SDK for interacting with the Scallop Lending Protocol on Sui blockchain.

English | 繁體中文

Features

Core Lending Operations

  • Supply/Withdraw: supply, withdraw (supply assets to earn interest)
  • Borrow/Repay: borrow, repay
  • Collateral Management: deposit_collateral, withdraw_collateral
  • Liquidation: liquidate
  • Flash Loans: borrow_flash_loan, repay_flash_loan

Quick Methods

  • Auto Coin Selection: supply_quick, withdraw_quick, repay_quick
  • Auto Price Update: borrow_quick, liquidate_quick
  • Oracle Update: update_asset_prices_quick
  • veSCA Lifecycle Helpers: lock_sca_quick, extend_lock_period_quick, extend_lock_amount_quick, renew_expired_vesca_quick, redeem_sca_quick, split_vesca_quick, merge_vesca_quick

Advanced Features

  • Staking Pool (Spool): Stake market coins for rewards
  • sCoin: Mint/burn sCoin
  • veSCA: Lock SCA for veSCA
  • Borrow Incentive: Stake obligations for rewards
  • Referral: Referral system
  • Loyalty Program: Loyalty rewards

Infrastructure

  • Cryptography: Ed25519 signing, address derivation, Bech32 key import (suiprivkey1...)
  • BCS Serialization: Full transaction serialization support (mainnet verified)
  • Oracle Integration: Pyth + X Oracle

Installation

pip install sui-scallop-sdk

# Or install from source
git clone https://github.com/scallop-io/sui-scallop-sdk-python
cd sui-scallop-sdk-python
pip install -e .

Quick Start

Query Obligation

from sui_scallop_sdk import ScallopClient

# Initialize client
client = ScallopClient(network="mainnet")

# Query obligation
query = client.create_query()
obligation = query.get_obligation("0x...")

print(f"Risk Level: {obligation.risk_level}")
print(f"Is Liquidatable: {obligation.is_liquidatable}")
print(f"Total Debt USD: ${obligation.total_debt_usd}")
print(f"Total Collateral USD: ${obligation.total_collateral_usd}")

Query Market And TVL

from sui_scallop_sdk import ScallopClient

client = ScallopClient(network="mainnet")
query = client.create_query()

pool = query.get_market_pool("sui")
collateral = query.get_market_collateral("sui")
pool_address = query.get_pool_address("sui")
tvl = query.get_tvl()
prices = query.get_coin_prices(["sui", "sca", "usdc"])

print(pool.supply_amount, pool.borrow_amount, pool.supply_apy)
print(collateral.deposit_amount, collateral.collateral_factor)
print(pool_address.lending_pool_address, pool_address.flash_loan_fee_object)
print(tvl.total_value)
print(prices)

Query Portfolio And Lendings

from sui_scallop_sdk import ScallopClient

client = ScallopClient.from_env()
query = client.create_query()

portfolio = query.get_user_portfolio(client.wallet_address)
lendings = query.get_lendings(client.wallet_address)

print(portfolio.total_supply_value, portfolio.total_borrow_value, portfolio.net_apy)
print(portfolio.obligations)
print(lendings)

Query Spool, sCoin, veSCA, And Loyalty

from sui_scallop_sdk import ScallopClient

client = ScallopClient.from_env()
query = client.create_query()

spool = query.get_spool("ssui")
scoin_supply = query.get_scoin_total_supply("sui")
scoin_amounts = query.get_scoin_amounts(["ssui", "susdc"], client.wallet_address)
vescas = query.get_vescas(client.wallet_address, exclude_empty=True)

print(spool.reward_apr if spool else None)
print(scoin_supply, scoin_amounts)

if vescas:
    loyalty = query.get_loyalty_program_info(vescas[0].key_id)
    vesca_loyalty = query.get_vesca_loyalty_program_info(vescas[0].key_id)
    print(vescas[0].locked_sca_amount, vescas[0].current_vesca_balance)
    print(loyalty.pending_reward, vesca_loyalty.pending_vesca_reward)

For a runnable walkthrough, see examples/query_example.py.

Supply (Earn Interest)

from sui_scallop_sdk import ScallopClient

# Initialize with wallet credentials
# Supports: suiprivkey1... (bech32), base64, hex, or raw bytes
client = ScallopClient(
    secret_key="suiprivkey1...",
    network="mainnet",
)

# Create transaction
builder = client.create_builder()
tx = builder.create_tx_block()

# Supply 1 SUI to lending pool
market_coin_idx = tx.supply(coin_input_idx=0, coin_name="sui")
tx.transfer_objects([market_coin_idx], client.wallet_address)

# Execute
result = builder.sign_and_send_tx_block(tx)

if result.success:
    print(f"Success! TX: {result.digest}")
else:
    print(f"Failed: {result.error}")

Quick Supply (Auto Coin Selection)

tx = builder.create_tx_block()

# Use supply_quick for automatic coin selection
market_coin_idx = tx.supply_quick(
    amount=1_000_000_000,  # 1 SUI (9 decimals)
    coin_name="sui",
    sender=client.wallet_address
)
tx.transfer_objects([market_coin_idx], client.wallet_address)

result = builder.sign_and_send_tx_block(tx)

Initialize From Environment

export NETWORK=mainnet
export PRIVATE_KEY="suiprivkey1..."
# Optional when you want to override the derived address
export WALLET_ADDRESS="0x..."
from sui_scallop_sdk import ScallopClient

client = ScallopClient.from_env()

PRIVATE_KEY is supported for config-driven initialization. MNEMONIC is not yet supported.

Borrow

tx = builder.create_tx_block()

# Borrow with automatic oracle price update
borrowed_coin_idx = tx.borrow_quick(
    amount=100_000_000,  # 100 USDC (6 decimals)
    coin_name="usdc",
    obligation_id="0x...",
    obligation_key="0x...",
)
tx.transfer_objects([borrowed_coin_idx], client.wallet_address)

result = builder.sign_and_send_tx_block(tx)

Repay

tx = builder.create_tx_block()

# Auto coin selection for repay
tx.repay_quick(
    amount=50_000_000,  # Repay 50 USDC
    coin_name="usdc",
    obligation_id="0x...",
    sender=client.wallet_address,
)

result = builder.sign_and_send_tx_block(tx)

Flash Loan

tx = builder.create_tx_block()

# Borrow flash loan (returns TxResult objects for PTB tuple outputs)
coin_result, receipt_result = tx.borrow_flash_loan(
    amount=1_000_000_000,  # 1 SUI
    coin_name="sui"
)

# ... perform arbitrage or other operations ...

# Repay (must be in same transaction, includes fee)
tx.repay_flash_loan(coin_result, receipt_result, "sui")

result = builder.sign_and_send_tx_block(tx)

Liquidation

tx = builder.create_tx_block()

# Auto coin selection liquidation (auto updates oracle prices)
remaining_debt_idx, collateral_idx = tx.liquidate_quick(
    obligation_id="0x...",
    repay_amount=100_000_000,  # 100 USDC
    debt_coin_name="usdc",
    collateral_coin_name="sui",
    sender=client.wallet_address,
)

# Transfer received collateral
tx.transfer_objects(
    [{"kind": "NestedResult", "index": collateral_idx, "resultIndex": 1}],
    client.wallet_address,
)

result = builder.sign_and_send_tx_block(tx)

Advanced Features

Spool Staking

tx = builder.create_tx_block()

# Create stake account
stake_account_idx = tx.create_stake_account(stake_pool_id, "sui")

# Stake market coin
tx.stake_spool(
    stake_account_id="0x...",
    stake_pool_id="0x...",
    coin_input_idx=market_coin_idx,
    coin_name="sui"
)

# Claim rewards
reward_idx = tx.claim_spool_rewards(
    stake_account_id="0x...",
    stake_pool_id="0x...",
    reward_pool_id="0x...",
    coin_name="sca"
)
tx.transfer_objects([reward_idx], client.wallet_address)

result = builder.sign_and_send_tx_block(tx)

veSCA Lock

tx = builder.create_tx_block()

# Lock SCA to get veSCA key (365 days)
vesca_key_idx = tx.lock_sca(
    sca_coin_idx=sca_coin_input,
    lock_period_days=365
)
tx.transfer_objects([vesca_key_idx], client.wallet_address)

# Extend lock period
tx.extend_lock_period(
    vesca_key_id="0x...",
    new_unlock_at=new_unlock_timestamp  # Unix epoch in seconds
)

# Add more SCA to existing lock
tx.extend_lock_amount(
    vesca_key_id="0x...",
    sca_coin_idx=additional_sca_input
)

# Redeem SCA (after lock expires)
sca_idx = tx.redeem_sca(vesca_key_id="0x...")
tx.transfer_objects([sca_idx], client.wallet_address)

result = builder.sign_and_send_tx_block(tx)

veSCA Quick Methods

tx = builder.create_tx_block()

# Initial lock with automatic SCA coin selection
new_vesca_idx = tx.lock_sca_quick(
    amount=10_000_000_000,      # 10 SCA in raw units
    lock_period_days=365,
    sender=client.wallet_address,
)
tx.transfer_objects([new_vesca_idx], client.wallet_address)

# Extend only the lock duration of the highest-balance veSCA
tx.extend_lock_period_quick(
    lock_period_days=30,
    sender=client.wallet_address,
)

# Top up an existing veSCA with automatic coin selection
tx.extend_lock_amount_quick(
    sca_amount=1_000_000_000,   # 1 SCA in raw units
    sender=client.wallet_address,
    vesca_key_id="0x...",       # optional, defaults to the sender's highest-balance veSCA
)

# Renew an expired veSCA
tx.renew_expired_vesca_quick(
    sca_amount=10_000_000_000,
    lock_period_days=180,
    sender=client.wallet_address,
    vesca_key_id="0x...",
)

# Redeem unlocked SCA and auto-transfer it back to the sender
tx.redeem_sca_quick(
    sender=client.wallet_address,
    vesca_key_id="0x...",
)

# Split a veSCA key and auto-transfer the new key NFT
tx.split_vesca_quick(
    split_amount=5_000_000_000,
    vesca_key_id="0x...",
    sender=client.wallet_address,
)

# Merge two veSCA keys, aligning unlock times automatically when needed
tx.merge_vesca_quick(
    target_vesca_key_id="0x...",
    source_vesca_key_id="0x...",
)

result = builder.sign_and_send_tx_block(tx)

Notes:

  • veSCA quick methods stay builder-first in Python, so methods that need coin selection or auto-transfer still require an explicit sender
  • vesca_key_id is optional for lock_sca_quick, extend_lock_period_quick, extend_lock_amount_quick, renew_expired_vesca_quick, and redeem_sca_quick
  • when vesca_key_id is omitted, the SDK uses the sender's highest-balance veSCA position
  • amounts are raw on-chain units, so 1 SCA = 1_000_000_000

sCoin Operations

tx = builder.create_tx_block()

# Mint sCoin (from Market Coin)
scoin_idx = tx.mint_scoin(market_coin_idx=market_coin, coin_name="sui")
tx.transfer_objects([scoin_idx], client.wallet_address)

# Burn sCoin (get Market Coin back)
market_coin_idx = tx.burn_scoin(scoin_idx=scoin_input, coin_name="sui")
tx.transfer_objects([market_coin_idx], client.wallet_address)

result = builder.sign_and_send_tx_block(tx)

Borrow Incentive

tx = builder.create_tx_block()

# Stake obligation
tx.stake_obligation(
    obligation_id="0x...",
    obligation_key="0x..."
)

# Claim rewards
reward_idx = tx.claim_borrow_incentive(
    obligation_id="0x...",
    obligation_key="0x...",
    coin_name="sca"
)
tx.transfer_objects([reward_idx], client.wallet_address)

result = builder.sign_and_send_tx_block(tx)

Referral Program

tx = builder.create_tx_block()

# Bind to a referrer (using referrer's veSCA key ID)
tx.bind_referral(referrer_vesca_key_id="0x...")

# Claim referral rewards
reward_idx = tx.claim_referral_revenue(
    vesca_key_id="0x...",  # Your veSCA key ID
    coin_name="sui"
)
tx.transfer_objects([reward_idx], client.wallet_address)

result = builder.sign_and_send_tx_block(tx)

Loyalty Program

tx = builder.create_tx_block()

# Claim loyalty rewards
reward_idx = tx.claim_loyalty_revenue(vesca_key_id="0x...")
tx.transfer_objects([reward_idx], client.wallet_address)

result = builder.sign_and_send_tx_block(tx)

Configuration

Environment Variables

Create a .env file:

PRIVATE_KEY=suiprivkey1...  # or base64/hex format
NETWORK=mainnet
RPC_URL=https://fullnode.mainnet.sui.io:443

Then use from_env():

client = ScallopClient.from_env()

Supported Coins

Coin SDK Name Decimals
SUI sui 9
USDC (Native) usdc 6
wUSDC (Wormhole) wusdc 6
wUSDT (Wormhole) wusdt 6
wETH (Wormhole) weth 8
wBTC (Wormhole) wbtc 8
wSOL (Wormhole) wsol 8
wAPT (Wormhole) wapt 8
CETUS cetus 9
SCA sca 9
DEEP deep 6
FUD fud 5
BLUB blub 2
NS ns 6
afSUI afsui 9
haSUI hasui 9
vSUI vsui 9
HAEDAL haedal 9
WAL wal 9
haWAL hawal 9
wWAL wwal 9
USDY usdy 6
FDUSD fdusd 6
mUSD musd 9
sbETH (Sui Bridge) sbeth 8
sbUSDT (Sui Bridge) sbusdt 6
sbwBTC (Sui Bridge) sbwbtc 8
xBTC xbtc 8
LOFI lofi 9
suiUSDe suiusde 6
zBTC zwbtc 8

Note: The supported coins list is fetched from Scallop API. Some coins may only support specific operations (lending, collateral, or borrowing). Check Scallop documentation for the latest availability.

API Reference

ScallopClient

Main entry point for SDK.

Method Description
create_query() Create query interface
create_builder() Create transaction builder

ScallopQuery

Method Description
get_obligation(obligation_id) Query one obligation
list_user_obligations(address) List obligation IDs controlled by a wallet
list_user_obligation_keys(address) List obligations with the owned key each one needs
get_coin_price(coin_name) Query one oracle-backed coin price
get_coin_prices(coin_names) Query multiple oracle-backed coin prices
get_market_pool(coin_name) Query one lending pool
get_market_pools(coin_names=None) Query lending pools
get_market_collateral(coin_name) Query one collateral pool
get_market_collaterals(coin_names=None) Query collateral pools
get_pool_address(coin_name) Query derived on-chain object addresses for one pool
get_tvl(coin_names=None) Query aggregate TVL metrics
get_lendings(address, coin_names=None) Query active lending positions
get_user_portfolio(address) Query aggregated wallet portfolio
get_spool(market_coin_name) Query one spool staking pool
get_spools(market_coin_names=None) Query spool staking pools
get_stake_accounts(market_coin_name, address) Query owned stake accounts for one spool
get_all_stake_accounts(address) Query all owned stake accounts
get_scoin_total_supply(coin_name) Query total sCoin supply
get_scoin_amounts(coin_names=None, address=None) Query owned sCoin balances
get_scoin_swap_rate(from_scoin, to_scoin) Query swap rate between sCoins
get_vesca(vesca_key) Query one veSCA position
get_vescas(address, exclude_empty=False) Query wallet veSCA positions
get_vesca_treasury_info() Query aggregate veSCA treasury stats
get_loyalty_program_info(vesca_key=None) Query loyalty reward pool and pending reward
get_vesca_loyalty_program_info(vesca_key=None) Query veSCA loyalty pool and pending reward
get_asset_oracles(coin_names=None) Query primary and secondary oracle routing
get_price_update_policies() Query xOracle update policy rule objects

ScallopTxBlock - Core Methods

Method Description
supply(coin_input_idx, coin_name) Supply asset to lending pool
withdraw(market_coin_idx, coin_name) Withdraw from lending pool
borrow(obligation_id, obligation_key, amount, coin_name) Borrow from pool
repay(obligation_id, coin_input_idx, coin_name) Repay borrowed amount
deposit_collateral(obligation_id, coin_input_idx, coin_name) Deposit collateral
withdraw_collateral(obligation_id, obligation_key, amount, coin_name) Withdraw collateral
liquidate(obligation_id, repay_coin_idx, debt_coin_name, collateral_coin_name) Liquidate position → (TxResult, TxResult)
borrow_flash_loan(amount, coin_name) Borrow flash loan → (TxResult, TxResult)
repay_flash_loan(coin_idx, receipt_idx, coin_name) Repay flash loan (accepts int or TxResult)

ScallopTxBlock - Quick Methods

Method Description
supply_quick(amount, coin_name, sender) Auto coin selection supply
withdraw_quick(amount, coin_name, sender) Auto coin selection withdraw; amount is in market coin / sCoin units, not underlying
borrow_quick(amount, coin_name, obligation_id, obligation_key) Auto price update borrow
repay_quick(amount, coin_name, obligation_id, sender) Auto coin selection repay
liquidate_quick(...) Auto coin selection liquidation
lock_sca_quick(amount=None, lock_period_days=None, sender=None, vesca_key_id=None) Smart veSCA lifecycle helper for initial lock, extend, top-up, or expired renewal
extend_lock_period_quick(lock_period_days, vesca_key_id=None, sender=None) Extend an active veSCA lock using sender lookup when needed
extend_lock_amount_quick(sca_amount, sender, vesca_key_id=None) Auto-select SCA and add it to an active veSCA
renew_expired_vesca_quick(sca_amount, lock_period_days, sender, vesca_key_id=None) Renew an expired veSCA with automatic SCA coin selection
redeem_sca_quick(vesca_key_id=None, sender=None, transfer_sca=True) Redeem unlocked SCA and optionally auto-transfer it
split_vesca_quick(split_amount, vesca_key_id, sender=None, transfer_vesca_key=True) Split a veSCA key with optional auto-transfer of the new key
merge_vesca_quick(target_vesca_key_id, source_vesca_key_id) Merge veSCA keys with subscription safety and unlock alignment

ObligationInfo

Property Type Description
obligation_id str Object ID
debts List[DebtInfo] Debt positions
collaterals List[CollateralInfo] Collateral positions
risk_level Decimal Risk level (>= 1.0 = liquidatable)
is_liquidatable bool Liquidation eligibility
is_bad_debt bool Has debt but no collateral
total_debt_usd Decimal Total debt in USD
total_collateral_usd Decimal Total collateral in USD

Development

# Install dev dependencies
pip install -e ".[dev]"

# Run tests
make test

# Lint and format
make lint
make format

# Type checking
make typecheck

# Run all checks
make all

Mainnet Verification

All core operations have been verified on Sui mainnet (10/10 tests passed):

# Operation Transaction Status
1 Flash Loan (borrow + repay SUI) F32f8cr...
2 supply_quick (0.1 SUI) F21Efd3...
3 sCoin roundtrip (supply→mint→burn→withdraw) BkLb4LB...
4 Full lending cycle (Obligation + Collateral + Oracle + Borrow USDC) BVWwpce...
5 repay_quick (repay USDC) C27xDCh...
6 withdraw_collateral (with Oracle update) 8MMpZyb...
7 withdraw_quick (redeem Market Coin) CXsck8H...
8 supply + withdraw (non-quick) AwxC6dM...
9 transfer_objects (split + transfer) 9cqVRxN...
10 lock_sca (veSCA lock 10 SCA) EfNSJTH...

Total gas: ~0.027 SUI

Notes

  1. Mainnet-only: Scallop is deployed on mainnet; network="testnet" / "devnet" raise ScallopConfigError rather than building transactions against placeholder addresses
  2. Live addresses: Protocol package IDs change on every upgrade, and the protocol rejects calls into a superseded package, so the SDK refreshes addresses from the Scallop address API on first use. Set SCALLOP_LIVE_ADDRESSES=0 or pass resolve_live_addresses=False to pin the compiled-in values
  3. Private Key Formats: Supports suiprivkey1... (Bech32), Base64, and Hex. Use environment variables instead of hardcoding
  4. Gas Fees: Transactions consume SUI as gas; ensure sufficient balance. Supplying or repaying SUI is taken from the gas coin, so keep more than amount + gas_budget
  5. Oracle Updates: Call update_asset_prices_quick() before borrow/liquidate operations; it raises OraclePriceError if a price cannot be posted
  6. Retrying failures: check TransactionResult.error_kind — only validation and build are safe to retry blindly; submission means the transaction may already be on chain

License

Apache License 2.0 - see LICENSE for details.

About

Official Python SDK for the Scallop lending protocol on Sui.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages