Skip to content

[docs] Add transaction builder methods documentation#25890

Draft
mystenmark wants to merge 12 commits intomainfrom
mlogan-transaction-builder
Draft

[docs] Add transaction builder methods documentation#25890
mystenmark wants to merge 12 commits intomainfrom
mlogan-transaction-builder

Conversation

@mystenmark
Copy link
Contributor

@mystenmark mystenmark commented Mar 19, 2026

Summary

  • Documents the step-by-step implementation of transfer and pay methods in TransactionBuilder
  • Explains how callers can use coin reservation objects for address balance support

This PR is stacked on top of #25818 (integrate-coin-smashing).

Test plan

  • Documentation only, no code changes

🤖 Generated with Claude Code

@mystenmark mystenmark temporarily deployed to sui-typescript-aws-kms-test-env March 19, 2026 02:07 — with GitHub Actions Inactive
@vercel
Copy link

vercel bot commented Mar 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sui-docs Ready Ready Preview, Comment Mar 20, 2026 9:23pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
multisig-toolkit Ignored Ignored Preview Mar 20, 2026 9:23pm
sui-kiosk Ignored Ignored Preview Mar 20, 2026 9:23pm

Request Review

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

📋 afdocs check results

URL: https://sui-docs-32nwld2yd-sui-foundation.vercel.app

Running checks on sui-docs-32nwld2yd-sui-foundation.vercel.app...

Agent-Friendly Docs Check: https://sui-docs-32nwld2yd-sui-foundation.vercel.app
Timestamp: 2026-03-19T02:26:02.774Z

llms-txt
  ✓ llms-txt-exists: llms.txt found at 1 location(s)
  ✓ llms-txt-valid: llms.txt follows the proposed structure (H1, blockquote, heading-delimited link sections)
  ⚠ llms-txt-size: llms.txt is 68,839 characters (between 50,000 and 100,000; consider splitting)
      Learn more: https://agentdocsspec.com/spec/#llms-txt-size
  ⚠ llms-txt-links-resolve: 659/673 links resolve (98%); 14 broken
      Learn more: https://agentdocsspec.com/spec/#llms-txt-links-resolve
  ✓ llms-txt-links-markdown: 646/673 links point to markdown content (96%)

markdown-availability
  ✓ markdown-url-support: 626/673 pages support .md URLs (93%)
  ✓ content-negotiation: 620/673 pages support content negotiation (92%)

page-size
  ✓ rendering-strategy: All 673 pages contain server-rendered content
  ⚠ page-size-markdown: 11 of 626 pages between 50K–100K chars (max 78K)
      Learn more: https://agentdocsspec.com/spec/#page-size-markdown
  ⚠ page-size-html: 12 of 673 pages convert to 50K–100K chars (max 78K, 7% boilerplate)
      Learn more: https://agentdocsspec.com/spec/#page-size-html
  ✗ content-start-position: 40 of 673 pages have content starting past 50% (worst 100%)
      Learn more: https://agentdocsspec.com/spec/#content-start-position

content-structure
  ✓ tabbed-content-serialization: 20 tab group(s) across 2 of 673 pages; all serialize under 50K chars
  ✓ section-header-quality: 1 page(s) with tab headers checked; headers include variant context
  ✓ markdown-code-fence-validity: All 1824 code fences properly closed across 627 pages

url-stability
  ✓ http-status-codes: All 673 pages return proper error codes for bad URLs
  ✗ redirect-behavior: 23 JavaScript redirect(s) detected across 673 pages
      Learn more: https://agentdocsspec.com/spec/#redirect-behavior

agent-discoverability
  ✓ llms-txt-directive: llms.txt directive found in all 618 pages, near the top of content; 55 failed to fetch

observability
  ○ llms-txt-freshness: No sitemap found; cannot assess llms.txt freshness without a sitemap as ground truth
  ✗ markdown-content-parity: 220 of 595 pages have substantive content differences between markdown and HTML (avg 20% missing); 31 failed to fetch
      Learn more: https://agentdocsspec.com/spec/#markdown-content-parity
  ✓ cache-header-hygiene: All 674 endpoints have appropriate cache headers

authentication
  ✓ auth-gate-detection: All 673 pages are publicly accessible
  ○ auth-alternative-access: All docs pages are publicly accessible; no alternative access paths needed

Summary
  13 passed, 4 warnings, 3 failed, 2 skipped (22 total)

Full spec: https://agentdocsspec.com/spec/

mlogan and others added 11 commits March 19, 2026 22:01
- Add coin_reservation_obj_refs() to extract coin reservations from gas payment
- Validate coin reservations are owned by sender (no sponsored tx support)
- Validate coin reservations are for SUI balance type
- Update gas balance checks to account for address balance gas
- Filter coin reservations from input objects (they're virtual)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pass compatibility arguments (for withdrawal PTB argument conversion)
through the execution pipeline to the programmable transaction executor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add transaction_rewriting module for handling coin reservations
- Update authority to compute compat_args and pass to execution
- Handle accumulator events for address balance gas payments

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Thread chain_identifier through simulacrum, replay, genesis-builder
- Update network_config_builder and single_node_benchmark
- Enable withdrawal compatibility PTB arguments in test config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add test helpers for coin reservation encoding and validation
- Add transactional tests for coin reservation gas scenarios
- Add e2e tests for coin reservation compatibility
- Test sponsored transaction rejection, mixed gas payments, etc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add --enable-coin-reservations flag to coin_reservation_gas and
insufficient_gas_payment tests so coin reservations work properly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Document why unwrap is safe in transaction_rewriting.rs (validation
  happens before execution, and scheduler reserves funds)
- Complete the incomplete comment in check_gas

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The change to allow empty transactions when gas is paid from address
balance should be gated by enable_coin_reservation_obj_refs to avoid
changing behavior for existing protocol versions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These tests use address balance and coin reservation features that are
not yet enabled on mainnet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test uses empty input objects with address balance gas, which now
requires coin reservations to be enabled.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mystenmark mystenmark marked this pull request as draft March 19, 2026 22:10
Documents the step-by-step implementation of transfer and pay methods
in TransactionBuilder, including how address balances can be used via
coin reservation objects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mystenmark mystenmark force-pushed the mlogan-transaction-builder branch from 1726629 to 69a3c7b Compare March 20, 2026 21:16
@mystenmark mystenmark temporarily deployed to sui-typescript-aws-kms-test-env March 20, 2026 21:17 — with GitHub Actions Inactive
@mystenmark mystenmark changed the title [sui-transaction-builder] Add address balance fallback to transfer/pay methods [docs] Add transaction builder methods documentation Mar 20, 2026
@mystenmark mystenmark changed the base branch from main to integrate-coin-smashing March 20, 2026 21:17
Comment on lines +1 to +189
# Transfer and Pay Methods in TransactionBuilder

This document describes the step-by-step implementation of transfer and pay methods in `crates/sui-transaction-builder/src/lib.rs`.

## Address Balance Support

Address balances can be used as a funding source by passing **coin reservation objects** to these methods. Callers are responsible for:

1. Creating a `FundsWithdrawalArg` to reserve the required funds from their address balance
2. Using `coin::redeem_funds` to convert the withdrawal into a coin object
3. Passing the resulting coin object (or its ObjectID) to the transaction builder methods

The transaction builder treats coin reservation objects the same as regular coin objects. This approach gives callers full control over when and how address balances are used.

## 1. `transfer_object` (lines 98-121)

Transfers a single object to a recipient. The object must allow public transfers.

**Steps:**
1. **Create a ProgrammableTransactionBuilder** - Initialize an empty transaction builder
2. **Get full object reference** - Fetch the object from storage and compute its full reference (ID, version, digest, and owner info)
3. **Build transfer command** - Call `builder.transfer_object(recipient, full_obj_ref)` which:
- Creates a pure input for the recipient address
- Adds the object as an input (handling shared vs owned object args)
- Emits a `Command::TransferObjects` command
4. **Get reference gas price** - Fetch the current gas price from the network
5. **Select gas coin** - Find a suitable gas coin from the signer's owned coins that:
- Is not the object being transferred
- Has sufficient balance for the gas budget
- If `gas` is provided, use that coin directly
6. **Create TransactionData** - Package everything into a signed transaction

---

## 2. `transfer_sui` (lines 144-157)

Transfers SUI coin to a recipient. The SUI object is also used as the gas object.

**Steps:**
1. **Get object reference** - Fetch the SUI coin's reference (ID, version, digest)
2. **Get reference gas price** - Fetch the current gas price
3. **Create transaction via `TransactionData::new_transfer_sui`** which internally:
- Creates a ProgrammableTransactionBuilder
- Calls `builder.transfer_sui(recipient, amount)` which:
- If `amount` is Some: Splits that amount from GasCoin, then transfers the split coin
- If `amount` is None: Transfers the entire GasCoin
- Uses the SUI object as both the coin source and gas payment

---

## 3. `pay` (lines 171-197)

Sends `Coin<T>` to multiple recipients with specified amounts. Uses a separate gas object.

**Steps:**
1. **Validate gas not in input coins** - Ensure the gas coin is not in the list of input coins (fails if it is)
2. **Get coin references** - Fetch object references for all input coins in parallel
3. **Get reference gas price** - Fetch the current gas price
4. **Select gas coin** - Find a gas coin not in the input list
5. **Create transaction via `TransactionData::new_pay`** which internally:
- Creates a ProgrammableTransactionBuilder
- Calls `builder.pay(coins, recipients, amounts)` which:
- **Merge all input coins into the first coin** - If multiple coins, emit `Command::MergeCoins` to combine them
- **Split the merged coin** - Emit `Command::SplitCoins` with one split per amount
- **Transfer split coins** - Group recipients (to minimize transfers if same recipient appears multiple times) and emit `Command::TransferObjects` for each unique recipient
- Uses the separate gas object for gas payment

---

## 4. `pay_sui` (lines 222-248)

Sends SUI coins to multiple recipients. The first input coin is used as gas.

**Steps:**
1. **Validate input not empty** - Ensure at least one coin is provided
2. **Get coin references** - Fetch object references for all input coins
3. **Extract gas coin** - Remove the first coin to use as gas payment
4. **Get reference gas price** - Fetch the current gas price
5. **Create transaction via `TransactionData::new_pay_sui`** which:
- Re-inserts the gas coin at position 0 of the coins vector (so all coins are available)
- Creates a ProgrammableTransactionBuilder
- Calls `builder.pay_sui(recipients, amounts)` which:
- **Uses GasCoin as the source** - The runtime provides access to all input coins via `Argument::GasCoin`
- **Split coins** - Emit `Command::SplitCoins(GasCoin, amounts)`
- **Transfer split coins** - Emit `Command::TransferObjects` for each unique recipient
- After execution: first coin holds residual balance (sum(inputs) - sum(amounts) - gas_cost), other coins are deleted

---

## 5. `pay_all_sui` (lines 257-281)

Sends all SUI from input coins to a single recipient. The first input coin is used as gas.

**Steps:**
1. **Validate input not empty** - Ensure at least one coin is provided
2. **Get coin references** - Fetch object references for all input coins
3. **Extract gas coin** - Remove the first coin to use as gas payment
4. **Get reference gas price** - Fetch the current gas price
5. **Create transaction via `TransactionData::new_pay_all_sui`** which:
- Re-inserts the gas coin at position 0 of the coins vector
- Creates a ProgrammableTransactionBuilder
- Calls `builder.pay_all_sui(recipient)` which:
- **Transfer entire GasCoin** - Emits `Command::TransferObjects([GasCoin], recipient)`
- After execution: The runtime first merges all input coins into the gas coin, then transfers it to the recipient (minus gas cost). All other input coins are deleted.

---

## 6. `split_coin` (lines 664-694)

Splits a coin into multiple coins with specified amounts.

**Steps:**
1. **Get coin object** - Fetch the coin and its reference
2. **Get coin type** - Extract the coin's type arguments (e.g., `Coin<SUI>` -> `SUI`)
3. **Get reference gas price** - Fetch the current gas price
4. **Select gas coin** - Find a gas coin that is not the coin being split
5. **Create Move call transaction** - Calls `sui::pay::split_vec(coin, amounts)`:
- Input 0: The coin object
- Input 1: BCS-encoded vector of split amounts
- Newly split coins are created; the original coin keeps the remainder

---

## 7. `split_coin_equal` (lines 697-727)

Splits a coin into N equal-sized coins.

**Steps:**
1. **Get coin object** - Fetch the coin and its reference
2. **Get coin type** - Extract the coin's type arguments
3. **Get reference gas price** - Fetch the current gas price
4. **Select gas coin** - Find a gas coin that is not the coin being split
5. **Create Move call transaction** - Calls `sui::pay::split_n(coin, count)`:
- Input 0: The coin object
- Input 1: BCS-encoded split count
- Creates N coins of equal value from the original

---

## 8. `merge_coins` (lines 755-792)

Merges two coins into one.

**Steps:**
1. **Get primary coin** - Fetch the target coin object and its reference
2. **Get coin to merge** - Fetch the source coin's reference
3. **Get coin type** - Extract the coin's type arguments
4. **Get reference gas price** - Fetch the current gas price
5. **Select gas coin** - Find a gas coin that is neither the primary nor the coin being merged
6. **Create Move call transaction** - Calls `sui::pay::join(target, source)`:
- Input 0: Primary coin (receives the balance)
- Input 1: Coin to merge (gets destroyed)
- The primary coin's balance increases; the merged coin is deleted

---

## Key Helper: `select_gas` (lines 56-85)

Used by most methods to find a suitable gas coin.

**Steps:**
1. **Validate gas budget** - Ensure budget >= reference gas price
2. **If gas provided** - Use the specified gas object directly
3. **If gas not provided** - Query all owned `GasCoin` objects from the signer, find the first one that:
- Is not in the list of input objects for this transaction
- Has balance >= gas budget
4. **Error if no suitable gas** - Suggest using `pay-sui` or `transfer-sui` if no separate gas coin is available

---

## Summary Table

| Method | Gas Source | Coin Merging | Use Case |
|--------|-----------|--------------|----------|
| `transfer_object` | Separate gas coin | N/A | Transfer any object |
| `transfer_sui` | Same as transfer coin | N/A | Transfer SUI (simple) |
| `pay` | Separate gas coin | Yes (input coins merged) | Pay multiple recipients with any coin type |
| `pay_sui` | First input coin | Yes (via runtime) | Pay multiple recipients with SUI |
| `pay_all_sui` | First input coin | Yes (via runtime) | Send all SUI to one recipient |
| `split_coin` | Separate gas coin | N/A | Split coin by amounts |
| `split_coin_equal` | Separate gas coin | N/A | Split coin into N equal parts |
| `merge_coins` | Separate gas coin | Yes (2 coins) | Combine two coins |

## Related Files

- `crates/sui-transaction-builder/src/lib.rs` - Main implementation
- `crates/sui-types/src/programmable_transaction_builder.rs` - Low-level PTB commands
- `crates/sui-types/src/transaction.rs` - TransactionData constructors
- `crates/sui-json-rpc/src/transaction_builder_api.rs` - RPC wrapper
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Transfer and Pay Methods in TransactionBuilder
This document describes the step-by-step implementation of transfer and pay methods in `crates/sui-transaction-builder/src/lib.rs`.
## Address Balance Support
Address balances can be used as a funding source by passing **coin reservation objects** to these methods. Callers are responsible for:
1. Creating a `FundsWithdrawalArg` to reserve the required funds from their address balance
2. Using `coin::redeem_funds` to convert the withdrawal into a coin object
3. Passing the resulting coin object (or its ObjectID) to the transaction builder methods
The transaction builder treats coin reservation objects the same as regular coin objects. This approach gives callers full control over when and how address balances are used.
## 1. `transfer_object` (lines 98-121)
Transfers a single object to a recipient. The object must allow public transfers.
**Steps:**
1. **Create a ProgrammableTransactionBuilder** - Initialize an empty transaction builder
2. **Get full object reference** - Fetch the object from storage and compute its full reference (ID, version, digest, and owner info)
3. **Build transfer command** - Call `builder.transfer_object(recipient, full_obj_ref)` which:
- Creates a pure input for the recipient address
- Adds the object as an input (handling shared vs owned object args)
- Emits a `Command::TransferObjects` command
4. **Get reference gas price** - Fetch the current gas price from the network
5. **Select gas coin** - Find a suitable gas coin from the signer's owned coins that:
- Is not the object being transferred
- Has sufficient balance for the gas budget
- If `gas` is provided, use that coin directly
6. **Create TransactionData** - Package everything into a signed transaction
---
## 2. `transfer_sui` (lines 144-157)
Transfers SUI coin to a recipient. The SUI object is also used as the gas object.
**Steps:**
1. **Get object reference** - Fetch the SUI coin's reference (ID, version, digest)
2. **Get reference gas price** - Fetch the current gas price
3. **Create transaction via `TransactionData::new_transfer_sui`** which internally:
- Creates a ProgrammableTransactionBuilder
- Calls `builder.transfer_sui(recipient, amount)` which:
- If `amount` is Some: Splits that amount from GasCoin, then transfers the split coin
- If `amount` is None: Transfers the entire GasCoin
- Uses the SUI object as both the coin source and gas payment
---
## 3. `pay` (lines 171-197)
Sends `Coin<T>` to multiple recipients with specified amounts. Uses a separate gas object.
**Steps:**
1. **Validate gas not in input coins** - Ensure the gas coin is not in the list of input coins (fails if it is)
2. **Get coin references** - Fetch object references for all input coins in parallel
3. **Get reference gas price** - Fetch the current gas price
4. **Select gas coin** - Find a gas coin not in the input list
5. **Create transaction via `TransactionData::new_pay`** which internally:
- Creates a ProgrammableTransactionBuilder
- Calls `builder.pay(coins, recipients, amounts)` which:
- **Merge all input coins into the first coin** - If multiple coins, emit `Command::MergeCoins` to combine them
- **Split the merged coin** - Emit `Command::SplitCoins` with one split per amount
- **Transfer split coins** - Group recipients (to minimize transfers if same recipient appears multiple times) and emit `Command::TransferObjects` for each unique recipient
- Uses the separate gas object for gas payment
---
## 4. `pay_sui` (lines 222-248)
Sends SUI coins to multiple recipients. The first input coin is used as gas.
**Steps:**
1. **Validate input not empty** - Ensure at least one coin is provided
2. **Get coin references** - Fetch object references for all input coins
3. **Extract gas coin** - Remove the first coin to use as gas payment
4. **Get reference gas price** - Fetch the current gas price
5. **Create transaction via `TransactionData::new_pay_sui`** which:
- Re-inserts the gas coin at position 0 of the coins vector (so all coins are available)
- Creates a ProgrammableTransactionBuilder
- Calls `builder.pay_sui(recipients, amounts)` which:
- **Uses GasCoin as the source** - The runtime provides access to all input coins via `Argument::GasCoin`
- **Split coins** - Emit `Command::SplitCoins(GasCoin, amounts)`
- **Transfer split coins** - Emit `Command::TransferObjects` for each unique recipient
- After execution: first coin holds residual balance (sum(inputs) - sum(amounts) - gas_cost), other coins are deleted
---
## 5. `pay_all_sui` (lines 257-281)
Sends all SUI from input coins to a single recipient. The first input coin is used as gas.
**Steps:**
1. **Validate input not empty** - Ensure at least one coin is provided
2. **Get coin references** - Fetch object references for all input coins
3. **Extract gas coin** - Remove the first coin to use as gas payment
4. **Get reference gas price** - Fetch the current gas price
5. **Create transaction via `TransactionData::new_pay_all_sui`** which:
- Re-inserts the gas coin at position 0 of the coins vector
- Creates a ProgrammableTransactionBuilder
- Calls `builder.pay_all_sui(recipient)` which:
- **Transfer entire GasCoin** - Emits `Command::TransferObjects([GasCoin], recipient)`
- After execution: The runtime first merges all input coins into the gas coin, then transfers it to the recipient (minus gas cost). All other input coins are deleted.
---
## 6. `split_coin` (lines 664-694)
Splits a coin into multiple coins with specified amounts.
**Steps:**
1. **Get coin object** - Fetch the coin and its reference
2. **Get coin type** - Extract the coin's type arguments (e.g., `Coin<SUI>` -> `SUI`)
3. **Get reference gas price** - Fetch the current gas price
4. **Select gas coin** - Find a gas coin that is not the coin being split
5. **Create Move call transaction** - Calls `sui::pay::split_vec(coin, amounts)`:
- Input 0: The coin object
- Input 1: BCS-encoded vector of split amounts
- Newly split coins are created; the original coin keeps the remainder
---
## 7. `split_coin_equal` (lines 697-727)
Splits a coin into N equal-sized coins.
**Steps:**
1. **Get coin object** - Fetch the coin and its reference
2. **Get coin type** - Extract the coin's type arguments
3. **Get reference gas price** - Fetch the current gas price
4. **Select gas coin** - Find a gas coin that is not the coin being split
5. **Create Move call transaction** - Calls `sui::pay::split_n(coin, count)`:
- Input 0: The coin object
- Input 1: BCS-encoded split count
- Creates N coins of equal value from the original
---
## 8. `merge_coins` (lines 755-792)
Merges two coins into one.
**Steps:**
1. **Get primary coin** - Fetch the target coin object and its reference
2. **Get coin to merge** - Fetch the source coin's reference
3. **Get coin type** - Extract the coin's type arguments
4. **Get reference gas price** - Fetch the current gas price
5. **Select gas coin** - Find a gas coin that is neither the primary nor the coin being merged
6. **Create Move call transaction** - Calls `sui::pay::join(target, source)`:
- Input 0: Primary coin (receives the balance)
- Input 1: Coin to merge (gets destroyed)
- The primary coin's balance increases; the merged coin is deleted
---
## Key Helper: `select_gas` (lines 56-85)
Used by most methods to find a suitable gas coin.
**Steps:**
1. **Validate gas budget** - Ensure budget >= reference gas price
2. **If gas provided** - Use the specified gas object directly
3. **If gas not provided** - Query all owned `GasCoin` objects from the signer, find the first one that:
- Is not in the list of input objects for this transaction
- Has balance >= gas budget
4. **Error if no suitable gas** - Suggest using `pay-sui` or `transfer-sui` if no separate gas coin is available
---
## Summary Table
| Method | Gas Source | Coin Merging | Use Case |
|--------|-----------|--------------|----------|
| `transfer_object` | Separate gas coin | N/A | Transfer any object |
| `transfer_sui` | Same as transfer coin | N/A | Transfer SUI (simple) |
| `pay` | Separate gas coin | Yes (input coins merged) | Pay multiple recipients with any coin type |
| `pay_sui` | First input coin | Yes (via runtime) | Pay multiple recipients with SUI |
| `pay_all_sui` | First input coin | Yes (via runtime) | Send all SUI to one recipient |
| `split_coin` | Separate gas coin | N/A | Split coin by amounts |
| `split_coin_equal` | Separate gas coin | N/A | Split coin into N equal parts |
| `merge_coins` | Separate gas coin | Yes (2 coins) | Combine two coins |
## Related Files
- `crates/sui-transaction-builder/src/lib.rs` - Main implementation
- `crates/sui-types/src/programmable_transaction_builder.rs` - Low-level PTB commands
- `crates/sui-types/src/transaction.rs` - TransactionData constructors
- `crates/sui-json-rpc/src/transaction_builder_api.rs` - RPC wrapper
---
title: Transfer and Pay Methods in TransactionBuilder
description: Learn how transfer and pay methods work in the Sui transaction builder, including gas selection, coin merging, and address balance support.
keywords: [transfer, pay, transaction builder, gas coin, coin merging, split coin, merge coins, address balance, PTB, programmable transaction block]
---
This topic describes the implementation of transfer and pay methods in `crates/sui-transaction-builder/src/lib.rs`.
## Address balance support
You can use address balances as a funding source by passing coin reservation objects to these methods. You are responsible for:
1. Creating a `FundsWithdrawalArg` to reserve the required funds from the address balance.
2. Using `coin::redeem_funds` to convert the withdrawal into a coin object.
3. Passing the resulting coin object (or its `ObjectID`) to the transaction builder methods.
The transaction builder treats coin reservation objects the same as regular coin objects. This approach gives you full control over when and how address balances are used.
## `transfer_object`
Transfers a single object to a recipient. The object must allow public transfers.
The method performs the following operations:
1. Creates a `ProgrammableTransactionBuilder`.
2. Fetches the object from storage and computes its full reference (ID, version, digest, and owner information).
3. Calls `builder.transfer_object(recipient, full_obj_ref)`, which:
- Creates a pure input for the recipient address.
- Adds the object as an input (handling shared compared to owned object arguments).
- Emits a `Command::TransferObjects` command.
4. Fetches the current reference gas price from the network.
5. Selects a gas coin from the signer's owned coins that:
- Is not the object being transferred.
- Has sufficient balance for the gas budget.
- If `gas` is provided, uses that coin directly.
6. Packages everything into a `TransactionData`.
## `transfer_sui`
Transfers SUI to a recipient. The SUI object is also used as the gas object.
The method performs the following operations:
1. Fetches the SUI coin reference (ID, version, digest).
2. Fetches the current reference gas price.
3. Creates a transaction through `TransactionData::new_transfer_sui`, which internally:
- Creates a `ProgrammableTransactionBuilder`.
- Calls `builder.transfer_sui(recipient, amount)`, which:
- If `amount` is `Some`: Splits that amount from `GasCoin`, then transfers the split coin.
- If `amount` is `None`: Transfers the entire `GasCoin`.
- Uses the SUI object as both the coin source and gas payment.
## `pay`
Sends `Coin<T>` to multiple recipients with specified amounts. Uses a separate gas object.
The method performs the following operations:
1. Validates that the gas coin is not in the list of input coins. Fails if it is.
2. Fetches object references for all input coins in parallel.
3. Fetches the current reference gas price.
4. Selects a gas coin not in the input list.
5. Creates a transaction through `TransactionData::new_pay`, which internally:
- Creates a `ProgrammableTransactionBuilder`.
- Calls `builder.pay(coins, recipients, amounts)`, which:
- Merges all input coins into the first coin. If multiple coins exist, emits `Command::MergeCoins` to combine them.
- Emits `Command::SplitCoins` with one split per amount.
- Groups recipients (to minimize transfers if the same recipient appears multiple times) and emits `Command::TransferObjects` for each unique recipient.
- Uses the separate gas object for gas payment.
## `pay_sui`
Sends SUI to multiple recipients. The first input coin is used as gas.
The method performs the following operations:
1. Validates that at least one coin is provided.
2. Fetches object references for all input coins.
3. Removes the first coin to use as gas payment.
4. Fetches the current reference gas price.
5. Creates a transaction through `TransactionData::new_pay_sui`, which:
- Re-inserts the gas coin at position 0 of the coins vector (so all coins are available).
- Creates a `ProgrammableTransactionBuilder`.
- Calls `builder.pay_sui(recipients, amounts)`, which:
- Uses `Argument::GasCoin` as the source.
- Emits `Command::SplitCoins(GasCoin, amounts)`.
- Emits `Command::TransferObjects` for each unique recipient.
- After execution:
- The first coin holds the residual balance (sum of inputs minus sum of amounts minus gas cost).
- The other coins are deleted.
## `pay_all_sui`
Sends all SUI from input coins to a single recipient. The first input coin is used as gas.
The method performs the following operations:
1. Validates that at least one coin is provided.
2. Fetches object references for all input coins.
3. Removes the first coin to use as gas payment.
4. Fetches the current reference gas price.
5. Creates a transaction through `TransactionData::new_pay_all_sui`, which:
- Re-inserts the gas coin at position 0 of the coins vector.
- Creates a `ProgrammableTransactionBuilder`.
- Calls `builder.pay_all_sui(recipient)`, which:
- Emits `Command::TransferObjects([GasCoin], recipient)`.
- The runtime first merges all input coins into the gas coin, then transfers it to the recipient (minus gas cost). All other input coins are deleted.
## `split_coin`
Splits a coin into multiple coins with specified amounts.
The method performs the following operations:
1. Fetches the coin object and its reference.
2. Extracts the coin type arguments (for example, `Coin<SUI>` resolves to `SUI`).
3. Fetches the current reference gas price.
4. Selects a gas coin that is not the coin being split.
5. Creates a Move call transaction that calls `sui::pay::split_vec(coin, amounts)`:
- Input 0: The coin object.
- Input 1: BCS-encoded vector of split amounts.
- The newly split coins are created and the original coin keeps the remainder.
## `split_coin_equal`
Splits a coin into N equal-sized coins.
The method performs the following operations:
1. Fetches the coin object and its reference.
2. Extracts the coin type arguments.
3. Fetches the current reference gas price.
4. Selects a gas coin that is not the coin being split.
5. Creates a Move call transaction that calls `sui::pay::split_n(coin, count)`:
- Input 0: The coin object.
- Input 1: BCS-encoded split count.
- Creates N coins of equal value from the original.
## `merge_coins`
Merges two coins into one.
The method performs the following operations:
1. Fetches the target coin object and its reference.
2. Fetches the source coin reference.
3. Extracts the coin type arguments.
4. Fetches the current reference gas price.
5. Selects a gas coin that is neither the target nor the source coin.
6. Creates a Move call transaction that calls `sui::pay::join(target, source)`:
- Input 0: The target coin (receives the balance).
- Input 1: The source coin (gets destroyed).
- The target coin balance increases and the source coin is deleted.
## Gas selection helper
The `select_gas` helper (used by most methods) finds a suitable gas coin through the following logic:
1. Validates that the gas budget is greater than or equal to the reference gas price.
2. If `gas` is provided, uses the specified gas object directly.
3. If `gas` is not provided, queries all owned `GasCoin` objects from the signer and finds the first one that:
- Is not in the list of input objects for the transaction.
- Has a balance greater than or equal to the gas budget.
4. Returns an error if no suitable gas coin exists. The error suggests using `pay-sui` or `transfer-sui` if no separate gas coin is available.
## Method comparison
| **Method** | **Gas source** | **Coin merging** | **Use case** |
|--------|-----------|--------------|----------|
| `transfer_object` | Separate gas coin | N/A | Transfer any object |
| `transfer_sui` | Same as transfer coin | N/A | Transfer SUI |
| `pay` | Separate gas coin | Yes (input coins merged) | Pay multiple recipients with any coin type |
| `pay_sui` | First input coin | Yes (through runtime) | Pay multiple recipients with SUI |
| `pay_all_sui` | First input coin | Yes (through runtime) | Send all SUI to one recipient |
| `split_coin` | Separate gas coin | N/A | Split coin by amounts |
| `split_coin_equal` | Separate gas coin | N/A | Split coin into N equal parts |
| `merge_coins` | Separate gas coin | Yes (2 coins) | Combine two coins |
## Related files
The following source files contain the relevant implementations:
- `crates/sui-transaction-builder/src/lib.rs`: Main implementation
- `crates/sui-types/src/programmable_transaction_builder.rs`: Low-level PTB commands
- `crates/sui-types/src/transaction.rs`: `TransactionData` constructors
- `crates/sui-json-rpc/src/transaction_builder_api.rs`: RPC wrapper

@mystenmark mystenmark force-pushed the integrate-coin-smashing branch 3 times, most recently from f32cee7 to 2b7aa07 Compare March 23, 2026 22:14
Base automatically changed from integrate-coin-smashing to main March 24, 2026 04:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants