# Scallop Advanced Transactions

> Advanced Scallop transaction building - dry runs, gas control, BCS serialization, multi-step PTBs, and coin management. Use when user says "dry run", "simulate", "gas budget", "BCS", "serialize", "multi-step transaction", "programmable transaction", "PTB", "split coins", "merge coins", "gas optimization", or asks about advanced transaction patterns beyond basic lending operations.

- Skill: `scallop-io/scallop-advanced-transactions` (Agent Skill)
- Install (CLI): `npx skillmds@latest add scallop-io/scallop-advanced-transactions`
- Raw SKILL.md: https://api.skillmd.com/api/skills/scallop-io/scallop-advanced-transactions/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-advanced-transactions

---


# Advanced Transactions

Build complex multi-step transactions with dry runs, gas control, coin management, and BCS serialization.

## Overview

Beyond the basic `*_quick` methods, the SDK provides fine-grained control over transaction construction:

- **Dry Runs**: Simulate transactions before executing
- **Gas Budget**: Control gas spending
- **Coin Management**: Split, merge, and select coins manually
- **Multi-Step PTBs**: Compose multiple operations in a single transaction
- **BCS Serialization**: Build raw serialized transactions

## ScallopBuilder & ScallopTxBlock

```python
from sui_scallop_sdk import ScallopClient

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

The `ScallopBuilder` handles signing and coin selection. The `ScallopTxBlock` accumulates commands.

## Dry Runs (Simulation)

Simulate a transaction without executing it:

```python
from sui_scallop_sdk import DryRunResult

tx = builder.create_tx_block()
market_coin = tx.supply_quick(1_000_000_000, "sui", client.wallet_address)
tx.transfer_objects([market_coin], client.wallet_address)

# Simulate instead of executing
dry_result: DryRunResult = builder.dry_run_tx_block(tx)

if dry_result.success:
    print(f"Gas estimate: {dry_result.gas_estimate}")
    print(f"Balance changes: {dry_result.balance_changes}")
    print(f"Events: {len(dry_result.events)}")
else:
    print(f"Would fail: {dry_result.error}")
```

### DryRunResult Fields

```python
# DryRunResult is a Pydantic model:
# success: bool                    - Whether simulation passed
# gas_estimate: int = 0            - Estimated gas consumption
# effects: dict = {}               - Simulated transaction effects
# events: list[dict] = []          - Simulated events
# balance_changes: list[dict] = [] - Token balance changes
# error: str | None = None         - Error if simulation failed
```

Dry runs are useful for:
- Estimating gas costs before committing
- Validating transaction correctness
- Checking balance changes without spending gas

## Gas Budget Control

```python
tx = builder.create_tx_block()

# Set explicit gas budget (in MIST, 1 SUI = 1e9 MIST)
tx.set_gas_budget(50_000_000)  # 0.05 SUI max gas

# Build and execute
market_coin = tx.supply_quick(1_000_000_000, "sui", client.wallet_address)
tx.transfer_objects([market_coin], client.wallet_address)
result = builder.sign_and_send_tx_block(tx)
```

If no budget is set, the SDK uses the default from dry-run estimation.

## Coin Management

### Split Coins

Split a coin into specific amounts:

```python
tx = builder.create_tx_block()

# Split from an existing coin (by input index)
coin_idx = tx.add_object_input("0xCOIN_OBJECT_ID")
smaller_coin = tx.split_coin(coin_idx, 500_000_000, coin_is_input=True)

# Split from gas coin (SUI only)
sui_for_fee = tx.split_from_gas(1_000_000)  # 0.001 SUI from gas
```

### Merge Coins

Combine multiple coins into one:

```python
tx = builder.create_tx_block()

# Merge coin objects
destination = tx.add_object_input("0xCOIN_A")
source1 = tx.add_object_input("0xCOIN_B")
source2 = tx.add_object_input("0xCOIN_C")

tx.merge_coins(destination, [source1, source2], is_objects=True)
```

### Coin Selection via Builder

The builder can automatically select coins to cover an amount:

```python
# Get coins covering a specific amount
coins = builder.get_coins_for_amount("sui", 5_000_000_000)

# Or let select_coin handle it within a transaction
coin_ref = builder.select_coin(tx, "sui", 5_000_000_000, sender=client.wallet_address)
# coin_ref["takeCoin"] - the coin input to use
# coin_ref["leftCoin"] - remaining change (or None)
```

## Multi-Step Programmable Transaction Blocks

Compose multiple operations in a single atomic transaction:

```python
tx = builder.create_tx_block()

# Step 1: Supply SUI to get market coins
market_coin = tx.supply_quick(10_000_000_000, "sui", client.wallet_address)

# Step 2: Mint sCoin from market coins (uses result of step 1)
scoin = tx.mint_scoin(market_coin, "sui", coin_is_result=True)

# Step 3: Transfer sCoin
tx.transfer_objects([scoin], client.wallet_address)

# All 3 steps execute atomically
result = builder.sign_and_send_tx_block(tx)
```

### Chaining Results

Many methods return an `int` index that references a transaction result. Pass these between operations using `coin_is_result=True`:

```python
tx = builder.create_tx_block()

# Result of supply is index 0
market_coin_idx = tx.supply(coin_input_idx, "sui", coin_is_result=True)

# Feed result into deposit_collateral
tx.deposit_collateral(
    obligation_id,
    market_coin_idx,
    "sui",
    coin_is_result=True,  # coin_input is a result, not an object input
)
```

### Complex Example: Flash Loan Arbitrage

```python
tx = builder.create_tx_block()

# 1. Borrow flash loan
loan_coin, receipt = tx.borrow_flash_loan(1_000_000_000, "sui")

# 2. Use borrowed funds (e.g., supply to earn market coins)
market_coin = tx.supply(loan_coin, "sui", coin_is_result=True)

# 3. Withdraw to get back underlying
withdrawn = tx.withdraw(market_coin, "sui", coin_is_result=True)

# 4. Repay flash loan (must happen in same transaction)
tx.repay_flash_loan(withdrawn, receipt, "sui")

# 5. Transfer any profit
# (In a real arbitrage, you'd do a swap between steps 2-3)

result = builder.sign_and_send_tx_block(tx)
```

### Multi-Asset Obligation Setup

This example uses an existing obligation (already created in a previous transaction):

```python
# obligation_id and obligation_key must already exist on-chain
tx = builder.create_tx_block()

# Deposit multiple collaterals
tx.deposit_collateral_quick(5_000_000_000, "sui", obligation_id, client.wallet_address)
tx.deposit_collateral_quick(1_000_000_000, "weth", obligation_id, client.wallet_address)

# Update oracle prices
tx.update_asset_prices_quick(["sui", "weth", "usdc"])

# Borrow against combined collateral
borrowed = tx.borrow_quick(500_000_000, "usdc", obligation_id, obligation_key)
tx.transfer_objects([borrowed], client.wallet_address)

result = builder.sign_and_send_tx_block(tx)
```

> **Note**: `create_obligation()` returns a single `int` (command index). The obligation ID and key must be parsed from the transaction result's `created_objects` after executing.

## Adding Arbitrary Object Inputs

For custom Move calls or advanced composition:

```python
# Add any on-chain object as a transaction input
input_idx = tx.add_object_input("0xOBJECT_ID")
```

## Transaction Inspection

```python
# Convert transaction to a dictionary for debugging
tx_dict = tx.to_dict()
print(tx_dict)
# Shows all commands, inputs, and their types
```

## BCS Serialization

Build a raw BCS-serialized transaction for manual signing or external submission:

```python
# Get gas coins and price
rpc = client.rpc_client
gas_price = rpc.get_reference_gas_price()
coins_data = rpc.get_coins(client.wallet_address)
gas_coins = [
    {
        "objectId": c["coinObjectId"],
        "version": c["version"],
        "digest": c["digest"],
    }
    for c in coins_data["data"][:1]  # Use first coin as gas
]

# Build BCS bytes
tx_bytes = tx.build_bcs_transaction(
    sender=client.wallet_address,
    gas_coins=gas_coins,
    gas_price=gas_price,
)

# Sign manually
from sui_scallop_sdk import SuiKeypair
keypair = SuiKeypair.from_bech32("suiprivkey1...")
signature = keypair.sign_transaction(tx_bytes)

# Submit via RPC
result = rpc.execute_transaction(
    tx_bytes=base64.b64encode(tx_bytes).decode(),
    signatures=[signature],
)
```

## TransactionResult Fields

```python
from sui_scallop_sdk import TransactionResult

# TransactionResult from sign_and_send_tx_block:
# success: bool               - Whether transaction succeeded
# digest: str | None           - Transaction hash
# error: str | None            - Error message if failed
# gas_used: int | None         - Gas consumed
# effects: dict | None         - Full transaction effects
# events: list[dict]           - Emitted events
# created_objects: list[str]   - IDs of new objects
# mutated_objects: list[str]   - IDs of changed objects
```

## References

- [Transaction Flow](../scallop-lend-integration/references/transaction-flow.md) - Basic transaction patterns
- [Error Handling](../scallop-error-handling/SKILL.md) - Transaction error recovery
- [Supported Coins](../../references/supported-coins.md) - Coin names and decimals

