Sui.

Post

Share your knowledge.

Bounty+15

Bolke .
Aug 12, 2025
Expert Q&A

Sui Move Error - Unable to process transaction No valid gas coins found for the transaction

When I do this:

        // Split payment from primary coin
        const [paymentCoin] = tx.splitCoins(
            tx.object(primaryCoin.coinObjectId),
            [tx.pure.u64(requiredPaymentAmount)]
        );

        // Use the original coin for gas payment
        tx.setGasPayment([{
            objectId: primaryCoin.coinObjectId,
            version: primaryCoin.version,
            digest: primaryCoin.digest
        }]);

        tx.setGasBudget(10_000_000);

It complains about mutatable objects cannot appear more than one in one transaction. When I remove the gas payment, it complains "Unable to process transaction No valid gas coins found for the transaction.". My contract function accepts .01 sui in exchange for an NFT

  • Sui
  • Transaction Processing
  • Move
2
9
Share
Comments
.

Answers

9
Matthardy.
Aug 15 2025, 09:02
  1. In Sui, a coin is a mutable object.

  2. When you use the same coin as both a payment and a gas coin, the transaction will fail with the "mutable objects" error.

  3. This is because a mutable object cannot be accessed in multiple roles within the same transaction.

  4. In your code, primaryCoin is being split for payment and also set as the gas payment.

  5. This triggers the mutability conflict in the Sui transaction rules.

  6. The solution is to use two separate coin objects — one for gas and one for payment.

  7. If your wallet only has one coin, you first need to split it into two before constructing your actual transaction.

  8. Sui’s CLI or JavaScript SDK can split coins using splitCoins and return two distinct coin objects.

  9. The gas coin should not be modified during the transaction.

  10. Below is a working JavaScript approach:

const coins = await provider.getCoins({ owner: senderAddress }); if (coins.data.length < 2) throw new Error("Need at least 2 coins for gas + payment");

const gasCoin = coins.data[0]; // For gas only const paymentCoin = coins.data[1]; // For contract payment

const tx = new TransactionBlock(); tx.transferObjects([tx.object(paymentCoin.coinObjectId)], tx.pure(recipientAddress)); tx.setGasPayment([{ objectId: gasCoin.coinObjectId, version: gasCoin.version, digest: gasCoin.digest }]); tx.setGasBudget(10_000_000);

await signer.signAndExecuteTransactionBlock({ transactionBlock: tx });

  1. This way, the gas coin remains untouched except for paying gas.

  2. The payment coin is used for your contract’s .01 SUI fee.

  3. If you have only one coin, first send yourself a small transaction to split it into two.

  4. This can be done using sui client split-coin or an SDK call before the main transaction.

  5. Following this separation rule prevents both "mutable objects" and "no valid gas coin" errors

4
Comments
.
MiniBob.
Aug 12 2025, 19:33

This happens because you're trying to use the same coin object (primaryCoin) both as gas payment and as input to splitCoins, which makes it a mutable reference used in two different contexts — and Sui doesn't allow that for safety and linear logic (since coins are linear objects).

The way IMHO is to not manually set gas payment at all. Just let the Sui wallet/client automatically select an appropriate gas coin. Only use setGasPayment if you really need to specify which coin pays for gas (e.g., multi-coin wallets, specific gas management). Otherwise, avoid it.

Try the following:

// Split the primary coin to get a payment coin
const [paymentCoin] = tx.splitCoins(
    tx.object(primaryCoin.coinObjectId),
    [tx.pure.u64(requiredPaymentAmount)]
);

// Do your call that sends .01 SUI and gets an NFT
tx.moveCall({
    target: `${packageId}::your_module::buy_nft`,
    arguments: [
        paymentCoin,
        // other args...
    ],
});

// DO NOT set gas payment manually
// tx.setGasPayment(...) ← Remove this line

// Optional: set budget
tx.setGasBudget(10_000_000);

// Send the transaction
const result = await signer.signAndExecuteTransactionBlock({
    transactionBlock: tx,
    options: { showEffects: true },
});

Sui will:

  • Automatically choose a gas coin (could be the same one or another SUI coin in the wallet).
  • Handle the splitCoins safely.
  • Use a different coin (or sometimes the same one, but handled safely under the hood via proper object versioning).

Important: As long as your wallet has at least 1 $SUI that can cover gas, this will work.

2
Comments
.
0xduckmove.
Aug 13 2025, 03:14

You are running into a common Sui Move transaction design constraint the same coin object cannot be used both as a mutable input (e.g., for splitting or transferring) and as the gas coin in a single transaction.

Why this happens

  • When you use tx.splitCoins(tx.object(primaryCoin.coinObjectId), ...), you are marking primaryCoin as a mutable input.
  • When you also set it as the gas coin with tx.setGasPayment(...), Sui sees the same object being used in two roles, which is not allowed.
  • If you remove the gas payment, Sui cannot find a valid gas coin, hence the "No valid gas coins found for the transaction" error.

From context:

Transaction Effects Status: Invalid usage of value. Mutably borrowed values require unique usage. Immutably borrowed values cannot be taken or borrowed mutably. Taken values cannot be used again. (source)

How to fix

You must use a different coin for gas than the one you are splitting or transferring.

The Solution: Have at least two SUI coins in your wallet. Use one for the payment (splitting/transferring), and a different one for gas.

Example Flow

  1. Select two coins:
  • primaryCoin (for payment)
  • gasCoin (for gas)
  1. Split and pay with primaryCoin:
const [paymentCoin] = tx.splitCoins(
    tx.object(primaryCoin.coinObjectId),
    [tx.pure.u64(requiredPaymentAmount)]
);
  1. Set gas payment with gasCoin:
tx.setGasPayment([{
    objectId: gasCoin.coinObjectId,
    version: gasCoin.version,
    digest: gasCoin.digest
}]);
tx.setGasBudget(10_000_000);

Do not use the same coin object for both payment and gas.

My Reference

Sui Move: Invalid taking of the Gas coin. It can only be used by-value with TransferObjects

2
Comments
.
Owen.
Owen4622
Aug 13 2025, 06:10

The error occurs because you're trying to use the original primaryCoin object (which is consumed during the splitCoins operation) as gas payment. After splitting, the original coin's version/digest becomes invalid, causing the "mutatable objects cannot appear more than one" error when referenced again.

To fix do not manually set gas payment using the pre-split primaryCoin object. And nsure your primaryCoin has sufficient balance to cover both:

  • The payment amount (requiredPaymentAmount = 0.01 SUI)
  • The gas budget (10_000_000 = 0.01 SUI) → Total needed: ≥ 0.02 SUI

Just try

// Split payment from primary coin (leaves remaining balance in primaryCoin)
const [paymentCoin] = tx.splitCoins(
  tx.object(primaryCoin.coinObjectId),
  [tx.pure.u64(requiredPaymentAmount)]
);

// DO NOT setGasPayment manually - SDK auto-uses updated primaryCoin for gas
tx.setGasBudget(10_000_000); // Gas paid from primaryCoin's remaining balance
2
Comments
.
Paul.
Paul4200
Aug 13 2025, 08:48

Issues

You're encountering two main issues:

  1. Mutability Error: Attempting to use the same coin object for both gas payment and transaction input leads to the error: "Mutable objects cannot appear more than once in one transaction."

  2. Missing Gas Coin: Without a valid gas coin, the error "Unable to process transaction: No valid gas coins found for the transaction" occurs.


Solution

To resolve these issues:

  1. Split the Primary Coin for Payment: Use tx.splitCoins to create a new coin for the NFT purchase, ensuring it's separate from the gas payment.

  2. Set a Separate Gas Coin: Assign a different coin for gas payment using tx.setGasPayment.

  3. Define Gas Budget: Set an appropriate gas budget using tx.setGasBudget.


Code

// Step 1: Split the primary coin for payment
const [paymentCoin] = tx.splitCoins(
    tx.object(primaryCoin.coinObjectId),
    [tx.pure.u64(requiredPaymentAmount)]
);

// Step 2: Set a separate gas payment coin
const gasCoin = tx.object(gasCoinObjectId);
tx.setGasPayment([{
    objectId: gasCoin.coinObjectId,
    version: gasCoin.version,
    digest: gasCoin.digest
}]);

// Step 3: Set the gas budget for the transaction
tx.setGasBudget(10_000_000);
2
Comments
.
casey.
Aug 14 2025, 03:40

On Sui, a single coin object is mutable (its balance changes when you use it), and you cannot reference the same mutable object twice in one transaction — that’s why:

You’re splitting from the same primaryCoin

And also using it as gas payment

→ The transaction builder flags this as "mutable object appears more than once".

Why "No valid gas coins found" happens when you remove setGasPayment

When you don’t specify a gas coin, the Sui SDK automatically picks a gas coin from your owned coins that isn’t used in the transaction yet. But since your transaction is already consuming the only coin you have (via splitCoins), there’s no extra coin left to pay gas.

How to fix it:

You need two distinct coin objects:

**Coin A **→ used for gas only

Coin B → split for your .01 SUI payment

If you only have one coin in your wallet, you must first split it into two separate coin objects in a preliminary transaction.

The key is:

  • Sui prevents a single coin object from being both gas and a mutable input in the same transaction because it’s treated as the same mutable reference.
  • But in Move, you can receive a coin for payment without caring which object it came from — including one that was split from the gas coin earlier in the same transaction.

That means your Move function should accept only the payment coin, not the original coin, and you let the transaction builder handle splitting it before passing it in.

move


module my_package::nft_market {

    use sui::coin::{Self, Coin};
    use sui::sui::{SUI};
    use sui::object::{UID};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};

    /// Simple function: take 0.01 SUI and give NFT
    public entry fun buy_nft(
        payment: Coin<SUI>,  // User provides exactly 0.01 SUI
        ctx: &mut TxContext
    ) {
        let amount = coin::value(&payment);
        let price = 10_000_000; // 0.01 SUI in MIST (1 SUI = 1_000_000_000 MIST)

        assert!(amount == price, 0);

        // Transfer the payment to the seller (hardcoded example)
        transfer::transfer(payment, tx_context::sender(ctx));

        // Mint NFT for buyer
        let nft = NFT {
            id: object::new(ctx),
            name: b"Special NFT".to_vec(),
        };
        transfer::transfer(nft, tx_context::sender(ctx));
    }

    struct NFT has key, store {
        id: UID,
        name: vector<u8>,
    }
}

How this fixes the issue

When the client builds the transaction:

1. Gas coin (tx.gas) is automatically used for gas.

2. Split from gas to get a new paymentCoin:

js


const [paymentCoin] = tx.splitCoins(
    tx.gas, // mutable reference to gas coin
    [tx.pure.u64(requiredPaymentAmount)]
);

tx.moveCall({
    target: `${packageId}::nft_market::buy_nft`,
    arguments: [paymentCoin],
});

tx.setGasBudget(10_000_000);

3. Sui allows this because:

  • tx.gas is the gas payment object.
  • **The split output **(paymentCoin) is a new object.
  • They are now **distinct mutable objects **in the transaction.

✅ Benefit: You no longer need the wallet to hold two separate coin objects beforehand — the Move function only cares about the payment coin, and the client can always split it from the gas coin before calling.

2
Comments
.
d-podium.
Aug 15 2025, 00:18

The error you're encountering occurs because you're trying to use the same coin object for both payment and gas payment. Let's fix this with a proper implementation that follows Sui's best practices.

Here's the correct way to structure your transaction:

const tx = new Transaction();

// Split the primary coin into two parts:
// 1. Payment amount (0.01 SUI)
// 2. Gas payment amount (remainder)
const [paymentCoin, gasCoin] = tx.splitCoins(
    tx.object(primaryCoin.coinObjectId),
    [
        tx.pure.u64(requiredPaymentAmount),  // 0.01 SUI for payment
        tx.pure.u64(1_000_000)              // 1 SUI for gas
    ]
);

// Set the gas payment using the gas coin
tx.setGasPayment([gasCoin]);

// Set appropriate gas budget
tx.setGasBudget(10_000_000);

// Now use the payment coin in your transaction
// Example:
tx.moveCall({
    target: '0x2::nft::mint',
    arguments: [
        tx.object(paymentCoin),
        // ... other arguments
    ]
});

Why Your Original Code Failed Your original code had two issues:

  1. Duplicate Object Usage
// Problem: Using same coin object twice
const [paymentCoin] = tx.splitCoins(tx.object(primaryCoin.coinObjectId), [tx.pure.u64(requiredPaymentAmount)]);
tx.setGasPayment([{
    objectId: primaryCoin.coinObjectId,  // Same object used again
    version: primaryCoin.version,
    digest: primaryCoin.digest
}]);

This fails because Sui doesn't allow using the same mutable object more than once in a transaction.

  1. Missing Gas Payment
// Problem: No valid gas coins found
tx.setGasBudget(10_000_000);  // Budget set but no gas payment specified

When you removed the gas payment, the transaction failed because every transaction needs a valid gas payment.

Best Practices for Gas Management

  1. Split Coins Efficiently

    i. Always split coins before using them in transactions

    ii. Ensure sufficient balance for both payment and gas

    iii. Use appropriate gas amounts based on transaction complexity

  2. Gas Payment Configuration

    i. Set gas payment immediately after splitting coins

    ii. Use the SDK's built-in gas management features

    iii. Ensure gas budget is sufficient for transaction execution

  3. Transaction Structure

    i. Split coins first

    ii. Set gas payment

    iii. Set gas budget

    iv. Then perform the main transaction operations

The solution provided at the top of this answer follows these best practices and will resolve both errors you're encountering. Remember that proper gas management is crucial for successful transaction execution on the Sui network.

2
Comments
.
Redterror.
Aug 15 2025, 10:50

To fix the problem you're running into with the Sui transaction error about no valid gas coins found or mutable objects showing up more than once, it's because you can't use the same coin for both splitting off the payment and covering gas fees, as gas coins need to stay separate from the items you're tweaking in the deal. The easy tweak is to split the payment straight from the gas source instead of your main coin, so change it to something like

const paymentCoin = tx.splitCoins(tx.gas(), [tx.pure.u64(requiredPaymentAmount)]);

then drop the manual gas payment line altogether since the system will pick it up on its own, and keep your gas budget set as usual. This lets you pull from the gas coin without clashing, as long as your wallet has plenty to handle the 0.01 Sui payment plus fees.

2
Comments
.
BigDev.
Aug 15 2025, 16:20

This issue happens because you’re using the same coin (primaryCoin) for both gas and as an input in splitCoins, which isn’t allowed in Sui due to its rules around linear objects and safe mutation.

To fix it, don’t manually set the gas payment. Let the Sui wallet or client auto-select a suitable gas coin. You only need setGasPayment in advanced cases (like precise coin control). Here’s the clean approach:


// Split the primary coin to create a new payment coin
const [paymentCoin] = tx.splitCoins(
  tx.object(primaryCoin.coinObjectId),
  [tx.pure.u64(requiredPaymentAmount)]
);

// Call your function using the new coin
tx.moveCall({
  target: ${packageId}::your_module::buy_nft,
  arguments: [paymentCoin],
});

// No manual gas setting — remove tx.setGasPayment(...)

// Set your gas budget
tx.setGasBudget(10_000_000);

// Execute the transaction
const result = await signer.signAndExecuteTransactionBlock({
  transactionBlock: tx,
  options: { showEffects: true },
});

Sui will safely pick a gas coin from your wallet (as long as one is available) and handle everything behind the scenes

0
Comments
.

Do you know the answer?

Please log in and share it.