A Python SDK for interacting with the Scallop Lending Protocol on Sui blockchain.
English | 繁體中文
- 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
- 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
- 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
- Cryptography: Ed25519 signing, address derivation, Bech32 key import (
suiprivkey1...) - BCS Serialization: Full transaction serialization support (mainnet verified)
- Oracle Integration: Pyth + X Oracle
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 .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}")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)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)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.
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}")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)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.
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)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)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)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)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)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)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_idis optional forlock_sca_quick,extend_lock_period_quick,extend_lock_amount_quick,renew_expired_vesca_quick, andredeem_sca_quick- when
vesca_key_idis omitted, the SDK uses the sender's highest-balance veSCA position - amounts are raw on-chain units, so
1 SCA = 1_000_000_000
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)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)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)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)Create a .env file:
PRIVATE_KEY=suiprivkey1... # or base64/hex format
NETWORK=mainnet
RPC_URL=https://fullnode.mainnet.sui.io:443Then use from_env():
client = ScallopClient.from_env()| 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.
Main entry point for SDK.
| Method | Description |
|---|---|
create_query() |
Create query interface |
create_builder() |
Create transaction builder |
| 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 |
| 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) |
| 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 |
| 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 |
# 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 allAll 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
- Mainnet-only: Scallop is deployed on mainnet;
network="testnet"/"devnet"raiseScallopConfigErrorrather than building transactions against placeholder addresses - 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=0or passresolve_live_addresses=Falseto pin the compiled-in values - Private Key Formats: Supports
suiprivkey1...(Bech32), Base64, and Hex. Use environment variables instead of hardcoding - 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 - Oracle Updates: Call
update_asset_prices_quick()before borrow/liquidate operations; it raisesOraclePriceErrorif a price cannot be posted - Retrying failures: check
TransactionResult.error_kind— onlyvalidationandbuildare safe to retry blindly;submissionmeans the transaction may already be on chain
Apache License 2.0 - see LICENSE for details.