Skip to content
RustPluginGenerator

OnGiveSoldItem

OnGiveSoldItem is called when a vending machine hands a purchased item to the buyer, before the item is moved out of the machine and into the buyer's inventory.

Signature
object OnGiveSoldItem(VendingMachine vendingMachine, Item soldItem, BasePlayer buyer)

Parameters

Hook parameters
ParameterTypeDescription
vendingMachineVendingMachineThe VendingMachine entity that is handing the purchased item to the buyer. Use it to read position, OwnerID, the machine's shop front or its inventory.
soldItemItemThe Item instance being given to the buyer. It is the actual item moved out of the machine, so its amount, skin and condition reflect what was purchased.
buyerBasePlayerThe BasePlayer receiving the item. May be null-checked defensively even though the game normally supplies a valid connected player.

Return value

Return null to let the machine continue and give the item to the buyer as normal. Return any non-null value (for example true) to stop the vanilla give code; the item is not delivered, so handle refunds or logging yourself if that is the intended behavior.

How it works

The hook fires inside VendingMachine.GiveSoldItem, which the server calls once a purchase has been accepted and payment taken. It runs before the machine splits the sold item into stackable chunks and calls GiveItem on the buyer, so it is the last point where you can inspect the transaction before the item leaves the machine.

soldItem is the real item object, not a copy: reading info.shortname, amount, skin, condition or name tells you exactly what was bought. buyer gives you the purchasing player for permission checks, messaging or logging, and vendingMachine lets you tie the sale back to a specific entity (for example by net.ID or OwnerID).

Returning a non-null value cancels the default hand-off. Because the original method returns void, any non-null return simply stops the vanilla code from moving the item, which effectively voids the delivery without refunding automatically. Declaring more specific parameter types is unnecessary here; all three arguments are already the concrete types you want.

Example plugin: Announce and log high-value vending machine sales

VendingSaleLogger.cs
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Vending Sale Logger", "RustPluginGenerator", "1.0.0")]
    [Description("Logs vending machine sales and announces purchases above a configurable amount")]
    public class VendingSaleLogger : RustPlugin
    {
        private const string PermNotify = "vendingsalelogger.notify";

        private PluginConfig config;
        private SaleData data;

        private class PluginConfig
        {
            [JsonProperty("Announce sales with amount at or above")]
            public int AnnounceThreshold = 100;

            [JsonProperty("Keep sale history in data file")]
            public bool KeepHistory = true;

            [JsonProperty("Maximum history entries")]
            public int MaxHistory = 500;
        }

        private class SaleData
        {
            public List<SaleRecord> Sales = new List<SaleRecord>();
        }

        private class SaleRecord
        {
            public string Buyer;
            public ulong BuyerId;
            public string Item;
            public int Amount;
            public ulong Skin;
            public ulong MachineId;
            public DateTime Time;
        }

        protected override void LoadDefaultConfig() => config = new PluginConfig();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<PluginConfig>();
                if (config == null) throw new Exception();
            }
            catch
            {
                PrintWarning("Configuration file is invalid, using defaults");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

        protected override void SaveConfig() => Config.WriteObject(config);

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["SaleBroadcast"] = "{0} bought {1} x {2} from a vending machine",
                ["SaleNotify"] = "You bought {0} x {1}"
            }, this);
        }

        private string Msg(string key, string userId = null, params object[] args) =>
            string.Format(lang.GetMessage(key, this, userId), args);

        private void Init()
        {
            permission.RegisterPermission(PermNotify, this);
            LoadData();
        }

        private void Unload()
        {
            if (config.KeepHistory)
            {
                Interface.Oxide.DataFileSystem.WriteObject(Name, data);
            }
        }

        private void LoadData()
        {
            try
            {
                data = Interface.Oxide.DataFileSystem.ReadObject<SaleData>(Name) ?? new SaleData();
            }
            catch
            {
                data = new SaleData();
            }
        }

        private object OnGiveSoldItem(VendingMachine vendingMachine, Item soldItem, BasePlayer buyer)
        {
            if (vendingMachine == null || soldItem == null || soldItem.info == null || buyer == null) return null;

            if (config.KeepHistory)
            {
                data.Sales.Add(new SaleRecord
                {
                    Buyer = buyer.displayName,
                    BuyerId = buyer.userID.Get(),
                    Item = soldItem.info.shortname,
                    Amount = soldItem.amount,
                    Skin = soldItem.skin,
                    MachineId = vendingMachine.net.ID.Value,
                    Time = DateTime.UtcNow
                });

                if (data.Sales.Count > config.MaxHistory)
                {
                    data.Sales.RemoveRange(0, data.Sales.Count - config.MaxHistory);
                }
            }

            if (soldItem.amount >= config.AnnounceThreshold)
            {
                Server.Broadcast(Msg("SaleBroadcast", null, buyer.displayName, soldItem.amount, soldItem.info.displayName.english));
            }

            if (permission.UserHasPermission(buyer.UserIDString, PermNotify))
            {
                SendReply(buyer, Msg("SaleNotify", buyer.UserIDString, soldItem.amount, soldItem.info.displayName.english));
            }

            return null;
        }
    }
}

The plugin hooks OnGiveSoldItem and records each sale: item shortname, amount, skin, buyer name and the machine's net ID. Sales whose total stack amount meets a configurable threshold are broadcast to the server, and every sale is appended to an in-memory list that is written to oxide/data on unload. Permission vendingsalelogger.notify lets individual players receive a private chat confirmation instead of relying on the broadcast. The hook always returns null so the machine completes the delivery untouched; the plugin only observes. Language messages keep the output translatable and the config controls the threshold.

Common use cases

  • Audit trail for shop activity

    Record who bought what from which machine and persist it to a data file so admins can review trades after a dispute or a wipe.

  • Announce rare purchases

    Broadcast to the server when someone buys an expensive or limited item, which drives attention to player-run shops.

  • Restrict sales to permitted buyers

    Return a non-null value when the buyer lacks a permission, blocking the hand-off; combine with your own refund logic if you do this.

Pitfalls

  • Blocking does not refund

    Returning non-null stops the item hand-off but the purchase currency has already been handled by the shop flow, so any block should be paired with a deliberate refund or admin notice.

  • Item may still be split later

    The vanilla code splits large purchases into stackable chunks after this hook, so soldItem.amount is the whole amount, not necessarily one stack.

  • Player-run machines are everywhere

    This hook fires for every vending machine on the map, including hundreds of player shops, so keep the handler cheap and avoid chat floods or heavy allocations.

Build a plugin with OnGiveSoldItem

Describe what should happen and get a compiled plugin that uses this hook.

Prompt: Audit trail for shop activity using the OnGiveSoldItem hook, with a config file and a permission
Generate this plugin

Edit the prompt on the next screen. 2 free plugins a month, no card.

Last updated Oct 26, 2026. Found a mistake? Email support@rustplugingenerator.com.

Build a plugin with OnGiveSoldItem

Generate