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.
object OnGiveSoldItem(VendingMachine vendingMachine, Item soldItem, BasePlayer buyer)Parameters
| Parameter | Type | Description |
|---|---|---|
vendingMachine | VendingMachine | The 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. |
soldItem | Item | The 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. |
buyer | BasePlayer | The 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
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.amountis 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.
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