# Scallop Vesca

> Lock SCA for veSCA governance power. Use when user says "veSCA", "lock SCA", "vote escrow", "extend lock", "voting power", "unlock SCA", "governance", or asks about Scallop governance and revenue sharing.

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

---


# veSCA (Vote Escrow SCA)

Lock SCA tokens to receive veSCA for governance voting and protocol revenue sharing.

## Overview

veSCA is Scallop's governance token system:
- **Lock SCA** → Receive veSCA
- **veSCA** → Voting power + Revenue share
- **Longer lock** → More voting power

## Voting Power Formula

```python
voting_power = locked_sca * (remaining_time / max_lock_time)

max_lock_time = 4 years (1461 days)
```

Power depends on the time *remaining*, not time served, and decays linearly to zero at expiry.
Full derivation, the multiplier table, an off-chain calculator, and decay behaviour:
[voting-power.md](references/voting-power.md).

## Lock SCA

### Create New Lock

```python
tx = builder.create_tx_block()

# First, get a SCA coin input (e.g., split from existing coins)
sca_coin_idx = tx.add_object_input("0xSCA_COIN_OBJECT_ID")

# Lock SCA for 365 days
ve_sca_key = tx.lock_sca(
    sca_coin_idx=sca_coin_idx,     # Index of SCA coin (Input or Result)
    lock_period_days=365,           # Lock period in days (max 1461 = 4 years)
    coin_is_result=False,           # True if sca_coin_idx is a Result index
)

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

```typescript
// TypeScript - Lock SCA
const tx = builder.createTxBlock();
const unlockAt = Math.floor(Date.now() / 1000) + (365 * 24 * 60 * 60); // 1 year
tx.lockSca(veScaKey, scaCoin, unlockAt);
await builder.signAndSendTxBlock(tx);
```

### Lock Period Options

Maximum lock is **4 years (1461 days)**; the multiplier is `period / 1461 days`, so only a full
4-year lock gives 1:1. Full multiplier table:
[voting-power.md](references/voting-power.md).

## Manage Lock

### Extend Lock Period

Extend your lock to increase voting power:

```python
tx = builder.create_tx_block()

# Extend lock by 6 months
new_unlock_timestamp = current_unlock + (86400 * 180)  # 180 days

tx.extend_lock_period(
    vesca_key_id=ve_sca_key_id,
    new_unlock_at=new_unlock_timestamp
)

result = builder.sign_and_send_tx_block(tx)
```

```typescript
// TypeScript - Extend lock period
const tx = builder.createTxBlock();
const newUnlockAt = currentUnlock + (180 * 24 * 60 * 60);
tx.extendLockPeriod(veScaKey, newUnlockAt);
await builder.signAndSendTxBlock(tx);
```

### Extend Lock Amount

Add more SCA to existing lock:

```python
tx = builder.create_tx_block()

# Add more SCA to existing lock
sca_coin_idx = tx.add_object_input("0xSCA_COIN_OBJECT_ID")
tx.extend_lock_amount(
    vesca_key_id=ve_sca_key_id,
    sca_coin_idx=sca_coin_idx,  # Index of additional SCA coin
)

result = builder.sign_and_send_tx_block(tx)
```

```typescript
// TypeScript - Extend lock amount
const tx = builder.createTxBlock();
tx.extendLockAmount(veScaKey, scaCoin);
await builder.signAndSendTxBlock(tx);
```

### Renew Expired Lock

Re-lock expired veSCA:

```python
tx = builder.create_tx_block()

# Renew expired lock with new SCA and new unlock timestamp
sca_coin_idx = tx.add_object_input("0xSCA_COIN_OBJECT_ID")
new_unlock_at = int(time.time()) + (365 * 86400)  # 1 year in seconds
tx.renew_expired_vesca(
    vesca_key_id=ve_sca_key_id,
    sca_coin_idx=sca_coin_idx,        # SCA coin to lock
    new_unlock_at=new_unlock_at,       # Unlock timestamp in seconds
)

result = builder.sign_and_send_tx_block(tx)
```

```typescript
// TypeScript - Renew expired lock
const tx = builder.createTxBlock();
tx.renewExpiredVeSca(veScaKey);
await builder.signAndSendTxBlock(tx);
```

## Redeem SCA

After lock expires, redeem your SCA:

```python
tx = builder.create_tx_block()

# Redeem locked SCA (only works after unlock time)
sca_coin = tx.redeem_sca(vesca_key_id=ve_sca_key_id)

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

## Query veSCA Position

```python
query = client.create_query()

# Get veSCA object data (raw object query — no dedicated veSCA query method yet)
vesca_data = query.get_object(ve_sca_key_id)

# Parse fields from content
fields = vesca_data.get("content", {}).get("fields", {})
print(f"veSCA Object: {ve_sca_key_id}")
print(f"Fields: {fields}")
```

### On-Chain Getter Helpers

For cases where you don't want to parse raw object JSON, the `ve_sca` Move module exposes three public read helpers that take the veSCA key ID, the shared `VeScaTable`, and (for current power) the `Clock`:

| Function | Returns | Purpose |
|----------|---------|---------|
| `ve_sca::unlock_at(ve_sca_key_id, &ve_sca_table)` | `u64` | Lock expiry, Unix seconds |
| `ve_sca::locked_sca_amount(ve_sca_key_id, &ve_sca_table)` | `u64` | Total SCA currently locked |
| `ve_sca::ve_sca_amount(ve_sca_key_id, &ve_sca_table, &clock)` | `u64` | Current voting power (decayed) |

Source: [ve-sca/sources/ve_sca.move](../../../ve-sca/sources/ve_sca.move). The `VeScaTable` is a shared object; its ID lives in protocol addresses (`core.vesca.table`).

#### Use in a Devnet-Inspect / Dry-Run

These are `public fun` (not `entry`), so the standard way to consume them off-chain is `devInspectTransactionBlock` on a PTB that calls them:

```typescript
const tx = new TransactionBlock();

const power = tx.moveCall({
  target: `${veScaPkg}::ve_sca::ve_sca_amount`,
  arguments: [
    tx.pure.id(veScaKeyId),
    tx.object(veScaTableId),
    tx.object('0x6'), // clock
  ],
});
tx.moveCall({ target: `${utilsPkg}::utils::emit_u64`, arguments: [power] }); // any sink

const r = await suiClient.devInspectTransactionBlock({
  sender,
  transactionBlock: tx,
});
// Parse the returned value from r.results
```

The same pattern works for `unlock_at` and `locked_sca_amount` — and is cheaper than fetching and parsing the raw veSCA object when you only need one scalar.

> The Python and TypeScript SDKs don't currently wrap these getters. Use them when raw-object parsing becomes brittle (e.g. across module upgrades), or to fold a fresh, decayed voting-power read into a larger PTB.

## Revenue Sharing

veSCA holders receive protocol revenue in proportion to voting power:

```python
your_share = your_voting_power / total_voting_power
your_revenue = total_protocol_revenue * your_share
```

Your share falls as others lock more and as your own power decays. Details:
[revenue-sharing.md](references/revenue-sharing.md).

### Claim Revenue

veSCA holders can claim revenue through loyalty and referral programs:

```python
tx = builder.create_tx_block()

# Claim loyalty revenue (SCA rewards)
revenue = tx.claim_loyalty_revenue(vesca_key_id=ve_sca_key_id)

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

See [Loyalty](../scallop-loyalty/SKILL.md) and [Referral](../scallop-referral/SKILL.md) for more claim options.

## Complete veSCA Flow

```python
from sui_scallop_sdk import ScallopClient

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

# Step 1: Lock SCA for 2 years (730 days)
tx1 = builder.create_tx_block()
sca_coin_idx = tx1.add_object_input("0xSCA_COIN_OBJECT_ID")
ve_sca_key = tx1.lock_sca(sca_coin_idx, lock_period_days=730)
tx1.transfer_objects([ve_sca_key], client.wallet_address)
result1 = builder.sign_and_send_tx_block(tx1)
ve_sca_key_id = parse_created_object(result1)
print(f"Locked SCA for 2 years. veSCA key: {ve_sca_key_id}")

# Step 2: Check veSCA object
query = client.create_query()
vesca_data = query.get_object(ve_sca_key_id)
print(f"veSCA created: {ve_sca_key_id}")

# Step 3: Extend lock to 4 years for max voting power
tx2 = builder.create_tx_block()
new_unlock = int(time.time()) + (4 * 365 * 86400)  # seconds
tx2.extend_lock_period(ve_sca_key_id, new_unlock)
builder.sign_and_send_tx_block(tx2)
print("Extended lock to 4 years")

# Step 4: Claim loyalty revenue periodically
tx3 = builder.create_tx_block()
revenue = tx3.claim_loyalty_revenue(ve_sca_key_id)
tx3.transfer_objects([revenue], client.wallet_address)
builder.sign_and_send_tx_block(tx3)
print("Claimed loyalty revenue")

# Step 5: After 4 years - redeem SCA
# tx4 = builder.create_tx_block()
# sca = tx4.redeem_sca(ve_sca_key_id)
# tx4.transfer_objects([sca], client.wallet_address)
```

## Max Lock Strategy

For maximum voting power, lock for the full 1461 days:

```python
tx = builder.create_tx_block()

sca_coin_idx = tx.add_object_input("0xSCA_COIN_OBJECT_ID")
ve_sca_key = tx.lock_sca(
    sca_coin_idx=sca_coin_idx,
    lock_period_days=1461,  # Max: 4 years
)
```

Power decays continuously, so holding at 100% means periodically extending back out to 1461 days —
a max lock is not set-and-forget. See [vesca-mechanics.md](references/vesca-mechanics.md).

## Error Handling

| Error | Cause | Solution |
|-------|-------|----------|
| `LockPeriodTooShort` | Below minimum lock | Use longer lock period |
| `LockPeriodTooLong` | Above 4 years | Max is 4 years |
| `LockNotExpired` | Trying to redeem early | Wait for unlock |
| `ExtendWouldShortenLock` | New time < current | Extend must increase |

## References

- [veSCA Mechanics](references/vesca-mechanics.md) - Lock mechanics
- [Voting Power](references/voting-power.md) - Power calculations
- [Revenue Sharing](references/revenue-sharing.md) - Revenue distribution

