/// <summary> /// query balance /// </summary> /// <param name="addresses"></param> /// <param name="assetId"></param> /// <param name="snapshot"></param> /// <returns></returns> public static List <BigDecimal> GetBalanceOf(this IEnumerable <UInt160> addresses, UInt160 assetId, StoreView snapshot) { var assetInfo = AssetCache.GetAssetInfo(assetId, snapshot); if (assetInfo == null) { throw new ArgumentException($"invalid assetId:[{assetId}]"); } using var sb = new ScriptBuilder(); foreach (var address in addresses) { sb.EmitAppCall(assetId, "balanceOf", address); } using ApplicationEngine engine = ApplicationEngine.Run(sb.ToArray(), snapshot, testMode: true); if (engine.State.HasFlag(VMState.FAULT)) { throw new Exception($"query balance error"); } var result = engine.ResultStack.Select(p => p.GetBigInteger()); return(result.Select(bigInt => new BigDecimal(bigInt, assetInfo.Decimals)).ToList()); }
/// <summary> /// /// </summary> /// <returns></returns> public async Task <object> GetAsset(UInt160 asset) { var assetInfo = AssetCache.GetAssetInfo(asset); if (assetInfo == null) { return(null); } var totalSupply = AssetCache.GetTotalSupply(asset); using var db = new TrackDB(); var record = db.GetContract(asset); var trans = db.QueryTransactions(new TransactionFilter() { Contracts = new List <UInt160>() { asset }, PageSize = 0 }); return(new AssetInfoModel() { Asset = assetInfo.Asset, Decimals = assetInfo.Decimals, Name = assetInfo.Name, Symbol = assetInfo.Symbol, TotalSupply = totalSupply, CreateTime = record?.CreateTime, TransactionCount = trans.TotalCount, }); }
private (TransferRequestModel, string) ParseTransferRequest(TransferRequest request) { var model = new TransferRequestModel() { Receiver = request.Receiver, Sender = request.Sender, }; model.Asset = ConvertToAssetId(request.Asset, out var convertError); if (model.Asset == null) { return(null, $"asset is not valid:{convertError}"); } var assetInfo = AssetCache.GetAssetInfo(model.Asset); if (assetInfo == null) { return(null, $"asset is not valid:{convertError}"); } if (!BigDecimal.TryParse(request.Amount, assetInfo.Decimals, out BigDecimal sendAmount) || sendAmount.Sign <= 0) { return(null, "Incorrect Amount Format"); } model.Amount = sendAmount; return(model, null); }
/// <summary> /// query balance /// </summary> /// <param name="addresses"></param> /// <param name="assetId"></param> /// <param name="snapshot"></param> /// <returns></returns> public static List<BigDecimal> GetBalanceOf(this IEnumerable<UInt160> addresses, UInt160 assetId, DataCache snapshot) { var assetInfo = AssetCache.GetAssetInfo(assetId, snapshot); if (assetInfo == null) { throw new ArgumentException($"invalid assetId:[{assetId}]"); } if (assetInfo.Asset == NativeContract.NEO.Hash) { return GetNativeBalanceOf(addresses, NativeContract.NEO, snapshot); } if (assetInfo.Asset == NativeContract.GAS.Hash) { return GetNativeBalanceOf(addresses, NativeContract.GAS, snapshot); } using var sb = new ScriptBuilder(); foreach (var address in addresses) { sb.EmitDynamicCall(assetId, "balanceOf", address); } using ApplicationEngine engine = sb.ToArray().RunTestMode(snapshot); if (engine.State.HasFlag(VMState.FAULT)) { throw new Exception($"query balance error"); } var result = engine.ResultStack.Select(p => p.GetInteger()); return result.Select(bigInt => new BigDecimal(bigInt, assetInfo.Decimals)).ToList(); }
private static void UpdateConsensusGas() { if (Blockchain.Singleton.Height > 0) { using var snapshot = Blockchain.Singleton.GetSnapshot(); var validators = NativeContract.NEO.GetValidators(snapshot); var addresses = new List <UInt160>(); foreach (var ecPoint in validators) { if (!_consensusMap.ContainsKey(ecPoint)) { _consensusMap[ecPoint] = ecPoint.ToVerificationContract().ScriptHash; } addresses.Add(_consensusMap[ecPoint]); } using var db = new TrackDB(); var gas = AssetCache.GetAssetInfo(NativeContract.GAS.Hash); var balances = addresses.GetBalanceOf(NativeContract.GAS.Hash, snapshot); for (var index = 0; index < addresses.Count; index++) { var address = addresses[index]; db.UpdateBalance(address, gas, balances[index].Value, snapshot.Height); } db.Commit(); } }
/// <summary> /// try to find "Transfer" event, then add record to db /// </summary> /// <param name="notification"></param> /// <param name="transaction"></param> /// <param name="block"></param> /// <param name="snapshot"></param> /// <returns></returns> private bool HasTransfer(NotificationInfo notification, Transaction transaction, Block block, SnapshotView snapshot) { var assetHash = UInt160.Parse(notification.Contract); var asset = AssetCache.GetAssetInfo(assetHash, snapshot); if (asset == null) { //not nep5 asset return(false); } var notify = JStackItem.FromJson(notification.State); if (!(notify.Value is IList <JStackItem> notifyArray) || notifyArray.Count < 4) { return(false); } if (!"transfer".Equals(notifyArray[0].ValueString, StringComparison.OrdinalIgnoreCase)) { return(false); } var from = notifyArray[1].Value as byte[]; var to = notifyArray[2].Value as byte[]; if (from == null && to == null) { return(false); } if (!ConvertBigInteger(notifyArray[3], out var amount)) { return(false); } var record = new TransferInfo { BlockHeight = block.Index, From = from == null ? null : new UInt160(from), To = to == null ? null : new UInt160(to), Asset = asset.Asset, Amount = amount, TxId = transaction.Hash, TimeStamp = block.Timestamp, AssetInfo = asset, }; _db.AddTransfer(record); if (record.From != null) { var fromBalance = record.From.GetBalanceOf(assetHash, snapshot); _db.UpdateBalance(record.From, asset, fromBalance.Value, snapshot.Height); } if (record.To != null && record.To != record.From) { var toBalance = record.To.GetBalanceOf(assetHash, snapshot); _db.UpdateBalance(record.To, asset, toBalance.Value, snapshot.Height); } return(true); }
/// <summary> /// convert bigint to asset decimal value /// </summary> /// <param name="amount"></param> /// <param name="assetId"></param> /// <returns></returns> public static (BigDecimal amount, AssetInfo asset) GetAssetAmount(this BigInteger amount, UInt160 assetId) { var asset = AssetCache.GetAssetInfo(assetId); if (asset == null) { return (new BigDecimal(BigInteger.Zero, 0), null); } return (new BigDecimal(amount, asset.Decimals), asset); }
/// <summary> /// query balance /// </summary> /// <param name="address"></param> /// <param name="assetId"></param> /// <param name="snapshot"></param> /// <returns></returns> public static BigDecimal GetBalanceOf(this UInt160 address, UInt160 assetId, DataCache snapshot) { var assetInfo = AssetCache.GetAssetInfo(assetId, snapshot); if (assetInfo == null) { return new BigDecimal(BigInteger.Zero, 0); } using var sb = new ScriptBuilder(); sb.EmitDynamicCall(assetId, "balanceOf", address); using var engine = sb.ToArray().RunTestMode(snapshot); if (engine.State.HasFlag(VMState.FAULT)) { return new BigDecimal(BigInteger.Zero, 0); } var balances = engine.ResultStack.Pop().GetInteger(); return new BigDecimal(balances, assetInfo.Decimals); }
/// <summary> /// query balance /// </summary> /// <param name="address"></param> /// <param name="assetId"></param> /// <param name="snapshot"></param> /// <returns></returns> public static BigDecimal GetBalanceOf(this UInt160 address, UInt160 assetId, StoreView snapshot) { var assetInfo = AssetCache.GetAssetInfo(assetId, snapshot); if (assetInfo == null) { return(new BigDecimal(0, 0)); } using var sb = new ScriptBuilder(); sb.EmitAppCall(assetId, "balanceOf", address); using var engine = ApplicationEngine.Run(sb.ToArray(), snapshot, testMode: true); if (engine.State.HasFlag(VMState.FAULT)) { return(new BigDecimal(0, 0)); } var balances = engine.ResultStack.Pop().GetBigInteger(); return(new BigDecimal(balances, assetInfo.Decimals)); }
private void ProcessTransfer(NotifyEventArgs notification, Blockchain.ApplicationExecuted appExec) { var transfer = notification.ConvertToTransfer(); // HasTransfer(notification, transaction); if (transfer == null) { return; } var asset = AssetCache.GetAssetInfo(transfer.Asset, _snapshot); if (asset == null) { return; } if (transfer.From != null) { Result.BalanceChangeAccounts.Add(new AccountAsset(transfer.From, transfer.Asset)); } if (transfer.To != null) { Result.BalanceChangeAccounts.Add(new AccountAsset(transfer.To, transfer.Asset)); } if (appExec.Trigger == TriggerType.Application) { var transferStorageItem = new TransferStorageItem() { From = transfer.From, To = transfer.To, Asset = transfer.Asset, Amount = transfer.Amount, TxId = appExec?.Transaction?.Hash, Trigger = appExec.Trigger, }; Result.Transfers.Add(transferStorageItem); } }
/// <summary> /// send asset /// </summary> /// <param name="sender"></param> /// <param name="receivers"></param> /// <param name="asset"></param> /// <returns></returns> public async Task <object> SendToMultiAddress(MultiReceiverRequest[] receivers, string asset = "neo", UInt160 sender = null) { if (CurrentWallet == null) { return(Error(ErrorCode.WalletNotOpen)); } if (receivers.IsEmpty()) { return(Error(ErrorCode.ParameterIsNull, $"receivers is null!")); } UInt160 assetHash = ConvertToAssetId(asset, out var convertError); if (assetHash == null) { return(Error(ErrorCode.InvalidPara, $"asset is not valid:{convertError}")); } var assetInfo = AssetCache.GetAssetInfo(assetHash); if (assetInfo == null) { return(Error(ErrorCode.InvalidPara, $"asset is not valid:{convertError}")); } if (sender != null) { var account = CurrentWallet.GetAccount(sender); if (account == null) { return(Error(ErrorCode.AddressNotFound)); } } var toes = new List <(UInt160 scriptHash, BigDecimal amount)>(); foreach (var receiver in receivers) { if (!BigDecimal.TryParse(receiver.Amount, assetInfo.Decimals, out BigDecimal sendAmount) || sendAmount.Sign <= 0) { return(Error(ErrorCode.InvalidPara, $"Incorrect Amount Format:{receiver.Amount}")); } toes.Add((receiver.Address, sendAmount)); } var outputs = toes.Select(t => new TransferOutput() { AssetId = assetHash, Value = t.amount, ScriptHash = t.scriptHash, }).ToArray(); try { Transaction tx = CurrentWallet.MakeTransaction(Helpers.GetDefaultSnapshot(), outputs, sender); if (tx == null) { return(Error(ErrorCode.BalanceNotEnough, "Insufficient funds")); } var(signSuccess, context) = CurrentWallet.TrySignTx(tx); if (!signSuccess) { return(Error(ErrorCode.SignFail, context.SafeSerialize())); } await tx.Broadcast(); return(new TransactionModel(tx)); } catch (Exception ex) { if (ex.Message.Contains("Insufficient GAS")) { return(Error(ErrorCode.GasNotEnough)); } return(Error(ErrorCode.TransferError, ex.Message)); } }
/// <summary> /// send asset /// </summary> /// <param name="sender"></param> /// <param name="receiver"></param> /// <param name="amount"></param> /// <param name="asset"></param> /// <returns></returns> public async Task <object> SendToAddress(UInt160 receiver, string amount, string asset = "neo", UInt160 sender = null) { if (CurrentWallet == null) { return(Error(ErrorCode.WalletNotOpen)); } if (receiver == null) { return(Error(ErrorCode.ParameterIsNull, $"receiver address is null!")); } UInt160 assetHash = ConvertToAssetId(asset, out var convertError); if (assetHash == null) { return(Error(ErrorCode.InvalidPara, $"asset is not valid:{convertError}")); } var assetInfo = AssetCache.GetAssetInfo(assetHash); if (assetInfo == null) { return(Error(ErrorCode.InvalidPara, $"asset is not valid:{convertError}")); } if (!BigDecimal.TryParse(amount, assetInfo.Decimals, out BigDecimal sendAmount) || sendAmount.Sign <= 0) { return(Error(ErrorCode.InvalidPara, "Incorrect Amount Format")); } if (sender != null) { var account = CurrentWallet.GetAccount(sender); if (account == null) { return(Error(ErrorCode.AddressNotFound)); } var balance = sender.GetBalanceOf(assetHash); if (balance.Value < sendAmount.Value) { return(Error(ErrorCode.BalanceNotEnough)); } } try { Transaction tx = CurrentWallet.MakeTransaction(Helpers.GetDefaultSnapshot(), new[] { new TransferOutput { AssetId = assetHash, Value = sendAmount, ScriptHash = receiver } }, sender); if (tx == null) { return(Error(ErrorCode.BalanceNotEnough, "Insufficient funds")); } var(signSuccess, context) = CurrentWallet.TrySignTx(tx); if (!signSuccess) { return(Error(ErrorCode.SignFail, context.SafeSerialize())); } await tx.Broadcast(); return(new TransactionModel(tx)); } catch (Exception ex) { if (ex.Message.Contains("Insufficient GAS")) { return(Error(ErrorCode.GasNotEnough)); } return(Error(ErrorCode.TransferError, ex.Message)); } }
private void AnalysisAppExecuteResult(Blockchain.ApplicationExecuted appExec) { var execResult = new ExecuteResultInfo(); Transaction transaction = appExec.Transaction; if (transaction != null) { //fee account Result.BalanceChangeAccounts.Add(new AccountAsset(transaction.Sender, NativeContract.GAS.Hash)); execResult.TxId = transaction.Hash; } execResult.Trigger = appExec.Trigger; execResult.VMState = appExec.VMState; execResult.GasConsumed = appExec.GasConsumed; try { execResult.ResultStack = appExec.Stack.Select(q => q.ToContractParameter().ToJson()).ToArray(); } catch (InvalidOperationException) { execResult.ResultStack = "error: recursive reference"; } execResult.Notifications = appExec.Notifications.Select(n => n.ToNotificationInfo()).ToList(); Result.ExecuteResultInfos.Add(execResult); foreach (var contract in execResult.Notifications.Select(n => n.Contract).Distinct()) { var asset = AssetCache.GetAssetInfo(contract, _snapshot); if (asset != null) { Result.AssetInfos[asset.Asset] = asset; } } if (execResult.VMState.HasFlag(VMState.FAULT)) { //no need to track return; } if (execResult.Notifications.IsEmpty()) { //no need to track return; } foreach (var notification in appExec.Notifications) { switch (notification.EventName) { case "transfer": case "Transfer": ProcessTransfer(notification, appExec); break; case "Deploy": ProcessDeploy(notification, appExec); break; case "Destory": ProcessDestory(notification, appExec); break; default: break; } } }