# Scallop Spool Staking

> Stake market coins in Scallop spool. Use when user says "spool", "stake market coins", "spool rewards", "staking rewards", "unstake spool", "SCA rewards", or asks about Scallop staking yields.

- Skill: `scallop-io/scallop-spool-staking` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add scallop-io/scallop-spool-staking`
- Raw SKILL.md: https://api.skillmd.com/api/skills/scallop-io/scallop-spool-staking/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: scallop-io (https://skillmd.com/u/scallop-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/scallop-io/scallop-spool-staking

---


# Spool Staking

Stake market coins in Scallop spool to earn additional SCA rewards.

## Overview

Spool is Scallop's staking system where depositors can stake their market coins to earn:
- **Base Yield**: Interest from lending
- **SCA Rewards**: Additional SCA token rewards

> **Note**: This skill uses `supply()` to supply assets to the lending pool before staking.

## How Spool Works

```
1. Deposit assets → Receive market coins
2. Stake market coins in spool → Earn SCA rewards
3. Claim rewards anytime
4. Unstake when desired → Get market coins back
```

## Getting Started

### Create Stake Account

Each asset requires a separate stake account. You need the `stake_pool_id` for that asset (from protocol addresses):

```python
tx = builder.create_tx_block()

# Create stake account for SUI market coins
# stake_pool_id comes from protocol addresses config
stake_account_idx = tx.create_stake_account(
    stake_pool_id=stake_pool_id,  # Stake pool object ID
    coin_name="sui",
)

# Transfer stake account to yourself
tx.transfer_objects([stake_account_idx], wallet_address)

result = builder.sign_and_send_tx_block(tx)
# Parse stake_account_id from result
```

```typescript
// TypeScript - Create stake account
const client = await scallop.createScallopClient();
const result = await client.createStakeAccount('sui');
// Parse stakeAccountId from result
```

### Stake Market Coins

```python
tx = builder.create_tx_block()

# Option 1: Deposit and stake in one transaction
market_coin = tx.supply_quick(1_000_000_000, "sui", wallet_address)
tx.stake_spool(
    stake_account_id=stake_account_id,  # Stake account object ID
    stake_pool_id=stake_pool_id,        # Stake pool object ID
    coin_input_idx=market_coin,         # Index of market coin to stake
    coin_name="sui",
)

# Option 2: Stake existing market coins
# market_coin_idx = tx.add_object_input("0xMARKET_COIN_OBJECT_ID")
# tx.stake_spool(stake_account_id, stake_pool_id, market_coin_idx, "sui")

result = builder.sign_and_send_tx_block(tx)
```

```typescript
// TypeScript - Deposit and stake
const client = await scallop.createScallopClient();
const result = await client.depositAndStake('sui', 1_000_000_000, true, stakeAccountId);

// Or stake existing market coins
const tx = builder.createTxBlock();
await tx.stakeQuick(1_000_000_000, 'sui', stakeAccountId);
await builder.signAndSendTxBlock(tx);
```

### Claim Rewards

```python
tx = builder.create_tx_block()

# Claim accumulated SCA rewards
reward_coin = tx.claim_spool_rewards(
    stake_account_id=stake_account_id,  # Stake account object ID
    stake_pool_id=stake_pool_id,        # Stake pool object ID
    reward_pool_id=reward_pool_id,      # Reward pool object ID
    coin_name="sui",
)

tx.transfer_objects([reward_coin], wallet_address)
result = builder.sign_and_send_tx_block(tx)
```

```typescript
// TypeScript - Claim rewards
const client = await scallop.createScallopClient();
const result = await client.claim('sui', true, stakeAccountId);

// Or using builder
const tx = builder.createTxBlock();
const rewards = await tx.claimQuick('sui', stakeAccountId);
tx.transferObjects(rewards, sender);
await builder.signAndSendTxBlock(tx);
```

### Unstake

```python
tx = builder.create_tx_block()

# Unstake market coins
market_coin = tx.unstake_spool(
    stake_account_id=stake_account_id,  # Stake account object ID
    stake_pool_id=stake_pool_id,        # Stake pool object ID
    amount=1_000_000_000,               # Amount of market coins to unstake
    coin_name="sui",
)

# Option 1: Keep market coins
tx.transfer_objects([market_coin], wallet_address)

# Option 2: Withdraw to underlying
underlying = tx.withdraw(market_coin, "sui", coin_is_result=True)
tx.transfer_objects([underlying], wallet_address)

result = builder.sign_and_send_tx_block(tx)
```

```typescript
// TypeScript - Unstake
const client = await scallop.createScallopClient();
const result = await client.unstake('sui', 1_000_000_000, true, stakeAccountId);

// Or unstake and withdraw in one call
const result = await client.unstakeAndWithdraw('sui', 1_000_000_000, true, stakeAccountId);

// Using builder with unstakeQuick
const tx = builder.createTxBlock();
const sCoin = await tx.unstakeQuick(1_000_000_000, 'sui', stakeAccountId);
tx.transferObjects([sCoin], sender);
await builder.signAndSendTxBlock(tx);
```

## Complete Staking Flow

```python
from sui_scallop_sdk import ScallopClient

client = ScallopClient(secret_key="...", network="mainnet")
builder = client.create_builder()

# Step 1: Create stake account (one-time)
tx1 = builder.create_tx_block()
stake_account = tx1.create_stake_account(stake_pool_id, "sui")
tx1.transfer_objects([stake_account], client.wallet_address)
result1 = builder.sign_and_send_tx_block(tx1)
stake_account_id = parse_created_object(result1)

# Step 2: Deposit and stake
tx2 = builder.create_tx_block()
market_coin = tx2.supply_quick(10_000_000_000, "sui", client.wallet_address)
tx2.stake_spool(stake_account_id, stake_pool_id, market_coin, "sui")
result2 = builder.sign_and_send_tx_block(tx2)

# Step 3: Wait and accumulate rewards...

# Step 4: Claim rewards
tx3 = builder.create_tx_block()
rewards = tx3.claim_spool_rewards(stake_account_id, stake_pool_id, reward_pool_id, "sui")
tx3.transfer_objects([rewards], client.wallet_address)
result3 = builder.sign_and_send_tx_block(tx3)

# Step 5: Unstake when ready
tx4 = builder.create_tx_block()
market_coin = tx4.unstake_spool(stake_account_id, stake_pool_id, 10_000_000_000, "sui")
underlying = tx4.withdraw(market_coin, "sui", coin_is_result=True)
tx4.transfer_objects([underlying], client.wallet_address)
result4 = builder.sign_and_send_tx_block(tx4)
```

## Query Staking Position

```python
query = client.create_query()

# Get stake account object data (raw object query — no dedicated stake query method yet)
stake_data = query.get_object(stake_account_id)

# Parse fields from content
fields = stake_data.get("content", {}).get("fields", {})
print(f"Stake Account: {stake_account_id}")
print(f"Fields: {fields}")
```

> **Note**: The Python SDK (`sui-scallop-sdk` >= 0.3.0a1) also has dedicated spool reads on `ScallopQuery`: `get_spool(market_coin_name)` / `get_spools()` for pool state and `get_stake_accounts()` / `get_all_stake_accounts()` for a wallet's stake accounts — prefer these over raw `get_object()` parsing.

## Supported Spool Markets

Only **9** of the 34 lending markets have a spool: `ssui`, `susdc`, `swusdc`, `swusdt`, `sweth`,
`scetus`, `safsui`, `shasui`, `svsui` (keyed by market coin). Check before assuming one exists.

APY is variable — it depends on the pool's emission rate and total staked, so query it with
`query.get_spool(market_coin_name)` rather than assuming a figure.

Full table with stake pool and reward pool IDs:
[staking-mechanics.md](references/staking-mechanics.md).

## Multi-Asset Staking

Stake multiple assets for diversified yield:

```python
assets_to_stake = [
    ("sui", 10_000_000_000),
    ("usdc", 1_000_000_000),
    ("weth", 50_000_000)
]

for coin_name, amount in assets_to_stake:
    # Get pool IDs for this coin from protocol addresses
    pool_id = get_stake_pool_id(coin_name)  # from addresses config

    # Create stake account
    tx1 = builder.create_tx_block()
    stake_account = tx1.create_stake_account(pool_id, coin_name)
    tx1.transfer_objects([stake_account], wallet_address)
    result1 = builder.sign_and_send_tx_block(tx1)
    stake_account_id = parse_created_object(result1)

    # Deposit and stake
    tx2 = builder.create_tx_block()
    market_coin = tx2.supply_quick(amount, coin_name, wallet_address)
    tx2.stake_spool(stake_account_id, pool_id, market_coin, coin_name)
    builder.sign_and_send_tx_block(tx2)

    print(f"Staked {amount} {coin_name}")
```

## Auto-Compound Strategy

Rewards do not auto-compound — unclaimed rewards earn nothing, so reinvesting is a loop you run
yourself: claim → swap to the staked asset → supply → stake the market coin.

Full worked loop, interval trade-offs, and failure handling:
[rewards-calculation.md](references/rewards-calculation.md).

## Per-Reward-Type Redemption (Move)

The high-level helpers above call into `spool::user::redeem_rewards<StakeType, RewardType>` under the hood. A spool can have **multiple reward types** (e.g. SCA + a campaign token), so the Move entrypoint is generic in both the staked market coin and the reward coin:

```
public fun redeem_rewards<StakeType, RewardType>(
    spool:         &mut Spool,
    rewards_pool:  &mut RewardsPool<RewardType>,
    spool_account: &mut SpoolAccount<StakeType>,
    clock:         &Clock,
    ctx:           &mut TxContext,
): Coin<RewardType>
```

The shared `Spool` is reward-agnostic; the `RewardsPool` is generic over `RewardType` only. Argument **order matters** — `rewards_pool` sits between `spool` and `spool_account`.

Source: [spool/spool/sources/user.move](../../../spool/spool/sources/user.move).

### When to drop down to the raw move call

- The reward pool you want to claim isn't the default SCA pool the SDK helper targets (e.g. a temporary campaign reward pool).
- You want to **claim multiple reward types** for the same stake account in one PTB.
- You're building an indexer/keeper and need to enumerate `RewardsPool` objects via [rpc-client](../scallop-rpc-client/SKILL.md), then call `redeem_rewards` per pair.

### Example: Multi-Reward Claim (TypeScript)

```typescript
const tx = builder.createTxBlock();

// SCA rewards
const sca = tx.moveCall({
  target: `${spoolPkg}::user::redeem_rewards`,
  typeArguments: [marketCoinType, scaType],
  arguments: [
    tx.object(spoolId),
    tx.object(scaRewardsPoolId),
    tx.object(stakeAccountId),
    tx.object('0x6'),
  ],
});

// Campaign reward (different RewardType, same shared Spool + stake account)
const bonus = tx.moveCall({
  target: `${spoolPkg}::user::redeem_rewards`,
  typeArguments: [marketCoinType, bonusRewardType],
  arguments: [
    tx.object(spoolId),
    tx.object(bonusRewardsPoolId),
    tx.object(stakeAccountId),
    tx.object('0x6'),
  ],
});

tx.transferObjects([sca, bonus], sender);
await builder.signAndSendTxBlock(tx);
```

The SDK's `claim_spool_rewards()` / `claimQuick()` helpers internally use this for the canonical SCA pool only.

## Error Handling

| Error | Cause | Solution |
|-------|-------|----------|
| `SpoolNotFound` | Invalid coin name | Check spool exists for asset |
| `InvalidStakeAccount` | Wrong account | Use correct stake account |
| `InsufficientStake` | Not enough staked | Reduce unstake amount |
| `NoRewardsToClaim` | No pending rewards | Wait for rewards to accrue |
| `RewardPoolMismatch` | `redeem_rewards` called with a `RewardsPool` that doesn't match the `SpoolPool`'s `StakeType` | Verify the rewards pool object ID belongs to the same spool |

## References

- [Staking Mechanics](references/staking-mechanics.md) - How spool works
- [Rewards Calculation](references/rewards-calculation.md) - Reward formulas
- [Supported Coins](../../references/supported-coins.md) - All supported assets
- [Advanced Transactions](../scallop-advanced-transactions/SKILL.md) - Raw moveCall patterns

