# Scallop Borrow Incentive

> Earn borrow incentives on Scallop. Use when user says "borrow incentive", "borrower rewards", "stake obligation", "claim borrow rewards", or asks about earning rewards while borrowing on Scallop.

- Skill: `scallop-io/scallop-borrow-incentive` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add scallop-io/scallop-borrow-incentive`
- Raw SKILL.md: https://api.skillmd.com/api/skills/scallop-io/scallop-borrow-incentive/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-borrow-incentive

---


# Borrow Incentive

Earn additional rewards for borrowing on Scallop by staking your obligation.

## Overview

Borrow Incentive program rewards borrowers with SCA tokens. By staking your obligation, you earn rewards proportional to your borrow amount.

## How It Works

```
1. Create obligation and borrow assets
2. Stake obligation in borrow incentive program
3. Accumulate SCA rewards over time
4. Claim rewards anytime
5. Unstake when desired
```

## Getting Started

### Stake Obligation

```python
tx = builder.create_tx_block()

# Stake your obligation for borrow incentives
tx.stake_obligation(
    obligation_id=obligation_id,
    obligation_key=obligation_key
)

result = builder.sign_and_send_tx_block(tx)
```

```typescript
// TypeScript - Stake obligation
const client = await scallop.createScallopClient();
const result = await client.stakeObligation(obligationId, obligationKeyId);

// Or using builder
const tx = builder.createTxBlock();
await tx.stakeObligationWithVeScaQuick(obligationId, obligationKeyId);
await builder.signAndSendTxBlock(tx);
```

### Claim Rewards

```python
tx = builder.create_tx_block()

# Claim borrow incentive rewards for specific debt asset
reward_coin = tx.claim_borrow_incentive(
    obligation_id=obligation_id,
    obligation_key=obligation_key,
    coin_name="usdc"  # The debt asset you're claiming rewards for
)

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

```typescript
// TypeScript - Claim borrow incentive rewards
const client = await scallop.createScallopClient();
const result = await client.claimBorrowIncentive(obligationId, obligationKeyId);

// Or using builder
const tx = builder.createTxBlock();
const rewardCoin = await tx.claimBorrowIncentiveQuick('sca', obligationId, obligationKeyId);
tx.transferObjects([rewardCoin], sender);
await builder.signAndSendTxBlock(tx);
```

### Unstake Obligation

```python
tx = builder.create_tx_block()

# Unstake obligation
tx.unstake_obligation(
    obligation_id=obligation_id,
    obligation_key=obligation_key
)

result = builder.sign_and_send_tx_block(tx)
```

```typescript
// TypeScript - Unstake obligation
const client = await scallop.createScallopClient();
const result = await client.unstakeObligation(obligationId, obligationKeyId);

// Or using builder
const tx = builder.createTxBlock();
await tx.unstakeObligationQuick(obligationId, obligationKeyId);
await builder.signAndSendTxBlock(tx);
```

## Complete Flow

```python
from sui_scallop_sdk import ScallopClient

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

# Assume obligation already exists with borrows

# Step 1: Stake obligation
tx1 = builder.create_tx_block()
tx1.stake_obligation(obligation_id, obligation_key)
builder.sign_and_send_tx_block(tx1)
print("Obligation staked for borrow incentives")

# Step 2: Wait and accumulate rewards...

# Step 3: Claim rewards (for each debt asset)
tx2 = builder.create_tx_block()
usdc_rewards = tx2.claim_borrow_incentive(obligation_id, obligation_key, "usdc")
tx2.transfer_objects([usdc_rewards], client.wallet_address)
builder.sign_and_send_tx_block(tx2)
print("Claimed USDC borrow incentive rewards")

# Step 4: Unstake when done
tx3 = builder.create_tx_block()
tx3.unstake_obligation(obligation_id, obligation_key)
builder.sign_and_send_tx_block(tx3)
print("Obligation unstaked")
```

## Query Incentive Status

> **Note**: As of `sui-scallop-sdk` 0.3.0a1, the Python SDK's borrow-incentive reads are limited to veSCA binding lookups (`get_binded_obligation_id`, `get_binded_vesca_key`) — there are no pool/account/pending-reward queries. For those, use the TS SDK's `getBorrowIncentivePools()` / `getBorrowIncentiveAccounts()` (v4.3.0) or raw object queries.

## Reward Calculation

Rewards are distributed based on:
- Your borrow amount relative to total staked borrows
- Time duration
- Current reward rate

```python
your_share = your_borrow_value / total_staked_borrows
your_rewards = total_rewards * your_share * time_elapsed
```

## Effective Borrow Rate

With borrow incentives, your effective borrow rate is reduced:

```
Effective APY = Borrow APY - Reward APY

Example:
  Borrow APY: 8.5%
  Reward APY: 3.2%
  Effective APY: 5.3% (or even negative = paid to borrow!)
```

> **Note**: Market and incentive APY data is not yet queryable via the Python SDK. Use the TypeScript SDK or Scallop app for current rates.

> The two terms are not the same kind of number: borrow APY is a certain cost in the borrowed
> asset, while reward APY is SCA-denominated income whose value depends on the SCA price when you
> sell. A negative effective APY is real but is not a locked-in free carry. See
> [incentive-mechanics.md](references/incentive-mechanics.md).

## Supported Assets

Incentive coverage and reward rates are protocol parameters that change as programs are funded and
retired — there is no fixed list. Read the current set from chain with the TS SDK's
`getBorrowIncentivePools()` rather than assuming a table.

## Multi-Asset Rewards

If you have multiple debts, claim rewards for each:

```python
tx = builder.create_tx_block()

# Get your debt assets
obligation = query.get_obligation(obligation_id)
debt_assets = [d.coin_name for d in obligation.debts]

# Claim rewards for each
rewards = []
for coin_name in debt_assets:
    reward = tx.claim_borrow_incentive(obligation_id, obligation_key, coin_name)
    rewards.append(reward)

# Transfer all rewards
tx.transfer_objects(rewards, wallet_address)
result = builder.sign_and_send_tx_block(tx)
```

## Auto-Claim Strategy

Automatically claim rewards periodically:

```python
import time

def auto_claim_borrow_incentives(client, obligation_id, obligation_key, interval_hours=24):
    """Auto-claim borrow incentives."""
    builder = client.create_builder()
    query = client.create_query()

    while True:
        try:
            obligation = query.get_obligation(obligation_id)

            if obligation.debts:
                tx = builder.create_tx_block()

                for debt in obligation.debts:
                    reward = tx.claim_borrow_incentive(
                        obligation_id,
                        obligation_key,
                        debt.coin_name
                    )
                    tx.transfer_objects([reward], client.wallet_address)

                builder.sign_and_send_tx_block(tx)
                print(f"Claimed borrow incentives")

        except Exception as e:
            print(f"Error: {e}")

        time.sleep(interval_hours * 3600)
```

## Combining with Spool Staking

Double rewards strategy:

1. **Deposit**: Earn lending interest
2. **Stake in Spool**: Earn spool SCA rewards
3. **Borrow**: Use different assets as collateral
4. **Stake Obligation**: Earn borrow incentive rewards

```python
# Combined strategy
tx = builder.create_tx_block()

# Deposit and stake in spool
market_coin = tx.supply_quick(10_000_000_000, "sui", wallet)
tx.stake_spool(stake_account_id, stake_pool_id, market_coin, "sui")

# Add collateral and borrow
tx.deposit_collateral_quick(5_000_000_000, "sui", obligation_id, wallet)
borrowed = tx.borrow_quick(100_000_000, "usdc", obligation_id, obligation_key)
tx.transfer_objects([borrowed], wallet)

# Stake obligation for borrow incentives
tx.stake_obligation(obligation_id, obligation_key)

result = builder.sign_and_send_tx_block(tx)
# Now earning: lending yield + spool rewards + borrow incentives!
```

## Keeper / Liquidator Entry Points

`borrow-incentive-v2` exposes a handful of entry functions in [borrow_incentive/sources/user.move](../../../borrow-incentive-v2/borrow_incentive/sources/user.move) that aren't part of the normal user flow but matter for keepers, liquidators and indexers:

| Move entrypoint | Purpose |
|-----------------|---------|
| `force_unstake_unhealthy_v3` | Anyone can unstake an obligation from the incentive pool once it crosses into unhealthy territory. Pair with a liquidation in the same PTB. (`force_unstake_unhealthy` / `_v2` still exist in the module but are deprecated `abort 0` stubs — calling them always fails; they are **not** a backward-compat path.) |
| `refresh_inactive_boost`     | Re-evaluates the obligation's veSCA-derived boost when the underlying veSCA position changed (e.g. lock extended, key transferred). Needed before claims pay out at the new boost. |
| `deactivate_boost_v2`        | Removes a veSCA boost binding (public fun, not entry) — used during obligation closures. |

### Force-Unstake an Unhealthy Obligation (TypeScript)

The full v3 signature ([user.move](../../../borrow-incentive-v2/borrow_incentive/sources/user.move)) takes the incentive triplet **plus** the lending-protocol objects needed to evaluate health and the veSCA subscriber tables needed to unbind any boost:

```
force_unstake_unhealthy_v3(
  incentive_config:        &IncentiveConfig,
  incentive_pools:         &mut IncentivePools,
  incentive_accounts:      &mut IncentiveAccounts,
  protocol_version:        &Version,
  obligation:              &mut Obligation,
  market:                  &mut Market,
  coin_decimals_registry:  &CoinDecimalsRegistry,
  x_oracle:                &XOracle,
  ve_sca_subs_table:       &mut VeScaSubscriberTable,
  ve_sca_subs_whitelist:   &VeScaSubscriberWhitelist,
  clock:                   &Clock,
  ctx:                     &mut TxContext,
)
```

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

tx.moveCall({
  target: `${borrowIncentivePkg}::user::force_unstake_unhealthy_v3`,
  arguments: [
    tx.object(borrowIncentiveConfigId),
    tx.object(incentivePoolsId),
    tx.object(incentiveAccountsId),
    tx.object(protocolVersionId),
    tx.object(targetObligationId),
    tx.object(marketId),
    tx.object(coinDecimalsRegistryId),
    tx.object(xOracleId),
    tx.object(veScaSubscriberTableId),
    tx.object(veScaSubscriberWhitelistId),
    tx.object('0x6'), // clock
  ],
});

// Same PTB can then run a liquidate() against targetObligationId.
await builder.signAndSendTxBlock(tx);
```

All object IDs except clock come from `ScallopAddress` — e.g. `address.get('core.version')`, `address.get('core.market')`, `address.get('core.coinDecimalsRegistry')`, `address.get('core.oracles.xOracle')`, and the `borrowIncentive.*` and `vesca.subscriber.*` fields.

### Refresh Boost After veSCA Change

Full signature:

```
refresh_inactive_boost(
  incentive_config:    &IncentiveConfig,
  incentive_pools:     &mut IncentivePools,
  incentive_accounts:  &mut IncentiveAccounts,
  ve_sca_table:        &VeScaTable,
  obligation:          &mut Obligation,
  clock:               &Clock,
  ctx:                 &mut TxContext,
)
```

```typescript
tx.moveCall({
  target: `${borrowIncentivePkg}::user::refresh_inactive_boost`,
  arguments: [
    tx.object(borrowIncentiveConfigId),
    tx.object(incentivePoolsId),
    tx.object(incentiveAccountsId),
    tx.object(veScaTableId),
    tx.object(obligationId),
    tx.object('0x6'), // clock
  ],
});
```

Note: this entrypoint takes the **`Obligation`** (not its key) and the shared **`VeScaTable`** (not the user's `VeScaKey`). It asserts the bound veSCA's current power is zero before unbinding — i.e. it's specifically for cleaning up a stale boost after the lock expired.

> The TS SDK wraps `deactivate_boost_v2` as the builder method `deactivateBoost(obligation, veScaKey)` (see `src/txBuilders/borrowIncentive/moveCalls.ts`). `force_unstake_unhealthy_v3` and `refresh_inactive_boost` are not wrapped by either SDK — call them via `moveCall` ([advanced-transactions](../scallop-advanced-transactions/SKILL.md)). The `stake_with_ve_sca_v2` entrypoint (also in the same module) is the recommended path for boosted staking and is wrapped by the TS `stakeObligationWithVeScaQuick` helper.

## Error Handling

| Error | Cause | Solution |
|-------|-------|----------|
| `ObligationNotStaked` | Obligation not in program | Stake first |
| `NoBorrowsToIncentivize` | No active borrows | Borrow first |
| `NoRewardsToClaim` | No pending rewards | Wait for rewards |
| `ObligationStillHealthy` | `force_unstake_unhealthy_*` called against a healthy obligation | Check obligation risk first |
| `BoostStillActive` | `refresh_inactive_boost` called when the boost is current | No refresh needed |

## References

- [Incentive Mechanics](references/incentive-mechanics.md) - How incentives work
- [Obligation Manager](../scallop-obligation-manager/SKILL.md) - Obligation basics
- [Advanced Transactions](../scallop-advanced-transactions/SKILL.md) - Raw moveCall patterns
- [veSCA](../scallop-vesca/SKILL.md) - Boost source

