/// <summary> /// Get any information required to construct a transaction for a specific network. Metadata returned here could be a recent hash to use, an account sequence number, or even arbitrary chain state. /// The request used when calling this endpoint is often created by calling `/construction/preprocess` in an offline environment. /// It is important to clarify that this endpoint should not pre-construct any transactions for the client (this should happen in `/construction/payloads`). /// </summary> /// <returns></returns> public JObject ConstructionMetadata(ConstructionMetadataRequest request) { // check params if (request.Options is null || request.Options.Pairs is null || !request.Options.ContainsKey("tx_type")) { return(Error.PARAMETER_INVALID.ToJson()); } if (!request.Options.TryGetValue("tx_type", out JObject txType)) { return(Error.PARAMETER_INVALID.ToJson()); } Metadata metadata; try { switch (txType.AsString()) { case nameof(ClaimTransaction): { JObject json = new JObject(); json["type"] = "CoinReference[]"; json["required"] = "true"; json["reference"] = @"https://github.com/neo-project/neo/blob/master-2.x/neo/Network/P2P/Payloads/CoinReference.cs"; json["example"] = "[{\"txid\": \"0xe1e44f41a1f0854063ccdc9beb7537fc40565575e0ae2366b4a93a73c18b6166\", \"vout\": 2},{\"txid\": \"0x69fd452c92fb0e5861b27588549a8e55d3f9fee542884ae317600508bbacedbb\",\"vout\": 2}]"; metadata = new Metadata(new Dictionary <string, JObject> { { "tx_type", nameof(ClaimTransaction) }, { "claims", json } }); break; } case nameof(ContractTransaction): { metadata = new Metadata(new Dictionary <string, JObject> { { "tx_type", nameof(ContractTransaction) } }); break; } case nameof(InvocationTransaction): { JObject script = new JObject(); script["type"] = "byte[] (represented in hex string)"; script["required"] = "true"; script["example"] = "00046e616d656724058e5e1b6008847cd662728549088a9ee82191"; JObject gas = new JObject(); gas["type"] = "Fixed8"; gas["required"] = "false"; gas["reference"] = @"https://github.com/neo-project/neo/blob/master-2.x/neo/Fixed8.cs"; gas["example"] = "1234567890"; metadata = new Metadata(new Dictionary <string, JObject> { { "tx_type", nameof(InvocationTransaction) }, { "script", script }, { "gas", gas } }); break; } case nameof(StateTransaction): { JObject json = new JObject(); json["type"] = "StateDescriptor[]"; json["required"] = "true"; json["reference"] = @"https://docs.neo.org/tutorial/zh-cn/3-transactions/3-NEO_transaction_types.html#statetransaction"; json["example"] = "[{\"type\":\"Account\",\"key\":\"ce47912d51b7ed8a24a77f97f2cb6ef9a0e0bf0d\",\"field\":\"Votes\",\"value\":\"04024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba55402df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093\"}]"; metadata = new Metadata(new Dictionary <string, JObject> { { "tx_type", nameof(StateTransaction) }, { "descriptors", json } }); break; } default: throw new NotSupportedException(); } } catch (Exception) { return(Error.PARAMETER_INVALID.ToJson()); } ConstructionMetadataResponse response = new ConstructionMetadataResponse(metadata); return(response.ToJson()); }
/// <summary> /// Get any information required to construct a transaction for a specific network. /// Metadata returned here could be a recent hash to use, an account sequence number, or even arbitrary chain state. /// The request used when calling this endpoint is often created by calling `/construction/preprocess` in an offline environment. /// You should NEVER assume that the request sent to this endpoint will be created by the caller or populated with any custom parameters. This must occur in /construction/preprocess. /// It is important to clarify that this endpoint should not pre-construct any transactions for the client (this should happen in `/construction/payloads`). /// This endpoint is left purposely unstructured because of the wide scope of metadata that could be required. /// </summary> /// <returns></returns> public JObject ConstructionMetadata(ConstructionMetadataRequest request) { if (request.NetworkIdentifier?.Blockchain?.ToLower() != "neo n3") { return(Error.NETWORK_IDENTIFIER_INVALID.ToJson()); } if (request.NetworkIdentifier?.Network?.ToLower() != network) { return(Error.NETWORK_IDENTIFIER_INVALID.ToJson()); } // get signers Signer[] signers = Array.Empty <Signer>(); try { signers = (request.Options["signers"] as JArray).Select(p => p.ToSigner(system.Settings.AddressVersion)).ToArray(); } catch (Exception) { return(Error.PARAMETER_INVALID.ToJson()); } // get public keys, need public keys to calculate network fee if (!request.Options.ContainsKey("signer_metadata")) { return(Error.PARAMETER_INVALID.ToJson()); } SignerMetadata[] signerMetadatas = (request.Options["signer_metadata"] as JArray).Select(p => SignerMetadata.FromJson(p, system.Settings.AddressVersion)).ToArray(); if (request.PublicKeys == null) { return(Error.PARAMETER_INVALID.ToJson()); } // get script byte[] script = Array.Empty <byte>(); if (request.Options.ContainsKey("has_existing_script") && request.Options["has_existing_script"].AsBoolean() == true) { if (request.Options.ContainsKey("script")) { script = Convert.FromBase64String(request.Options["script"].AsString()); } else { return(Error.PARAMETER_INVALID.ToJson()); } } else if (request.Options.ContainsKey("has_existing_script") && request.Options["has_existing_script"].AsBoolean() == false) { if (request.Options.ContainsKey("operations_script")) { script = Convert.FromBase64String(request.Options["operations_script"].AsString()); } else { return(Error.PARAMETER_INVALID.ToJson()); } } else { return(Error.PARAMETER_INVALID.ToJson()); } // get max fee long maxFee = 2000000000; if (request.Options.ContainsKey("max_fee")) { maxFee = long.Parse(request.Options["max_fee"].AsString()); } // invoke script NeoTransaction tx = new NeoTransaction { Signers = signers, Attributes = Array.Empty <TransactionAttribute>(), }; using ApplicationEngine engine = ApplicationEngine.Run(script, system.StoreView, container: tx, settings: system.Settings, gas: maxFee); if (engine.State == VMState.FAULT) { return(Error.VM_FAULT.ToJson()); } // script tx.Script = script; // version var version = (byte)0; tx.Version = version; // nonce Random random = new(); var nonce = (uint)random.Next(); tx.Nonce = nonce; // system fee var sysFee = engine.GasConsumed; tx.SystemFee = sysFee; // network fee is not available here, will be calculated in var netFee = CalculateNetworkFee(system.StoreView, tx, request.PublicKeys, signerMetadatas); // valid until block var validUntilBlock = NativeContract.Ledger.CurrentIndex(system.StoreView) + system.Settings.MaxValidUntilBlockIncrement; Metadata metadata = new(new() { { "version", version }, { "nonce", nonce },