public void BuyToken(Address from, string symbol, BigInteger tokenID)
        {
            Runtime.Expect(Runtime.IsWitness(from), "invalid witness");

            var auctionID = symbol + "." + tokenID;

            Runtime.Expect(_auctionMap.ContainsKey <string>(auctionID), "invalid auction");
            var auction = _auctionMap.Get <string, MarketAuction>(auctionID);

            Runtime.Expect(Runtime.TokenExists(auction.BaseSymbol), "invalid base token");
            var baseToken = Runtime.GetToken(auction.BaseSymbol);

            Runtime.Expect(!baseToken.Flags.HasFlag(TokenFlags.Fungible), "token must be non-fungible");

            var nft = Runtime.ReadToken(symbol, tokenID);

            Runtime.Expect(nft.CurrentChain == Runtime.Chain.Name, "token not currently in this chain");
            Runtime.Expect(nft.CurrentOwner == this.Address, "invalid owner");

            if (auction.Creator != from)
            {
                Runtime.Expect(Runtime.TokenExists(auction.QuoteSymbol), "invalid quote token");
                var quoteToken = Runtime.GetToken(auction.QuoteSymbol);
                Runtime.Expect(quoteToken.Flags.HasFlag(TokenFlags.Fungible), "quote token must be fungible");

                var balance = Runtime.GetBalance(quoteToken.Symbol, from);
                Runtime.Expect(balance >= auction.Price, "not enough balance");

                Runtime.Expect(Runtime.TransferTokens(quoteToken.Symbol, from, auction.Creator, auction.Price), "payment failed");
            }

            Runtime.Expect(Runtime.TransferToken(baseToken.Symbol, this.Address, from, auction.TokenID), "transfer failed");

            _auctionMap.Remove <string>(auctionID);
            _auctionIDs.Remove(auctionID);

            if (auction.Creator == from)
            {
                Runtime.Notify(EventKind.OrderCancelled, from, new MarketEventData()
                {
                    ID = auction.TokenID, BaseSymbol = auction.BaseSymbol, QuoteSymbol = auction.QuoteSymbol, Price = 0
                });
            }
            else
            {
                Runtime.Notify(EventKind.TokenSend, from, new TokenEventData()
                {
                    chainAddress = this.Address, symbol = auction.QuoteSymbol, value = auction.Price
                });
                Runtime.Notify(EventKind.TokenReceive, auction.Creator, new TokenEventData()
                {
                    chainAddress = this.Address, symbol = auction.QuoteSymbol, value = auction.Price
                });

                Runtime.Notify(EventKind.OrderFilled, from, new MarketEventData()
                {
                    ID = auction.TokenID, BaseSymbol = auction.BaseSymbol, QuoteSymbol = auction.QuoteSymbol, Price = auction.Price
                });
            }

            Runtime.Notify(EventKind.TokenReceive, from, new TokenEventData()
            {
                chainAddress = this.Address, symbol = auction.BaseSymbol, value = auction.TokenID
            });
        }