Show / Hide
Table of Contents
Last modified: Wed Jun 12 17:33:26 2024 +0800
Transaction Construction
RpcClient
encapsulates the transaction construction module, which allows you to construct transactions in Neo N3 with specific parameters and methods to personalize your functions. This document introduces the relevant methods.
If you use SDK to construct a transaction that requires a signature, you need to ensure that the RpcClient object and the network it is connected to are configured the same way, or the transaction constructed by the SDK will not be validated in the blockchain. To do so, load Neo-CLI config.json when constructing the RpcClient object, for example:
RpcClient client = new RpcClient(new Uri(" http://localhost:20332"), null, null, ProtocolSettings.Load("config.json" ))
Transaction construction process
Construct a transaction script to determine what functions the transaction will perform, such as a transfer transaction:
Show all Copy // construct the script, in this example, we will transfer 1 NEO to the receiver
UInt160 scriptHash = NativeContract.NEO.Hash;
byte[] script = scriptHash.MakeScript("transfer", sender, receiver, 1,"data");
Construct TransactionManagerFactory
with the parameter RpcClient
; Construct TransactionManager
with the parameters Script
and Signers
:
Show all Copy TransactionManager txManager = await new TransactionManagerFactory(client)
.MakeTransactionAsync(script, signers).ConfigureAwait(false);
Add signature (single or multiple signatures) and use KeyPair
of the account as the parameter.
Show all Copy // add signature for the transaction with sendKey
txManager.AddSignature(sendKey);
Show all Copy // add multi-signatures for the transaction with sendKey
txManager.AddMultiSig(key1, 2, receiverKey.PublicKey, key2.PublicKey, key3.PublicKey);
txManager.AddMultiSig(key2, 2, receiverKey.PublicKey, key2.PublicKey, key3.PublicKey);
Show all Copy // create a multi-signature contract, which needs at least 2 of 3 KeyPairs to sign
Contract multiContract = Contract.CreateMultiSigContract(2, sendKey.PublicKey, key2.PublicKey, key3.PublicKey);
// get the scripthash of the multi-signature contract
UInt160 multiAccount = multiContract.Script.ToScriptHash();
Verify signatures and add Witness
to the transaction body.
If there are not enough signatures or fees an exception will be thrown.
Show all Copy // sign the transaction with the added signatures
Transaction tx = await txManager.SignAsync().ConfigureAwait(false);
Transaction Construction Examples
Constructing an NEP17 transfer transaction
The following example implements a function that transfers 1024 NEO from the sender account to the receiver account. You need to pay attention to the difference between the script and the signature in a transaction for constructing different transactions.
Show all Copy using Neo;
using Neo.Network.P2P.Payloads;
using Neo.Network.RPC;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.Wallets;
using System;
using Utility = Neo.Network.RPC.Utility;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
TestNep17Transfer().GetAwaiter().GetResult();
Console.Read();
}
private static async Task TestNep17Transfer()
{
// get the protocol settings of the network
ProtocolSettings protocolSettings = ProtocolSettings.Load("/path/to/config.testnet.json");
// choose a neo node with rpc opened
RpcClient client = new RpcClient(new Uri("http://127.0.0.1:10332"), null, null, protocolSettings);
// get the KeyPair of your account, which will pay the system and network fee
KeyPair sendKey = Utility.GetKeyPair("L53tg72Az8QhYUAyyqTQ3LaXMXBE3S9mJGGZVKHBryZxya7prwhZ");
UInt160 sender = Contract.CreateSignatureContract(sendKey.PublicKey).ScriptHash;
// add Signers, which is a collection of scripthashs that need to be signed
Signer[] cosigners = new[] { new Signer { Scopes = WitnessScope.CalledByEntry, Account = sender } };
// get the scripthash of the account you want to transfer to
UInt160 receiver = Utility.GetScriptHash("NirHUAteaMr6CqWuAAMaEUScPcS3FDKebM", protocolSettings);
// construct the script, in this example, we will transfer 1024 NEO to receiver
UInt160 scriptHash = NativeContract.NEO.Hash;
byte[] script = scriptHash.MakeScript("transfer", sender, receiver, 1024, null);
// initialize the TransactionManagerFactory with rpc client and magic
// fill in the TransactionManager with the script and cosigners
TransactionManager txManager = await new TransactionManagerFactory(client)
.MakeTransactionAsync(script, cosigners).ConfigureAwait(false);
// add signature and sign transaction with the added signature
Transaction tx = await txManager.AddSignature(sendKey).SignAsync().ConfigureAwait(false);
// broadcasts the transaction over the Neo network.
await client.SendRawTransactionAsync(tx).ConfigureAwait(false);
Console.WriteLine($"Transaction {tx.Hash.ToString()} is broadcasted!");
// print a message after the transaction is on chain
WalletAPI neoAPI = new WalletAPI(client);
await neoAPI.WaitTransactionAsync(tx)
.ContinueWith(async (p) => Console.WriteLine($"Transaction vm state is {(await p).VMState}"));
}
}
}
WalletAPI
encapsulates the above process, so you can simplify the NEP17 transfer as follows:
Show all Copy using Neo;
using Neo.Network.P2P.Payloads;
using Neo.Network.RPC;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.Wallets;
using System;
using Utility = Neo.Network.RPC.Utility;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
TestNep17Transfer().GetAwaiter().GetResult();
Console.Read();
}
private static async Task TestNep17Transfer()
{
// get the protocol settings of the network
ProtocolSettings protocolSettings = ProtocolSettings.Load("/path/to/config.testnet.json");
// choose a neo node with rpc opened
RpcClient client = new RpcClient(new Uri("http://127.0.0.1:10332"), null, null, protocolSettings);
// get the KeyPair of your account, which will pay the system and network fee
KeyPair sendKey = Utility.GetKeyPair("L53tg72Az8QhYUAyyqTQ3LaXMXBE3S9mJGGZVKHBryZxya7prwhZ");
// get the scripthash of the account you want to transfer to
UInt160 receiver = Utility.GetScriptHash("NirHUAteaMr6CqWuAAMaEUScPcS3FDKebM", protocolSettings);
// use WalletAPI to create and send the transfer transaction
WalletAPI walletAPI = new WalletAPI(client);
Transaction tx = await walletAPI.TransferAsync(NativeContract.NEO.Hash, sendKey, receiver, 1024, null).ConfigureAwait(false);
// print a message after the transaction is on chain
WalletAPI neoAPI = new WalletAPI(client);
await neoAPI.WaitTransactionAsync(tx)
.ContinueWith(async (p) => Console.WriteLine($"Transaction vm state is {(await p).VMState}"));
}
}
}
Constructing a transaction to transfer to multi-signature account
The following example implements a function that transfers 10 GAS to a multi-signature account. The scripthash of a multi-signature account is obtained from the scripthash of the multi-signature contract. As the sender is a normal account, the process of adding a signature is the same as last example.
Show all Copy using Neo;
using Neo.Cryptography.ECC;
using Neo.Network.P2P.Payloads;
using Neo.Network.RPC;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.Wallets;
using System;
using Utility = Neo.Network.RPC.Utility;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
TestToMultiTransfer().GetAwaiter().GetResult();
Console.Read();
}
private static async Task TestToMultiTransfer()
{
// get the protocol settings of the network
ProtocolSettings protocolSettings = ProtocolSettings.Load("/path/to/config.testnet.json");
// choose a neo node with rpc opened
RpcClient client = new RpcClient(new Uri("http://127.0.0.1:10332"), null, null, protocolSettings);
// get the KeyPair of your account, which will pay the system and network fee
KeyPair sendKey = Utility.GetKeyPair("L53tg72Az8QhYUAyyqTQ3LaXMXBE3S9mJGGZVKHBryZxya7prwhZ");
UInt160 sender = Contract.CreateSignatureContract(sendKey.PublicKey).ScriptHash;
// get the KeyPair of your accounts
KeyPair key2 = Utility.GetKeyPair("L1bQBbZWnKbPkpHM3jXWD3E5NwK7nui2eWHYXVZPy3t8jSFF1Qj3");
KeyPair key3 = Utility.GetKeyPair("KwrJfYyc7KWfZG5h97SYfcCQyW4jRw1njmHo48kZhZmuQWeTtUHM");
// add the KeyPairs to IReadOnlyCollection<ECPoint>
IReadOnlyCollection<ECPoint> keys = new List<ECPoint>()
{
sendKey.PublicKey, key2.PublicKey, key3.PublicKey
};
// create multi-signatures contract, this contract needs at least 2 of 3 KeyPairs to sign
Contract multiContract = Contract.CreateMultiSigContract(2, keys);
// get the scripthash of the multi-signature Contract
UInt160 multiAccount = multiContract.Script.ToScriptHash();
// construct the script, in this example, we will transfer 1024 GAS to multi-sign account
// in contract parameter, the amount type is BigInteger, so we need to muliply the contract factor
UInt160 scriptHash = NativeContract.GAS.Hash;
byte[] script = scriptHash.MakeScript("transfer", sender, multiAccount, 1024 * NativeContract.GAS.Factor, null);
// add Signers, which is a collection of scripthashs that need to be signed
Signer[] cosigners = new[] { new Signer { Scopes = WitnessScope.CalledByEntry, Account = sender } };
// initialize the TransactionManager with rpc client and magic
// fill the script and cosigners
TransactionManager txManager = await new TransactionManagerFactory(client)
.MakeTransactionAsync(script, cosigners).ConfigureAwait(false);
// add signature and sign transaction with the added signature
Transaction tx = await txManager.AddSignature(sendKey).SignAsync().ConfigureAwait(false);
// broadcasts the transaction over the Neo network.
await client.SendRawTransactionAsync(tx).ConfigureAwait(false);
Console.WriteLine($"Transaction {tx.Hash.ToString()} is broadcasted!");
// print a message after the transaction is on chain
WalletAPI neoAPI = new WalletAPI(client);
await neoAPI.WaitTransactionAsync(tx)
.ContinueWith(async (p) => Console.WriteLine($"Transaction vm state is {(await p).VMState}"));
}
}
}
Constructing a transaction to transfer from multi-signature account
The following example implements a function that transfers 1024 GAS from a multi-signature account. The scripthash of the multi-signature account is obtained from the scripthash of the multi-signature contract. To transfer assets from a multi-signature account, you need to add signatures required by the multi-signature contract.
Show all Copy using Neo;
using Neo.Cryptography.ECC;
using Neo.Network.P2P.Payloads;
using Neo.Network.RPC;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.Wallets;
using System;
using Utility = Neo.Network.RPC.Utility;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
TestFromMultiTransfer().GetAwaiter().GetResult();
Console.Read();
}
private static async Task TestFromMultiTransfer()
{
// get the protocol settings of the network
ProtocolSettings protocolSettings = ProtocolSettings.Load("/path/to/config.testnet.json");
// choose a neo node with rpc opened
RpcClient client = new RpcClient(new Uri("http://127.0.0.1:10332"), null, null, protocolSettings);
// get the KeyPair of your account
KeyPair receiverKey = Utility.GetKeyPair("L53tg72Az8QhYUAyyqTQ3LaXMXBE3S9mJGGZVKHBryZxya7prwhZ");
KeyPair key2 = Utility.GetKeyPair("L1bQBbZWnKbPkpHM3jXWD3E5NwK7nui2eWHYXVZPy3t8jSFF1Qj3");
KeyPair key3 = Utility.GetKeyPair("KwrJfYyc7KWfZG5h97SYfcCQyW4jRw1njmHo48kZhZmuQWeTtUHM");
// add the KeyPairs to IReadOnlyCollection<ECPoint>
IReadOnlyCollection<ECPoint> keys = new List<ECPoint>()
{
receiverKey.PublicKey, key2.PublicKey, key3.PublicKey
};
// create multi-signature contract, this contract needs at least 2 of 3 KeyPairs to sign
Contract multiContract = Contract.CreateMultiSigContract(2, keys);
// get the scripthash of the multi-signature Contract
UInt160 multiAccount = multiContract.Script.ToScriptHash();
UInt160 receiver = Contract.CreateSignatureContract(receiverKey.PublicKey).ScriptHash;
// construct the script, in this example, we will transfer 1024 GAS to multi-sign account
// in contract parameter, the amount type is BigInteger, so we need to muliply the contract factor
UInt160 scriptHash = NativeContract.GAS.Hash;
byte[] script = scriptHash.MakeScript("transfer", multiAccount, receiver, 1024 * NativeContract.GAS.Factor, null);
// add Signers, which is a collection of scripthashs that need to be signed
Signer[] cosigners = new[] { new Signer { Scopes = WitnessScope.CalledByEntry, Account = multiAccount } };
// initialize the TransactionManager with rpc client and magic
// fill the script and cosigners
TransactionManager txManager = await new TransactionManagerFactory(client)
.MakeTransactionAsync(script, cosigners).ConfigureAwait(false);
// add signature and sign transaction with the added signature
Transaction tx = await txManager.AddMultiSig(new KeyPair[] { receiverKey, key2 }, 2, receiverKey.PublicKey, key2.PublicKey, key3.PublicKey)
.SignAsync().ConfigureAwait(false);
// broadcasts the transaction over the Neo network.
await client.SendRawTransactionAsync(tx).ConfigureAwait(false);
Console.WriteLine($"Transaction {tx.Hash.ToString()} is broadcasted!");
// print a message after the transaction is on chain
WalletAPI neoAPI = new WalletAPI(client);
await neoAPI.WaitTransactionAsync(tx)
.ContinueWith(async (p) => Console.WriteLine($"Transaction vm state is {(await p).VMState}"));
}
}
}