diff --git a/templates/NftSaleContract/.template.config/template.json b/templates/NftSaleContract/.template.config/template.json
new file mode 100644
index 0000000..2dea2b2
--- /dev/null
+++ b/templates/NftSaleContract/.template.config/template.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "AElf",
+ "classifications": [
+ "AElf/SmartContract"
+ ],
+ "identity": "AElf.Contract.NftSale.Template",
+ "name": "AElf Contract NftSale Template",
+ "shortName": "aelf-nft-sale",
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "sourceName": "NftSale",
+ "symbols": {
+ "NamespacePath": {
+ "type": "parameter",
+ "replaces": "AElf.Contracts.NftSale"
+ }
+ }
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/src/ContractReferences.cs b/templates/NftSaleContract/src/ContractReferences.cs
new file mode 100644
index 0000000..e037d81
--- /dev/null
+++ b/templates/NftSaleContract/src/ContractReferences.cs
@@ -0,0 +1,11 @@
+using AElf.Contracts.Consensus.AEDPoS;
+using AElf.Contracts.MultiToken;
+
+namespace AElf.Contracts.NftSale
+{
+ public partial class NftSaleState
+ {
+ internal TokenContractContainer.TokenContractReferenceState TokenContract { get; set; }
+ internal AEDPoSContractContainer.AEDPoSContractReferenceState ConsensusContract { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/src/NftSale.cs b/templates/NftSaleContract/src/NftSale.cs
new file mode 100644
index 0000000..1c12ed0
--- /dev/null
+++ b/templates/NftSaleContract/src/NftSale.cs
@@ -0,0 +1,159 @@
+using AElf.Contracts.MultiToken;
+using AElf.Sdk.CSharp;
+using AElf.Types;
+using Google.Protobuf.WellKnownTypes;
+
+namespace AElf.Contracts.NftSale
+{
+ // Contract class must inherit the base class generated from the proto file
+ public class NftSale : NftSaleContainer.NftSaleBase
+ {
+ // Initializes the contract
+ public override Empty Initialize(NftPrice input)
+ {
+ // Check if the contract is already initialized
+ Assert(State.Initialized.Value == false, "Already initialized.");
+ // Set the contract state
+ State.Initialized.Value = true;
+ // Set the owner address
+ State.Owner.Value = Context.Sender;
+ State.NftPrice.Value = input.Price;
+
+ // Initialize the token contract
+ State.TokenContract.Value = Context.GetContractAddressByName(SmartContractConstants.TokenContractSystemName);
+ // The below code can be used to replace the above line. The below is a showcase of how you can reference to any contracts.
+ State.ConsensusContract.Value = Context.GetContractAddressByName(SmartContractConstants.ConsensusContractSystemName);
+
+ return new Empty();
+ }
+
+ // transfer nft
+ public override Empty Purchase(PurchaseInput input)
+ {
+ var price = State.NftPrice.Value;
+ // transfer token
+ State.TokenContract.TransferFrom.Send(new TransferFromInput
+ {
+ From = Context.Sender,
+ To = Context.Self,
+ Symbol = price.Symbol,
+ Amount = price.Amount * input.Amount
+ });
+ // transfer nft
+ State.TokenContract.Transfer.Send(new TransferInput
+ {
+ To = Context.Sender,
+ Symbol = input.Symbol,
+ Amount = input.Amount
+ });
+
+ Context.Fire(new SaleNft
+ {
+ To = Context.Self,
+ Symbol = input.Symbol,
+ Amount = input.Amount,
+ });
+
+ return new Empty();
+ }
+
+ //
+ public override Empty SetPriceAndSymbol(NftPrice input)
+ {
+ AssertIsOwner();
+ State.NftPrice.Value = input.Price;
+ return new Empty();
+ }
+
+ public override Price GetPrice(Empty input)
+ {
+ return State.NftPrice.Value;
+ }
+
+
+ // Withdraws a specified amount of tokens from the contract.
+ // This method can only be called by the owner of the contract.
+ // After the tokens are transferred, a WithdrawEvent is fired to notify any listeners about the withdrawal.
+ public override Empty Withdraw(Int64Value input)
+ {
+ AssertIsOwner();
+
+ // Transfer the token from the contract to the sender
+ State.TokenContract.Transfer.Send(new TransferInput
+ {
+ To = Context.Sender,
+ Symbol = State.NftPrice.Value.Symbol,
+ Amount = input.Value
+ });
+
+ // Emit an event to notify listeners about the withdrawal
+ Context.Fire(new WithdrawEvent
+ {
+ Amount = input.Value,
+ From = Context.Self,
+ To = Context.Sender
+ });
+
+ return new Empty();
+ }
+
+ // Deposits a specified amount of tokens into the contract.
+ // This method can only be called by the owner of the contract.
+ // After the tokens are transferred, a DepositEvent is fired to notify any listeners about the deposit.
+ public override Empty Deposit(DepositeInput input)
+ {
+ AssertIsOwner();
+
+ // Transfer the token from the sender to the contract
+ State.TokenContract.TransferFrom.Send(new TransferFromInput
+ {
+ From = Context.Sender,
+ To = Context.Self,
+ Symbol = input.Symbol,
+ Amount = input.Amount
+ });
+
+ // Emit an event to notify listeners about the deposit
+ Context.Fire(new DepositEvent
+ {
+ Amount = input.Amount,
+ From = Context.Sender,
+ To = Context.Self
+ });
+
+ return new Empty();
+ }
+
+
+ // A method that read the contract's current balance
+ public override Int64Value GetContractBalance(GetContractBalanceInput input)
+ {
+ // Get the balance of the contract
+ var balance = State.TokenContract.GetBalance.Call(new GetBalanceInput
+ {
+ Owner = input.Address,
+ Symbol = State.NftPrice.Value.Symbol
+ }).Balance;
+
+ // Wrap the value in the return type
+ return new Int64Value
+ {
+ Value = balance
+ };
+ }
+
+ // This method is used to ensure that only the owner of the contract can perform certain actions.
+ // If the context sender is not the owner, an exception is thrown with the message "Unauthorized to perform the action."
+ private void AssertIsOwner()
+ {
+ Assert(Context.Sender == State.Owner.Value, "Unauthorized to perform the action.");
+ }
+
+ // A method that read the contract's owner
+ public override StringValue GetOwner(Empty input)
+ {
+ return State.Owner.Value == null ? new StringValue() : new StringValue {Value = State.Owner.Value.ToBase58()};
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/src/NftSale.csproj b/templates/NftSaleContract/src/NftSale.csproj
new file mode 100644
index 0000000..072233a
--- /dev/null
+++ b/templates/NftSaleContract/src/NftSale.csproj
@@ -0,0 +1,28 @@
+
+
+ net6.0
+ AElf.Contracts.NftSale
+ true
+ true
+ 1.0.0.0
+
+
+ $(MSBuildProjectDirectory)/$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/NftSaleContract/src/NftSaleState.cs b/templates/NftSaleContract/src/NftSaleState.cs
new file mode 100644
index 0000000..ed77d7a
--- /dev/null
+++ b/templates/NftSaleContract/src/NftSaleState.cs
@@ -0,0 +1,15 @@
+using AElf.Sdk.CSharp.State;
+using AElf.Types;
+
+namespace AElf.Contracts.NftSale
+{
+ // The state class is access the blockchain state
+ public partial class NftSaleState : ContractState
+ {
+ // A state to check if contract is initialized
+ public BoolState Initialized { get; set; }
+ // A state to store the owner address
+ public SingletonState
Owner { get; set; }
+ public SingletonState NftPrice { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/src/Protobuf/contract/nft_sale_contract.proto b/templates/NftSaleContract/src/Protobuf/contract/nft_sale_contract.proto
new file mode 100644
index 0000000..373588e
--- /dev/null
+++ b/templates/NftSaleContract/src/Protobuf/contract/nft_sale_contract.proto
@@ -0,0 +1,114 @@
+syntax = "proto3";
+
+import "aelf/core.proto";
+import "aelf/options.proto";
+import "google/protobuf/empty.proto";
+import "google/protobuf/wrappers.proto";
+import "Protobuf/reference/acs12.proto";
+// The namespace of this class
+option csharp_namespace = "AElf.Contracts.NftSale";
+
+service NftSale {
+ // The name of the state class the smart contract is going to use to access blockchain state
+ option (aelf.csharp_state) = "AElf.Contracts.NftSale.NftSaleState";
+ option (aelf.base) = "Protobuf/reference/acs12.proto";
+
+ rpc Initialize (NftPrice) returns (google.protobuf.Empty) {
+ }
+
+ rpc Purchase (PurchaseInput) returns (google.protobuf.Empty) {
+ }
+
+ rpc SetPriceAndSymbol (NftPrice) returns (google.protobuf.Empty) {
+ }
+
+ rpc GetPrice (google.protobuf.Empty) returns (Price) {
+ option (aelf.is_view) = true;
+ }
+
+ rpc GetSymbol (google.protobuf.Empty) returns (NftSymbol) {
+ option (aelf.is_view) = true;
+ }
+
+ rpc Withdraw (google.protobuf.Int64Value) returns (google.protobuf.Empty) {
+ }
+
+ rpc Deposit (DepositeInput) returns (google.protobuf.Empty) {
+ }
+
+ rpc TransferOwnership (aelf.Address) returns (google.protobuf.Empty) {
+ }
+
+ rpc GetContractBalance (GetContractBalanceInput) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+
+ rpc GetOwner (google.protobuf.Empty) returns (google.protobuf.StringValue) {
+ option (aelf.is_view) = true;
+ }
+}
+
+message DepositeInput{
+ string symbol = 1; // nft
+ int64 amount = 2; // token amount
+}
+
+message Price {
+ string symbol = 1; // token type
+ int64 amount = 2; // token price
+}
+
+message NftSymbol {
+ string symbol = 1;
+}
+
+message NftPrice{
+ // // The token symbol price
+ Price price = 1;
+ // The token symbol
+ string symbol = 2;
+}
+
+message PurchaseInput {
+ // The token symbol to transfer.
+ string symbol = 2;
+ // The amount to to transfer.
+ int64 amount = 3;
+ // The memo.
+ string memo = 4;
+ // Transaction information
+ Price price = 5;
+}
+
+message SaleNft {
+ option (aelf.is_event) = true;
+ // The destination address of the transferred token.
+ aelf.Address to = 2 [(aelf.is_indexed) = true];
+ // The symbol of the transferred token.
+ string symbol = 3 [(aelf.is_indexed) = true];
+ // The amount of the transferred token.
+ int64 amount = 4;
+ // The memo.
+ string memo = 5;
+}
+
+message GetContractBalanceInput{
+ aelf.Address address = 1;
+}
+
+// An event that will be emitted from contract method call when Withdraw is called.
+message WithdrawEvent {
+ option (aelf.is_event) = true;
+ int64 amount = 1;
+ aelf.Address from = 2;
+ aelf.Address to = 3;
+}
+
+// An event that will be emitted from contract method call when Deposit is called.
+message DepositEvent {
+ option (aelf.is_event) = true;
+ int64 amount = 1;
+ string symbol = 2;
+ aelf.Address from = 3;
+ aelf.Address to = 4;
+}
diff --git a/templates/NftSaleContract/src/Protobuf/message/authority_info.proto b/templates/NftSaleContract/src/Protobuf/message/authority_info.proto
new file mode 100644
index 0000000..eee3496
--- /dev/null
+++ b/templates/NftSaleContract/src/Protobuf/message/authority_info.proto
@@ -0,0 +1,10 @@
+syntax = "proto3";
+
+import "aelf/core.proto";
+
+option csharp_namespace = "AElf.Contracts.NftSale";
+
+message AuthorityInfo {
+ aelf.Address contract_address = 1;
+ aelf.Address owner_address = 2;
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/src/Protobuf/reference/acs12.proto b/templates/NftSaleContract/src/Protobuf/reference/acs12.proto
new file mode 100644
index 0000000..e6ead4b
--- /dev/null
+++ b/templates/NftSaleContract/src/Protobuf/reference/acs12.proto
@@ -0,0 +1,35 @@
+/**
+ * AElf Standards ACS12(User Contract Standard)
+ *
+ * Used to manage user contract.
+ */
+syntax = "proto3";
+
+package acs12;
+
+import public "aelf/options.proto";
+import public "google/protobuf/empty.proto";
+import public "google/protobuf/wrappers.proto";
+import "aelf/core.proto";
+
+option (aelf.identity) = "acs12";
+option csharp_namespace = "AElf.Standards.ACS12";
+
+service UserContract{
+
+}
+
+//Specified method fee for user contract.
+message UserContractMethodFees {
+ // List of fees to be charged.
+ repeated UserContractMethodFee fees = 2;
+ // Optional based on the implementation of SetConfiguration method.
+ bool is_size_fee_free = 3;
+}
+
+message UserContractMethodFee {
+ // The token symbol of the method fee.
+ string symbol = 1;
+ // The amount of fees to be charged.
+ int64 basic_fee = 2;
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/src/Protobuf/reference/aedpos_contract.proto b/templates/NftSaleContract/src/Protobuf/reference/aedpos_contract.proto
new file mode 100644
index 0000000..37965d8
--- /dev/null
+++ b/templates/NftSaleContract/src/Protobuf/reference/aedpos_contract.proto
@@ -0,0 +1,507 @@
+/**
+ * AEDPoS contract.
+ */
+syntax = "proto3";
+
+package AEDPoS;
+
+import "aelf/options.proto";
+import "aelf/core.proto";
+import "Protobuf/message/authority_info.proto";
+import "google/protobuf/empty.proto";
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
+
+option csharp_namespace = "AElf.Contracts.Consensus.AEDPoS";
+
+service AEDPoSContract {
+
+ option (aelf.csharp_state) = "AElf.Contracts.Consensus.AEDPoS.AEDPoSContractState";
+
+ // Initialize the consensus contract.
+ rpc InitialAElfConsensusContract (InitialAElfConsensusContractInput) returns (google.protobuf.Empty) {
+ }
+
+ // Initializes the consensus information in the first round.
+ rpc FirstRound (Round) returns (google.protobuf.Empty) {
+ }
+
+ // Update consensus information.
+ rpc UpdateValue (UpdateValueInput) returns (google.protobuf.Empty) {
+ }
+
+ // Update consensus information, create a new round.
+ rpc NextRound (NextRoundInput) returns (google.protobuf.Empty) {
+ }
+
+ // Update consensus information, create a new term.
+ rpc NextTerm (NextTermInput) returns (google.protobuf.Empty) {
+ }
+
+ // Update consensus tiny block information.
+ rpc UpdateTinyBlockInformation (TinyBlockInput) returns (google.protobuf.Empty) {
+ }
+
+ // Set the maximum count of miners, by default, is unlimited.
+ // If you want to control the count of miners, you need to set it through parliament.
+ rpc SetMaximumMinersCount (google.protobuf.Int32Value) returns (google.protobuf.Empty) {
+ }
+
+ // The authority information for SetMaximumMinersCount, by default, is governed by parliament.
+ rpc ChangeMaximumMinersCountController (AuthorityInfo) returns (google.protobuf.Empty) {
+ }
+
+ // Set miner increase interval
+ rpc SetMinerIncreaseInterval (google.protobuf.Int64Value) returns (google.protobuf.Empty){
+ }
+
+ // Election Contract can notify AEDPoS Contract to aware candidate replacement happened.
+ rpc RecordCandidateReplacement (RecordCandidateReplacementInput) returns (google.protobuf.Empty) {
+ }
+
+ // Get the list of current miners.
+ rpc GetCurrentMinerList (google.protobuf.Empty) returns (MinerList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the list of current miners (hexadecimal format).
+ rpc GetCurrentMinerPubkeyList (google.protobuf.Empty) returns (PubkeyList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the list of current miners and current round number.
+ rpc GetCurrentMinerListWithRoundNumber (google.protobuf.Empty) returns (MinerListWithRoundNumber) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get information of the round according to round number.
+ rpc GetRoundInformation (google.protobuf.Int64Value) returns (Round) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the current round number.
+ rpc GetCurrentRoundNumber (google.protobuf.Empty) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the current round information.
+ rpc GetCurrentRoundInformation (google.protobuf.Empty) returns (Round) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the previous round information.
+ rpc GetPreviousRoundInformation (google.protobuf.Empty) returns (Round) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the current term number.
+ rpc GetCurrentTermNumber (google.protobuf.Empty) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the welfare reward the current term.
+ rpc GetCurrentTermMiningReward (google.protobuf.Empty) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the list of miners according to term number.
+ rpc GetMinerList (GetMinerListInput) returns (MinerList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the list of miner in previous term.
+ rpc GetPreviousMinerList (google.protobuf.Empty) returns (MinerList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the amount of mined blocks in previous term.
+ rpc GetMinedBlocksOfPreviousTerm (google.protobuf.Empty) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the miner that produces the next block.
+ rpc GetNextMinerPubkey (google.protobuf.Empty) returns (google.protobuf.StringValue) {
+ option (aelf.is_view) = true;
+ }
+
+ // Check to see if the account address is on the miner list for the current round.
+ rpc IsCurrentMiner (aelf.Address) returns (google.protobuf.BoolValue) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the left time before the next election takes effects (seconds).
+ rpc GetNextElectCountDown (google.protobuf.Empty) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get term information according term number.
+ rpc GetPreviousTermInformation (google.protobuf.Int64Value) returns (Round) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get random hash (Keep this for compatibility).
+ rpc GetRandomHash (google.protobuf.Int64Value) returns (aelf.Hash) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the maximum of tiny blocks produced by a miner each round.
+ rpc GetMaximumBlocksCount (google.protobuf.Empty) returns (google.protobuf.Int32Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the maximum count of miners.
+ rpc GetMaximumMinersCount (google.protobuf.Empty) returns (google.protobuf.Int32Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the authority information for SetMaximumMinersCount.
+ rpc GetMaximumMinersCountController (google.protobuf.Empty) returns (AuthorityInfo) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get miner increase interval
+ rpc GetMinerIncreaseInterval (google.protobuf.Empty) returns (google.protobuf.Int64Value){
+ option (aelf.is_view) = true;
+ }
+
+ // Gets the list of miners in the main chain.
+ rpc GetMainChainCurrentMinerList (google.protobuf.Empty) returns (MinerList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the list of miners in the previous term.
+ rpc GetPreviousTermMinerPubkeyList (google.protobuf.Empty) returns (PubkeyList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the current mining reward for each block.
+ rpc GetCurrentMiningRewardPerBlock (google.protobuf.Empty) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+}
+
+message InitialAElfConsensusContractInput {
+ // Whether not to change the term.
+ bool is_term_stay_one = 1;
+ // Is a side chain.
+ bool is_side_chain = 2;
+ // The number of seconds per term.
+ int64 period_seconds = 3;
+ // The interval second that increases the number of miners.
+ int64 miner_increase_interval = 4;
+}
+
+message UpdateValueInput {
+ // Calculated from current in value.
+ aelf.Hash out_value = 1;
+ // Calculated from current in value and signatures of previous round.
+ aelf.Hash signature = 2;
+ // To ensure the values to update will be apply to correct round by comparing round id.
+ int64 round_id = 3;
+ // Publish previous in value for validation previous signature and previous out value.
+ aelf.Hash previous_in_value = 4;
+ // The actual mining time, miners must fill actual mining time when they do the mining.
+ google.protobuf.Timestamp actual_mining_time = 5;
+ // The supposed order of mining for the next round.
+ int32 supposed_order_of_next_round = 6;
+ // The tuning order of mining for the next round, miner public key -> order.
+ map tune_order_information = 7;
+ // The encrypted pieces of InValue.
+ map encrypted_pieces = 8;
+ // The decrypted pieces of InValue.
+ map decrypted_pieces = 9;
+ // The amount of produced blocks.
+ int64 produced_blocks = 10;
+ // The InValue in the previous round, miner public key -> InValue.
+ map miners_previous_in_values = 11;
+ // The irreversible block height that miner recorded.
+ int64 implied_irreversible_block_height = 12;
+ // The random number.
+ bytes random_number = 13;
+}
+
+message MinerList {
+ // The miners public key list.
+ repeated bytes pubkeys = 1;
+}
+
+message PubkeyList {
+ // The miners public key list.
+ repeated string pubkeys = 1;
+}
+
+message TermNumberLookUp {
+ // Term number -> Round number.
+ map map = 1;
+}
+
+message Candidates {
+ // The candidate public keys.
+ repeated bytes pubkeys = 1;
+}
+
+message Round {
+ // The round number.
+ int64 round_number = 1;
+ // Current miner information, miner public key -> miner information.
+ map real_time_miners_information = 2;
+ // The round number on the main chain
+ int64 main_chain_miners_round_number = 3;
+ // The time from chain start to current round (seconds).
+ int64 blockchain_age = 4;
+ // The miner public key that produced the extra block in the previous round.
+ string extra_block_producer_of_previous_round = 5;
+ // The current term number.
+ int64 term_number = 6;
+ // The height of the confirmed irreversible block.
+ int64 confirmed_irreversible_block_height = 7;
+ // The round number of the confirmed irreversible block.
+ int64 confirmed_irreversible_block_round_number = 8;
+ // Is miner list different from the the miner list in the previous round.
+ bool is_miner_list_just_changed = 9;
+ // The round id, calculated by summing block producers’ expecting time (second).
+ int64 round_id_for_validation = 10;
+}
+
+message MinerInRound {
+ // The order of the miner producing block.
+ int32 order = 1;
+ // Is extra block producer in the current round.
+ bool is_extra_block_producer = 2;
+ // Generated by secret sharing and used for validation between miner.
+ aelf.Hash in_value = 3;
+ // Calculated from current in value.
+ aelf.Hash out_value = 4;
+ // Calculated from current in value and signatures of previous round.
+ aelf.Hash signature = 5;
+ // The expected mining time.
+ google.protobuf.Timestamp expected_mining_time = 6;
+ // The amount of produced blocks.
+ int64 produced_blocks = 7;
+ // The amount of missed time slots.
+ int64 missed_time_slots = 8;
+ // The public key of this miner.
+ string pubkey = 9;
+ // The InValue of the previous round.
+ aelf.Hash previous_in_value = 10;
+ // The supposed order of mining for the next round.
+ int32 supposed_order_of_next_round = 11;
+ // The final order of mining for the next round.
+ int32 final_order_of_next_round = 12;
+ // The actual mining time, miners must fill actual mining time when they do the mining.
+ repeated google.protobuf.Timestamp actual_mining_times = 13;
+ // The encrypted pieces of InValue.
+ map encrypted_pieces = 14;
+ // The decrypted pieces of InValue.
+ map decrypted_pieces = 15;
+ // The amount of produced tiny blocks.
+ int64 produced_tiny_blocks = 16;
+ // The irreversible block height that current miner recorded.
+ int64 implied_irreversible_block_height = 17;
+}
+
+message AElfConsensusHeaderInformation {
+ // The sender public key.
+ bytes sender_pubkey = 1;
+ // The round information.
+ Round round = 2;
+ // The behaviour of consensus.
+ AElfConsensusBehaviour behaviour = 3;
+}
+
+message AElfConsensusHint {
+ // The behaviour of consensus.
+ AElfConsensusBehaviour behaviour = 1;
+ // The round id.
+ int64 round_id = 2;
+ // The previous round id.
+ int64 previous_round_id = 3;
+}
+
+enum AElfConsensusBehaviour {
+ UPDATE_VALUE = 0;
+ NEXT_ROUND = 1;
+ NEXT_TERM = 2;
+ NOTHING = 3;
+ TINY_BLOCK = 4;
+}
+
+message AElfConsensusTriggerInformation {
+ // The miner public key.
+ bytes pubkey = 1;
+ // The InValue for current round.
+ aelf.Hash in_value = 2;
+ // The InValue for previous round.
+ aelf.Hash previous_in_value = 3;
+ // The behaviour of consensus.
+ AElfConsensusBehaviour behaviour = 4;
+ // The encrypted pieces of InValue.
+ map encrypted_pieces = 5;
+ // The decrypted pieces of InValue.
+ map decrypted_pieces = 6;
+ // The revealed InValues.
+ map revealed_in_values = 7;
+ // The random number.
+ bytes random_number = 8;
+}
+
+message TermInfo {
+ int64 term_number = 1;
+ int64 round_number = 2;
+}
+
+message MinerListWithRoundNumber {
+ // The list of miners.
+ MinerList miner_list = 1;
+ // The round number.
+ int64 round_number = 2;
+}
+
+message TinyBlockInput {
+ // The round id.
+ int64 round_id = 1;
+ // The actual mining time.
+ google.protobuf.Timestamp actual_mining_time = 2;
+ // Count of blocks currently produced
+ int64 produced_blocks = 3;
+ // The random number.
+ bytes random_number = 4;
+}
+
+message VoteMinersCountInput {
+ int32 miners_count = 1;
+ int64 amount = 2;
+}
+
+message ConsensusInformation {
+ bytes value = 1;
+}
+
+message GetMinerListInput {
+ // The term number.
+ int64 term_number = 1;
+}
+
+message RandomNumberRequestInformation {
+ int64 target_round_number = 1;// The random hash is likely generated during this round.
+ int64 order = 2;
+ int64 expected_block_height = 3;
+}
+
+message HashList {
+ repeated aelf.Hash values = 1;
+}
+
+message LatestPubkeyToTinyBlocksCount {
+ // The miner public key.
+ string pubkey = 1;
+ // The count of blocks the miner produced.
+ int64 blocks_count = 2;
+}
+
+message IrreversibleBlockFound {
+ option (aelf.is_event) = true;
+ // The irreversible block height found.
+ int64 irreversible_block_height = 1 [(aelf.is_indexed) = true];
+}
+
+message IrreversibleBlockHeightUnacceptable {
+ option (aelf.is_event) = true;
+ // Distance to the height of the last irreversible block.
+ int64 distance_to_irreversible_block_height = 1;
+}
+
+message MiningInformationUpdated {
+ option (aelf.is_event) = true;
+ // The miner public key.
+ string pubkey = 1 [(aelf.is_indexed) = true];
+ // The current block time.
+ google.protobuf.Timestamp mining_time = 2 [(aelf.is_indexed) = true];
+ // The behaviour of consensus.
+ string behaviour = 3 [(aelf.is_indexed) = true];
+ // The current block height.
+ int64 block_height = 4 [(aelf.is_indexed) = true];
+ // The previous block hash.
+ aelf.Hash previous_block_hash = 5 [(aelf.is_indexed) = true];
+}
+
+message SecretSharingInformation {
+ option (aelf.is_event) = true;
+ // The previous round information.
+ Round previous_round = 1 [(aelf.is_indexed) = true];
+ // The current round id.
+ int64 current_round_id = 2;
+ // The previous round id.
+ int64 previous_round_id = 3;
+}
+
+message MiningRewardGenerated {
+ option (aelf.is_event) = true;
+ // The number of term the mining reward is generated.
+ int64 term_number = 1 [(aelf.is_indexed) = true];
+ // The amount of mining reward.
+ int64 amount = 2;
+}
+
+message MinerReplaced {
+ option (aelf.is_event) = true;
+ // The new miner public key.
+ string new_miner_pubkey = 1;
+}
+
+message RecordCandidateReplacementInput {
+ string old_pubkey = 1;
+ string new_pubkey = 2;
+}
+
+// For compatibility, it is the same as the Round with the addition of the random_number property.
+message NextRoundInput {
+ // The round number.
+ int64 round_number = 1;
+ // Current miner information, miner public key -> miner information.
+ map real_time_miners_information = 2;
+ // The round number on the main chain
+ int64 main_chain_miners_round_number = 3;
+ // The time from chain start to current round (seconds).
+ int64 blockchain_age = 4;
+ // The miner public key that produced the extra block in the previous round.
+ string extra_block_producer_of_previous_round = 5;
+ // The current term number.
+ int64 term_number = 6;
+ // The height of the confirmed irreversible block.
+ int64 confirmed_irreversible_block_height = 7;
+ // The round number of the confirmed irreversible block.
+ int64 confirmed_irreversible_block_round_number = 8;
+ // Is miner list different from the the miner list in the previous round.
+ bool is_miner_list_just_changed = 9;
+ // The round id, calculated by summing block producers’ expecting time (second).
+ int64 round_id_for_validation = 10;
+ // The random number.
+ bytes random_number = 11;
+}
+
+// For compatibility, it is the same as the Round with the addition of the random_number property.
+message NextTermInput {
+ // The round number.
+ int64 round_number = 1;
+ // Current miner information, miner public key -> miner information.
+ map real_time_miners_information = 2;
+ // The round number on the main chain
+ int64 main_chain_miners_round_number = 3;
+ // The time from chain start to current round (seconds).
+ int64 blockchain_age = 4;
+ // The miner public key that produced the extra block in the previous round.
+ string extra_block_producer_of_previous_round = 5;
+ // The current term number.
+ int64 term_number = 6;
+ // The height of the confirmed irreversible block.
+ int64 confirmed_irreversible_block_height = 7;
+ // The round number of the confirmed irreversible block.
+ int64 confirmed_irreversible_block_round_number = 8;
+ // Is miner list different from the the miner list in the previous round.
+ bool is_miner_list_just_changed = 9;
+ // The round id, calculated by summing block producers’ expecting time (second).
+ int64 round_id_for_validation = 10;
+ // The random number.
+ bytes random_number = 11;
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/src/Protobuf/reference/token_contract.proto b/templates/NftSaleContract/src/Protobuf/reference/token_contract.proto
new file mode 100644
index 0000000..d632247
--- /dev/null
+++ b/templates/NftSaleContract/src/Protobuf/reference/token_contract.proto
@@ -0,0 +1,894 @@
+/**
+ * MultiToken contract.
+ */
+syntax = "proto3";
+
+package token;
+
+import "aelf/core.proto";
+import "aelf/options.proto";
+import "google/protobuf/empty.proto";
+import "google/protobuf/wrappers.proto";
+
+option csharp_namespace = "AElf.Contracts.MultiToken";
+
+service TokenContract {
+ // Create a new token.
+ rpc Create (CreateInput) returns (google.protobuf.Empty) {
+ }
+
+ // Issuing some amount of tokens to an address is the action of increasing that addresses balance
+ // for the given token. The total amount of issued tokens must not exceed the total supply of the token
+ // and only the issuer (creator) of the token can issue tokens.
+ // Issuing tokens effectively increases the circulating supply.
+ rpc Issue (IssueInput) returns (google.protobuf.Empty) {
+ }
+
+ // Transferring tokens simply is the action of transferring a given amount of tokens from one address to another.
+ // The origin or source address is the signer of the transaction.
+ // The balance of the sender must be higher than the amount that is transferred.
+ rpc Transfer (TransferInput) returns (google.protobuf.Empty) {
+ }
+
+ // The TransferFrom action will transfer a specified amount of tokens from one address to another.
+ // For this operation to succeed the from address needs to have approved (see allowances) enough tokens
+ // to Sender of this transaction. If successful the amount will be removed from the allowance.
+ rpc TransferFrom (TransferFromInput) returns (google.protobuf.Empty) {
+ }
+
+ // The approve action increases the allowance from the Sender to the Spender address,
+ // enabling the Spender to call TransferFrom.
+ rpc Approve (ApproveInput) returns (google.protobuf.Empty) {
+ }
+
+ rpc BatchApprove (BatchApproveInput) returns (google.protobuf.Empty) {
+ }
+
+ // This is the reverse operation for Approve, it will decrease the allowance.
+ rpc UnApprove (UnApproveInput) returns (google.protobuf.Empty) {
+ }
+
+ // This method can be used to lock tokens.
+ rpc Lock (LockInput) returns (google.protobuf.Empty) {
+ }
+
+ // This is the reverse operation of locking, it un-locks some previously locked tokens.
+ rpc Unlock (UnlockInput) returns (google.protobuf.Empty) {
+ }
+
+ // This action will burn the specified amount of tokens, removing them from the token’s Supply.
+ rpc Burn (BurnInput) returns (google.protobuf.Empty) {
+ }
+
+ // Set the primary token of side chain.
+ rpc SetPrimaryTokenSymbol (SetPrimaryTokenSymbolInput) returns (google.protobuf.Empty) {
+ }
+
+ // This interface is used for cross-chain transfer.
+ rpc CrossChainTransfer (CrossChainTransferInput) returns (google.protobuf.Empty) {
+ }
+
+ // This method is used to receive cross-chain transfers.
+ rpc CrossChainReceiveToken (CrossChainReceiveTokenInput) returns (google.protobuf.Empty) {
+ }
+
+ // The side chain creates tokens.
+ rpc CrossChainCreateToken(CrossChainCreateTokenInput) returns (google.protobuf.Empty) {
+ }
+
+ // When the side chain is started, the side chain is initialized with the parent chain information.
+ rpc InitializeFromParentChain (InitializeFromParentChainInput) returns (google.protobuf.Empty) {
+ }
+
+ // Handle the transaction fees charged by ChargeTransactionFees.
+ rpc ClaimTransactionFees (TotalTransactionFeesMap) returns (google.protobuf.Empty) {
+ }
+
+ // Used to collect transaction fees.
+ rpc ChargeTransactionFees (ChargeTransactionFeesInput) returns (ChargeTransactionFeesOutput) {
+ }
+
+ rpc ChargeUserContractTransactionFees(ChargeTransactionFeesInput) returns(ChargeTransactionFeesOutput){
+
+ }
+
+ // Check the token threshold.
+ rpc CheckThreshold (CheckThresholdInput) returns (google.protobuf.Empty) {
+ }
+
+ // Initialize coefficients of every type of tokens supporting charging fee.
+ rpc InitialCoefficients (google.protobuf.Empty) returns (google.protobuf.Empty){
+ }
+
+ // Processing resource token received.
+ rpc DonateResourceToken (TotalResourceTokensMaps) returns (google.protobuf.Empty) {
+ }
+
+ // A transaction resource fee is charged to implement the ACS8 standards.
+ rpc ChargeResourceToken (ChargeResourceTokenInput) returns (google.protobuf.Empty) {
+ }
+
+ // Verify that the resource token are sufficient.
+ rpc CheckResourceToken (google.protobuf.Empty) returns (google.protobuf.Empty) {
+ }
+
+ // Set the list of tokens to pay transaction fees.
+ rpc SetSymbolsToPayTxSizeFee (SymbolListToPayTxSizeFee) returns (google.protobuf.Empty){
+ }
+
+ // Update the coefficient of the transaction fee calculation formula.
+ rpc UpdateCoefficientsForSender (UpdateCoefficientsInput) returns (google.protobuf.Empty) {
+ }
+
+ // Update the coefficient of the transaction fee calculation formula.
+ rpc UpdateCoefficientsForContract (UpdateCoefficientsInput) returns (google.protobuf.Empty) {
+ }
+
+ // This method is used to initialize the governance organization for some functions,
+ // including: the coefficient of the user transaction fee calculation formula,
+ // the coefficient of the contract developer resource fee calculation formula, and the side chain rental fee.
+ rpc InitializeAuthorizedController (google.protobuf.Empty) returns (google.protobuf.Empty){
+ }
+
+ rpc AddAddressToCreateTokenWhiteList (aelf.Address) returns (google.protobuf.Empty) {
+ }
+ rpc RemoveAddressFromCreateTokenWhiteList (aelf.Address) returns (google.protobuf.Empty) {
+ }
+
+ rpc SetTransactionFeeDelegations (SetTransactionFeeDelegationsInput) returns (SetTransactionFeeDelegationsOutput){
+ }
+
+ rpc RemoveTransactionFeeDelegator (RemoveTransactionFeeDelegatorInput) returns (google.protobuf.Empty){
+ }
+
+ rpc RemoveTransactionFeeDelegatee (RemoveTransactionFeeDelegateeInput) returns (google.protobuf.Empty){
+ }
+
+ rpc SetSymbolAlias (SetSymbolAliasInput) returns (google.protobuf.Empty){
+ }
+
+ // Get all delegatees' address of delegator from input
+ rpc GetTransactionFeeDelegatees (GetTransactionFeeDelegateesInput) returns (GetTransactionFeeDelegateesOutput) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query token information.
+ rpc GetTokenInfo (GetTokenInfoInput) returns (TokenInfo) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query native token information.
+ rpc GetNativeTokenInfo (google.protobuf.Empty) returns (TokenInfo) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query resource token information.
+ rpc GetResourceTokenInfo (google.protobuf.Empty) returns (TokenInfoList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the balance at the specified address.
+ rpc GetBalance (GetBalanceInput) returns (GetBalanceOutput) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the account's allowance for other addresses
+ rpc GetAllowance (GetAllowanceInput) returns (GetAllowanceOutput) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the account's available allowance for other addresses
+ rpc GetAvailableAllowance (GetAllowanceInput) returns (GetAllowanceOutput) {
+ option (aelf.is_view) = true;
+ }
+
+ // Check whether the token is in the whitelist of an address,
+ // which can be called TransferFrom to transfer the token under the condition of not being credited.
+ rpc IsInWhiteList (IsInWhiteListInput) returns (google.protobuf.BoolValue) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the information for a lock.
+ rpc GetLockedAmount (GetLockedAmountInput) returns (GetLockedAmountOutput) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the address of receiving token in cross-chain transfer.
+ rpc GetCrossChainTransferTokenContractAddress (GetCrossChainTransferTokenContractAddressInput) returns (aelf.Address) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the name of the primary Token.
+ rpc GetPrimaryTokenSymbol (google.protobuf.Empty) returns (google.protobuf.StringValue) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the coefficient of the transaction fee calculation formula.
+ rpc GetCalculateFeeCoefficientsForContract (google.protobuf.Int32Value) returns (CalculateFeeCoefficients) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the coefficient of the transaction fee calculation formula.
+ rpc GetCalculateFeeCoefficientsForSender (google.protobuf.Empty) returns (CalculateFeeCoefficients) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query tokens that can pay transaction fees.
+ rpc GetSymbolsToPayTxSizeFee (google.protobuf.Empty) returns (SymbolListToPayTxSizeFee){
+ option (aelf.is_view) = true;
+ }
+
+ // Query the hash of the last input of ClaimTransactionFees.
+ rpc GetLatestTotalTransactionFeesMapHash (google.protobuf.Empty) returns (aelf.Hash){
+ option (aelf.is_view) = true;
+ }
+
+ // Query the hash of the last input of DonateResourceToken.
+ rpc GetLatestTotalResourceTokensMapsHash (google.protobuf.Empty) returns (aelf.Hash){
+ option (aelf.is_view) = true;
+ }
+ rpc IsTokenAvailableForMethodFee (google.protobuf.StringValue) returns (google.protobuf.BoolValue) {
+ option (aelf.is_view) = true;
+ }
+ rpc GetReservedExternalInfoKeyList (google.protobuf.Empty) returns (StringList) {
+ option (aelf.is_view) = true;
+ }
+
+ rpc GetTransactionFeeDelegationsOfADelegatee(GetTransactionFeeDelegationsOfADelegateeInput) returns(TransactionFeeDelegations){
+ option (aelf.is_view) = true;
+ }
+
+ rpc GetTokenAlias (google.protobuf.StringValue) returns (google.protobuf.StringValue) {
+ option (aelf.is_view) = true;
+ }
+
+ rpc GetSymbolByAlias (google.protobuf.StringValue) returns (google.protobuf.StringValue) {
+ option (aelf.is_view) = true;
+ }
+}
+
+message TokenInfo {
+ // The symbol of the token.f
+ string symbol = 1;
+ // The full name of the token.
+ string token_name = 2;
+ // The current supply of the token.
+ int64 supply = 3;
+ // The total supply of the token.
+ int64 total_supply = 4;
+ // The precision of the token.
+ int32 decimals = 5;
+ // The address that has permission to issue the token.
+ aelf.Address issuer = 6;
+ // A flag indicating if this token is burnable.
+ bool is_burnable = 7;
+ // The chain id of the token.
+ int32 issue_chain_id = 8;
+ // The amount of issued tokens.
+ int64 issued = 9;
+ // The external information of the token.
+ ExternalInfo external_info = 10;
+ // The address that owns the token.
+ aelf.Address owner = 11;
+}
+
+message ExternalInfo {
+ map value = 1;
+}
+
+message CreateInput {
+ // The symbol of the token.
+ string symbol = 1;
+ // The full name of the token.
+ string token_name = 2;
+ // The total supply of the token.
+ int64 total_supply = 3;
+ // The precision of the token
+ int32 decimals = 4;
+ // The address that has permission to issue the token.
+ aelf.Address issuer = 5;
+ // A flag indicating if this token is burnable.
+ bool is_burnable = 6;
+ // A whitelist address list used to lock tokens.
+ repeated aelf.Address lock_white_list = 7;
+ // The chain id of the token.
+ int32 issue_chain_id = 8;
+ // The external information of the token.
+ ExternalInfo external_info = 9;
+ // The address that owns the token.
+ aelf.Address owner = 10;
+}
+
+message SetPrimaryTokenSymbolInput {
+ // The symbol of the token.
+ string symbol = 1;
+}
+
+message IssueInput {
+ // The token symbol to issue.
+ string symbol = 1;
+ // The token amount to issue.
+ int64 amount = 2;
+ // The memo.
+ string memo = 3;
+ // The target address to issue.
+ aelf.Address to = 4;
+}
+
+message TransferInput {
+ // The receiver of the token.
+ aelf.Address to = 1;
+ // The token symbol to transfer.
+ string symbol = 2;
+ // The amount to to transfer.
+ int64 amount = 3;
+ // The memo.
+ string memo = 4;
+}
+
+message LockInput {
+ // The one want to lock his token.
+ aelf.Address address = 1;
+ // Id of the lock.
+ aelf.Hash lock_id = 2;
+ // The symbol of the token to lock.
+ string symbol = 3;
+ // a memo.
+ string usage = 4;
+ // The amount of tokens to lock.
+ int64 amount = 5;
+}
+
+message UnlockInput {
+ // The one want to un-lock his token.
+ aelf.Address address = 1;
+ // Id of the lock.
+ aelf.Hash lock_id = 2;
+ // The symbol of the token to un-lock.
+ string symbol = 3;
+ // a memo.
+ string usage = 4;
+ // The amount of tokens to un-lock.
+ int64 amount = 5;
+}
+
+message TransferFromInput {
+ // The source address of the token.
+ aelf.Address from = 1;
+ // The destination address of the token.
+ aelf.Address to = 2;
+ // The symbol of the token to transfer.
+ string symbol = 3;
+ // The amount to transfer.
+ int64 amount = 4;
+ // The memo.
+ string memo = 5;
+}
+
+message ApproveInput {
+ // The address that allowance will be increased.
+ aelf.Address spender = 1;
+ // The symbol of token to approve.
+ string symbol = 2;
+ // The amount of token to approve.
+ int64 amount = 3;
+}
+message BatchApproveInput {
+ repeated ApproveInput value = 1;
+}
+
+message UnApproveInput {
+ // The address that allowance will be decreased.
+ aelf.Address spender = 1;
+ // The symbol of token to un-approve.
+ string symbol = 2;
+ // The amount of token to un-approve.
+ int64 amount = 3;
+}
+
+message BurnInput {
+ // The symbol of token to burn.
+ string symbol = 1;
+ // The amount of token to burn.
+ int64 amount = 2;
+}
+
+message ChargeResourceTokenInput {
+ // Collection of charge resource token, Symbol->Amount.
+ map cost_dic = 1;
+ // The sender of the transaction.
+ aelf.Address caller = 2;
+}
+
+message TransactionFeeBill {
+ // The transaction fee dictionary, Symbol->fee.
+ map fees_map = 1;
+}
+
+message TransactionFreeFeeAllowanceBill {
+ // The transaction free fee allowance dictionary, Symbol->fee.
+ map free_fee_allowances_map = 1;
+}
+
+message CheckThresholdInput {
+ // The sender of the transaction.
+ aelf.Address sender = 1;
+ // The threshold to set, Symbol->Threshold.
+ map symbol_to_threshold = 2;
+ // Whether to check the allowance.
+ bool is_check_allowance = 3;
+}
+
+message GetTokenInfoInput {
+ // The symbol of token.
+ string symbol = 1;
+}
+
+message GetBalanceInput {
+ // The symbol of token.
+ string symbol = 1;
+ // The target address of the query.
+ aelf.Address owner = 2;
+}
+
+message GetBalanceOutput {
+ // The symbol of token.
+ string symbol = 1;
+ // The target address of the query.
+ aelf.Address owner = 2;
+ // The balance of the owner.
+ int64 balance = 3;
+}
+
+message GetAllowanceInput {
+ // The symbol of token.
+ string symbol = 1;
+ // The address of the token owner.
+ aelf.Address owner = 2;
+ // The address of the spender.
+ aelf.Address spender = 3;
+}
+
+message GetAllowanceOutput {
+ // The symbol of token.
+ string symbol = 1;
+ // The address of the token owner.
+ aelf.Address owner = 2;
+ // The address of the spender.
+ aelf.Address spender = 3;
+ // The amount of allowance.
+ int64 allowance = 4;
+}
+
+message CrossChainTransferInput {
+ // The receiver of transfer.
+ aelf.Address to = 1;
+ // The symbol of token.
+ string symbol = 2;
+ // The amount of token to transfer.
+ int64 amount = 3;
+ // The memo.
+ string memo = 4;
+ // The destination chain id.
+ int32 to_chain_id = 5;
+ // The chain id of the token.
+ int32 issue_chain_id = 6;
+}
+
+message CrossChainReceiveTokenInput {
+ // The source chain id.
+ int32 from_chain_id = 1;
+ // The height of the transfer transaction.
+ int64 parent_chain_height = 2;
+ // The raw bytes of the transfer transaction.
+ bytes transfer_transaction_bytes = 3;
+ // The merkle path created from the transfer transaction.
+ aelf.MerklePath merkle_path = 4;
+}
+
+message IsInWhiteListInput {
+ // The symbol of token.
+ string symbol = 1;
+ // The address to check.
+ aelf.Address address = 2;
+}
+
+message SymbolToPayTxSizeFee{
+ // The symbol of token.
+ string token_symbol = 1;
+ // The charge weight of primary token.
+ int32 base_token_weight = 2;
+ // The new added token charge weight. For example, the charge weight of primary Token is set to 1.
+ // The newly added token charge weight is set to 10. If the transaction requires 1 unit of primary token,
+ // the user can also pay for 10 newly added tokens.
+ int32 added_token_weight = 3;
+}
+
+message SymbolListToPayTxSizeFee{
+ // Transaction fee token information.
+ repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 1;
+}
+
+message ChargeTransactionFeesInput {
+ // The method name of transaction.
+ string method_name = 1;
+ // The contract address of transaction.
+ aelf.Address contract_address = 2;
+ // The amount of transaction size fee.
+ int64 transaction_size_fee = 3;
+ // Transaction fee token information.
+ repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 4;
+}
+
+message ChargeTransactionFeesOutput {
+ // Whether the charge was successful.
+ bool success = 1;
+ // The charging information.
+ string charging_information = 2;
+}
+
+message CallbackInfo {
+ aelf.Address contract_address = 1;
+ string method_name = 2;
+}
+
+message ExtraTokenListModified {
+ option (aelf.is_event) = true;
+ // Transaction fee token information.
+ SymbolListToPayTxSizeFee symbol_list_to_pay_tx_size_fee = 1;
+}
+
+message GetLockedAmountInput {
+ // The address of the lock.
+ aelf.Address address = 1;
+ // The token symbol.
+ string symbol = 2;
+ // The id of the lock.
+ aelf.Hash lock_id = 3;
+}
+
+message GetLockedAmountOutput {
+ // The address of the lock.
+ aelf.Address address = 1;
+ // The token symbol.
+ string symbol = 2;
+ // The id of the lock.
+ aelf.Hash lock_id = 3;
+ // The locked amount.
+ int64 amount = 4;
+}
+
+message TokenInfoList {
+ // List of token information.
+ repeated TokenInfo value = 1;
+}
+
+message GetCrossChainTransferTokenContractAddressInput {
+ // The chain id.
+ int32 chainId = 1;
+}
+
+message CrossChainCreateTokenInput {
+ // The chain id of the chain on which the token was created.
+ int32 from_chain_id = 1;
+ // The height of the transaction that created the token.
+ int64 parent_chain_height = 2;
+ // The transaction that created the token.
+ bytes transaction_bytes = 3;
+ // The merkle path created from the transaction that created the transaction.
+ aelf.MerklePath merkle_path = 4;
+}
+
+message InitializeFromParentChainInput {
+ // The amount of resource.
+ map resource_amount = 1;
+ // The token contract addresses.
+ map registered_other_token_contract_addresses = 2;
+ // The creator the side chain.
+ aelf.Address creator = 3;
+}
+
+message UpdateCoefficientsInput {
+ // The specify pieces gonna update.
+ repeated int32 piece_numbers = 1;
+ // Coefficients of one single type.
+ CalculateFeeCoefficients coefficients = 2;
+}
+
+enum FeeTypeEnum {
+ READ = 0;
+ STORAGE = 1;
+ WRITE = 2;
+ TRAFFIC = 3;
+ TX = 4;
+}
+
+message CalculateFeePieceCoefficients {
+ // Coefficients of one single piece.
+ // The first char is its type: liner / power.
+ // The second char is its piece upper bound.
+ repeated int32 value = 1;
+}
+
+message CalculateFeeCoefficients {
+ // The resource fee type, like READ, WRITE, etc.
+ int32 fee_token_type = 1;
+ // Coefficients of one single piece.
+ repeated CalculateFeePieceCoefficients piece_coefficients_list = 2;
+}
+
+message AllCalculateFeeCoefficients {
+ // The coefficients of fee Calculation.
+ repeated CalculateFeeCoefficients value = 1;
+}
+
+message TotalTransactionFeesMap
+{
+ // Token dictionary that charge transaction fee, Symbol->Amount.
+ map value = 1;
+ // The hash of the block processing the transaction.
+ aelf.Hash block_hash = 2;
+ // The height of the block processing the transaction.
+ int64 block_height = 3;
+}
+
+message TotalResourceTokensMaps {
+ // Resource tokens to charge.
+ repeated ContractTotalResourceTokens value = 1;
+ // The hash of the block processing the transaction.
+ aelf.Hash block_hash = 2;
+ // The height of the block processing the transaction.
+ int64 block_height = 3;
+}
+
+message ContractTotalResourceTokens {
+ // The contract address.
+ aelf.Address contract_address = 1;
+ // Resource tokens to charge.
+ TotalResourceTokensMap tokens_map = 2;
+}
+
+message TotalResourceTokensMap
+{
+ // Resource token dictionary, Symbol->Amount.
+ map value = 1;
+}
+
+message StringList {
+ repeated string value = 1;
+}
+
+message TransactionFeeDelegations{
+ // delegation, symbols and its' amount
+ map delegations = 1;
+ // height when added
+ int64 block_height = 2;
+ //Whether to pay transaction fee continuously
+ bool isUnlimitedDelegate = 3;
+}
+
+message TransactionFeeDelegatees{
+ map delegatees = 1;
+}
+
+message SetTransactionFeeDelegationsInput {
+ // the delegator address
+ aelf.Address delegator_address = 1;
+ // delegation, symbols and its' amount
+ map delegations = 2;
+}
+
+message SetTransactionFeeDelegationsOutput {
+ bool success = 1;
+}
+
+message RemoveTransactionFeeDelegatorInput{
+ // the delegator address
+ aelf.Address delegator_address = 1;
+}
+
+message RemoveTransactionFeeDelegateeInput {
+ // the delegatee address
+ aelf.Address delegatee_address = 1;
+}
+
+message GetTransactionFeeDelegationsOfADelegateeInput {
+ aelf.Address delegatee_address = 1;
+ aelf.Address delegator_address = 2;
+}
+
+message GetTransactionFeeDelegateesInput {
+ aelf.Address delegator_address = 1;
+}
+
+message GetTransactionFeeDelegateesOutput {
+ repeated aelf.Address delegatee_addresses = 1;
+}
+
+message SetSymbolAliasInput {
+ string symbol = 1;
+ string alias = 2;
+}
+
+// Events
+
+message Transferred {
+ option (aelf.is_event) = true;
+ // The source address of the transferred token.
+ aelf.Address from = 1 [(aelf.is_indexed) = true];
+ // The destination address of the transferred token.
+ aelf.Address to = 2 [(aelf.is_indexed) = true];
+ // The symbol of the transferred token.
+ string symbol = 3 [(aelf.is_indexed) = true];
+ // The amount of the transferred token.
+ int64 amount = 4;
+ // The memo.
+ string memo = 5;
+}
+
+message Approved {
+ option (aelf.is_event) = true;
+ // The address of the token owner.
+ aelf.Address owner = 1 [(aelf.is_indexed) = true];
+ // The address that allowance be increased.
+ aelf.Address spender = 2 [(aelf.is_indexed) = true];
+ // The symbol of approved token.
+ string symbol = 3 [(aelf.is_indexed) = true];
+ // The amount of approved token.
+ int64 amount = 4;
+}
+
+message UnApproved {
+ option (aelf.is_event) = true;
+ // The address of the token owner.
+ aelf.Address owner = 1 [(aelf.is_indexed) = true];
+ // The address that allowance be decreased.
+ aelf.Address spender = 2 [(aelf.is_indexed) = true];
+ // The symbol of un-approved token.
+ string symbol = 3 [(aelf.is_indexed) = true];
+ // The amount of un-approved token.
+ int64 amount = 4;
+}
+
+message Burned
+{
+ option (aelf.is_event) = true;
+ // The address who wants to burn token.
+ aelf.Address burner = 1 [(aelf.is_indexed) = true];
+ // The symbol of burned token.
+ string symbol = 2 [(aelf.is_indexed) = true];
+ // The amount of burned token.
+ int64 amount = 3;
+}
+
+message ChainPrimaryTokenSymbolSet {
+ option (aelf.is_event) = true;
+ // The symbol of token.
+ string token_symbol = 1;
+}
+
+message CalculateFeeAlgorithmUpdated {
+ option (aelf.is_event) = true;
+ // All calculate fee coefficients after modification.
+ AllCalculateFeeCoefficients all_type_fee_coefficients = 1;
+}
+
+message RentalCharged {
+ option (aelf.is_event) = true;
+ // The symbol of rental fee charged.
+ string symbol = 1;
+ // The amount of rental fee charged.
+ int64 amount = 2;
+ // The payer of rental fee.
+ aelf.Address payer = 3;
+ // The receiver of rental fee.
+ aelf.Address receiver = 4;
+}
+
+message RentalAccountBalanceInsufficient {
+ option (aelf.is_event) = true;
+ // The symbol of insufficient rental account balance.
+ string symbol = 1;
+ // The balance of the account.
+ int64 amount = 2;
+}
+
+message TokenCreated {
+ option (aelf.is_event) = true;
+ // The symbol of the token.
+ string symbol = 1;
+ // The full name of the token.
+ string token_name = 2;
+ // The total supply of the token.
+ int64 total_supply = 3;
+ // The precision of the token.
+ int32 decimals = 4;
+ // The address that has permission to issue the token.
+ aelf.Address issuer = 5;
+ // A flag indicating if this token is burnable.
+ bool is_burnable = 6;
+ // The chain id of the token.
+ int32 issue_chain_id = 7;
+ // The external information of the token.
+ ExternalInfo external_info = 8;
+ // The address that owns the token.
+ aelf.Address owner = 9;
+}
+
+message Issued {
+ option (aelf.is_event) = true;
+ // The symbol of issued token.
+ string symbol = 1;
+ // The amount of issued token.
+ int64 amount = 2;
+ // The memo.
+ string memo = 3;
+ // The issued target address.
+ aelf.Address to = 4;
+}
+
+message CrossChainTransferred {
+ option (aelf.is_event) = true;
+ // The source address of the transferred token.
+ aelf.Address from = 1;
+ // The destination address of the transferred token.
+ aelf.Address to = 2;
+ // The symbol of the transferred token.
+ string symbol = 3;
+ // The amount of the transferred token.
+ int64 amount = 4;
+ // The memo.
+ string memo = 5;
+ // The destination chain id.
+ int32 to_chain_id = 6;
+ // The chain id of the token.
+ int32 issue_chain_id = 7;
+}
+
+message CrossChainReceived {
+ option (aelf.is_event) = true;
+ // The source address of the transferred token.
+ aelf.Address from = 1;
+ // The destination address of the transferred token.
+ aelf.Address to = 2;
+ // The symbol of the received token.
+ string symbol = 3;
+ // The amount of the received token.
+ int64 amount = 4;
+ // The memo.
+ string memo = 5;
+ // The destination chain id.
+ int32 from_chain_id = 6;
+ // The chain id of the token.
+ int32 issue_chain_id = 7;
+ // The parent chain height of the transfer transaction.
+ int64 parent_chain_height = 8;
+ // The id of transfer transaction.
+ aelf.Hash transfer_transaction_id =9;
+}
+
+message TransactionFeeDelegationAdded {
+ option (aelf.is_event) = true;
+ aelf.Address delegator = 1 [(aelf.is_indexed) = true];
+ aelf.Address delegatee = 2 [(aelf.is_indexed) = true];
+ aelf.Address caller = 3 [(aelf.is_indexed) = true];
+}
+
+message TransactionFeeDelegationCancelled {
+ option (aelf.is_event) = true;
+ aelf.Address delegator = 1 [(aelf.is_indexed) = true];
+ aelf.Address delegatee = 2 [(aelf.is_indexed) = true];
+ aelf.Address caller = 3 [(aelf.is_indexed) = true];
+}
+
+message SymbolAliasAdded {
+ option (aelf.is_event) = true;
+ string symbol = 1 [(aelf.is_indexed) = true];
+ string alias = 2 [(aelf.is_indexed) = true];
+}
+
+message SymbolAliasDeleted {
+ option (aelf.is_event) = true;
+ string symbol = 1 [(aelf.is_indexed) = true];
+ string alias = 2 [(aelf.is_indexed) = true];
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/test/NftSale.Tests.csproj b/templates/NftSaleContract/test/NftSale.Tests.csproj
new file mode 100644
index 0000000..f23534b
--- /dev/null
+++ b/templates/NftSaleContract/test/NftSale.Tests.csproj
@@ -0,0 +1,51 @@
+
+
+ net6.0
+ AElf.Contracts.NftSale
+
+
+
+ 0436;CS2002
+
+
+ $(MSBuildProjectDirectory)/$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ nocontract
+
+
+ nocontract
+
+
+ stub
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/NftSaleContract/test/NftSaleTests.cs b/templates/NftSaleContract/test/NftSaleTests.cs
new file mode 100644
index 0000000..6755e7b
--- /dev/null
+++ b/templates/NftSaleContract/test/NftSaleTests.cs
@@ -0,0 +1,294 @@
+using System;
+using System.Threading.Tasks;
+using AElf.Contracts.MultiToken;
+using AElf.Contracts.Vote;
+using AElf.Types;
+using Google.Protobuf.WellKnownTypes;
+using Shouldly;
+using Xunit;
+
+namespace AElf.Contracts.NftSale
+{
+ // This class is unit test class, and it inherit TestBase. Write your unit test code inside it
+ public class NftSaleTests : TestBase
+ {
+ [Fact]
+ public async Task InitializeContract_Success()
+ {
+ var price = new Price
+ {
+ Amount = 4,
+ Symbol = "ELF"
+ };
+ var nftPrice = new NftPrice
+ {
+ Symbol = "ELF",
+ Price = price
+ };
+ // Act
+ var result = await NftSaleStub.Initialize.SendAsync(nftPrice);
+
+ // Assert
+ result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined);
+
+ var owner = await NftSaleStub.GetOwner.CallAsync(new Empty());
+ owner.Value.ShouldBe(DefaultAccount.Address.ToBase58());
+ }
+
+ [Fact]
+ public async Task InitializeContract_Fail_AlreadyInitialized()
+ {
+ var price = new Price
+ {
+ Amount = 4,
+ Symbol = "ELF"
+ };
+ var nftPrice = new NftPrice
+ {
+ Symbol = "ELF",
+ Price = price
+ };
+ // Arrange
+ await NftSaleStub.Initialize.SendAsync(nftPrice);
+
+ // Act & Assert
+ Should.Throw(async () => await NftSaleStub.Initialize.SendAsync(nftPrice));
+ }
+
+ [Fact]
+ public async Task SetPrice_and_GetPrice()
+ {
+ var price = new Price
+ {
+ Amount = 2,
+ Symbol = "ELF"
+ };
+ var nftPrice = new NftPrice
+ {
+ Symbol = "ELF",
+ Price = price
+ };
+ // Arrange
+ await NftSaleStub.Initialize.SendAsync(nftPrice);
+ var priceNew = new Price
+ {
+ Amount = 4,
+ Symbol = "ELF"
+ };
+
+ await NftSaleStub.SetPriceAndSymbol.SendAsync(new NftPrice
+ {
+ Symbol = "ELF",
+ Price = priceNew
+ });
+
+ var symbolPrice = await NftSaleStub.GetPrice.CallAsync(new Empty{});
+
+ symbolPrice.Amount.ShouldBe(4);
+ }
+
+ [Fact]
+ public async Task Purchase_Success()
+ {
+ var price = new Price
+ {
+ Amount = 2,
+ Symbol = "ELF"
+ };
+ var nftPrice = new NftPrice
+ {
+ Symbol = "ELF",
+ Price = price
+ };
+ await NftSaleStub.Initialize.SendAsync(nftPrice);
+ await ApproveSpendingAsync(10000_0000000);
+ await TokenContractStub.Approve.SendAsync(new ApproveInput
+ {
+ Spender = Accounts[1].Address,
+ Symbol = "ELF",
+ Amount = 100_00000000
+ });
+ await TokenContractStub.Approve.SendAsync(new ApproveInput
+ {
+ Spender = Accounts[0].Address,
+ Symbol = "ELF",
+ Amount = 100_00000000
+ });
+ //await SendTokenTo(Accounts[2].Address);
+ await SendTokenTo(ContractAddress);
+ //await SendTokenTo(Accounts[0].Address);
+ await SendTokenTo(Accounts[1].Address);
+ var initialContractBalance = await GetContractBalanceAsync(Accounts[0].Address);
+
+ var initialContractBalance1 = await GetContractBalanceAsync(Accounts[1].Address);
+ var priceNew = new Price
+ {
+ Amount = 3,
+ Symbol = "ELF"
+ };
+ await NftSaleStub.Purchase.SendAsync(new PurchaseInput
+ {
+ Amount = 1,
+ Symbol = "ELF",
+ Memo = "Test get resource",
+ Price = priceNew
+ });
+
+ var finalContractBalance = await GetContractBalanceAsync(Accounts[0].Address);
+ var finalContractBalance2 = await GetContractBalanceAsync(Accounts[1].Address);
+ finalContractBalance.ShouldBe(initialContractBalance - 1);
+ }
+
+ private async Task ApproveSpendingAsync(long amount)
+ {
+ await TokenContractStub.Approve.SendAsync(new ApproveInput
+ {
+ Spender = ContractAddress,
+ Symbol = "ELF",
+ Amount = amount
+ });
+ }
+
+ [Fact]
+ public async Task Deposit_Success()
+ {
+ var price = new Price
+ {
+ Amount = 2,
+ Symbol = "ELF"
+ };
+ var nftPrice = new NftPrice
+ {
+ Symbol = "ELF",
+ Price = price
+ };
+ // Arrange
+ await NftSaleStub.Initialize.SendAsync(nftPrice);
+
+ // Approve spending on the lottery contract
+ await ApproveSpendingAsync(100_00000000);
+
+ const long depositAmount = 10_000_000; // 0.1 ELF
+ // var depositInput = new Int64Value() { Value = depositAmount };
+ var depositInput = new DepositeInput
+ {
+ Symbol = "ELF",
+ Amount = 10_000_000
+ };
+
+ var initialContractBalance = await GetContractBalanceAsync(ContractAddress);
+
+ // Act
+ var result = await NftSaleStub.Deposit.SendAsync(depositInput);
+
+ // Assert
+ result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined);
+
+ // Check balance update
+ var finalContractBalance = await GetContractBalanceAsync(ContractAddress);
+ finalContractBalance.ShouldBe(initialContractBalance + depositAmount);
+
+ // Check if the event is emitted
+ var events = result.TransactionResult.Logs;
+ events.ShouldContain(log => log.Name == nameof(DepositEvent));
+ }
+
+ [Fact]
+ public async Task Withdraw_Success()
+ {
+ var price = new Price
+ {
+ Amount = 2,
+ Symbol = "ELF"
+ };
+ var nftPrice = new NftPrice
+ {
+ Symbol = "ELF",
+ Price = price
+ };
+ // Arrange
+ await NftSaleStub.Initialize.SendAsync(nftPrice);
+
+ // Approve spending on the lottery contract
+ await ApproveSpendingAsync(100_00000000);
+
+ var depositInput = new DepositeInput
+ {
+ Symbol = "ELF",
+ Amount = 10_000_000
+ };
+ await NftSaleStub.Deposit.SendAsync(depositInput);
+
+ const long withdrawAmount = 5_000_000; // 0.05 ELF
+ var withdrawInput = new Int64Value() { Value = withdrawAmount };
+
+ var initialSenderBalance = await GetTokenBalanceAsync(DefaultAccount.Address);
+ var initialContractBalance = await GetContractBalanceAsync(ContractAddress);
+
+ // Act
+ var result = await NftSaleStub.Withdraw.SendAsync(withdrawInput);
+
+ // Assert
+ result.TransactionResult.Status.ShouldBe(TransactionResultStatus.Mined);
+
+ // Check balance update
+ var finalSenderBalance = await GetTokenBalanceAsync(DefaultAccount.Address);
+ var finalContractBalance = await GetContractBalanceAsync(ContractAddress);
+
+ finalSenderBalance.ShouldBe(initialSenderBalance + withdrawAmount);
+ finalContractBalance.ShouldBe(initialContractBalance - withdrawAmount);
+
+ // Check if the event is emitted
+ var events = result.TransactionResult.Logs;
+ events.ShouldContain(log => log.Name == nameof(WithdrawEvent));
+ }
+
+ [Fact]
+ public async Task Withdraw_InsufficientBalance_Fail()
+ {
+ var price = new Price
+ {
+ Amount = 2,
+ Symbol = "ELF"
+ };
+ var nftPrice = new NftPrice
+ {
+ Symbol = "ELF",
+ Price = price
+ };
+ // Arrange
+ await NftSaleStub.Initialize.SendAsync(nftPrice);
+
+ long withdrawAmount = 5_000_000; // 0.05 ELF
+ var withdrawInput = new Int64Value() { Value = withdrawAmount };
+
+ // Act & Assert
+ Should.Throw(async () => await NftSaleStub.Withdraw.SendAsync(withdrawInput));
+ }
+
+ private async Task GetTokenBalanceAsync(Address owner)
+ {
+ return (await TokenContractStub.GetBalance.CallAsync(new GetBalanceInput
+ {
+ Owner = owner,
+ Symbol = "ELF"
+ })).Balance;
+ }
+
+ private async Task GetContractBalanceAsync(Address address)
+ {
+ var input = new GetContractBalanceInput {Address = address};
+ return (await NftSaleStub.GetContractBalance.CallAsync(input)).Value;
+ }
+
+ private async Task SendTokenTo(Address address)
+ {
+ await TokenContractStub.Transfer.SendAsync(new TransferInput
+ {
+ To = address,
+ Symbol = "ELF",
+ Amount = 100
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/test/Protobuf/message/authority_info.proto b/templates/NftSaleContract/test/Protobuf/message/authority_info.proto
new file mode 100644
index 0000000..eee3496
--- /dev/null
+++ b/templates/NftSaleContract/test/Protobuf/message/authority_info.proto
@@ -0,0 +1,10 @@
+syntax = "proto3";
+
+import "aelf/core.proto";
+
+option csharp_namespace = "AElf.Contracts.NftSale";
+
+message AuthorityInfo {
+ aelf.Address contract_address = 1;
+ aelf.Address owner_address = 2;
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/test/Protobuf/reference/acs12.proto b/templates/NftSaleContract/test/Protobuf/reference/acs12.proto
new file mode 100644
index 0000000..e6ead4b
--- /dev/null
+++ b/templates/NftSaleContract/test/Protobuf/reference/acs12.proto
@@ -0,0 +1,35 @@
+/**
+ * AElf Standards ACS12(User Contract Standard)
+ *
+ * Used to manage user contract.
+ */
+syntax = "proto3";
+
+package acs12;
+
+import public "aelf/options.proto";
+import public "google/protobuf/empty.proto";
+import public "google/protobuf/wrappers.proto";
+import "aelf/core.proto";
+
+option (aelf.identity) = "acs12";
+option csharp_namespace = "AElf.Standards.ACS12";
+
+service UserContract{
+
+}
+
+//Specified method fee for user contract.
+message UserContractMethodFees {
+ // List of fees to be charged.
+ repeated UserContractMethodFee fees = 2;
+ // Optional based on the implementation of SetConfiguration method.
+ bool is_size_fee_free = 3;
+}
+
+message UserContractMethodFee {
+ // The token symbol of the method fee.
+ string symbol = 1;
+ // The amount of fees to be charged.
+ int64 basic_fee = 2;
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/test/Protobuf/reference/aedpos_contract.proto b/templates/NftSaleContract/test/Protobuf/reference/aedpos_contract.proto
new file mode 100644
index 0000000..37965d8
--- /dev/null
+++ b/templates/NftSaleContract/test/Protobuf/reference/aedpos_contract.proto
@@ -0,0 +1,507 @@
+/**
+ * AEDPoS contract.
+ */
+syntax = "proto3";
+
+package AEDPoS;
+
+import "aelf/options.proto";
+import "aelf/core.proto";
+import "Protobuf/message/authority_info.proto";
+import "google/protobuf/empty.proto";
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
+
+option csharp_namespace = "AElf.Contracts.Consensus.AEDPoS";
+
+service AEDPoSContract {
+
+ option (aelf.csharp_state) = "AElf.Contracts.Consensus.AEDPoS.AEDPoSContractState";
+
+ // Initialize the consensus contract.
+ rpc InitialAElfConsensusContract (InitialAElfConsensusContractInput) returns (google.protobuf.Empty) {
+ }
+
+ // Initializes the consensus information in the first round.
+ rpc FirstRound (Round) returns (google.protobuf.Empty) {
+ }
+
+ // Update consensus information.
+ rpc UpdateValue (UpdateValueInput) returns (google.protobuf.Empty) {
+ }
+
+ // Update consensus information, create a new round.
+ rpc NextRound (NextRoundInput) returns (google.protobuf.Empty) {
+ }
+
+ // Update consensus information, create a new term.
+ rpc NextTerm (NextTermInput) returns (google.protobuf.Empty) {
+ }
+
+ // Update consensus tiny block information.
+ rpc UpdateTinyBlockInformation (TinyBlockInput) returns (google.protobuf.Empty) {
+ }
+
+ // Set the maximum count of miners, by default, is unlimited.
+ // If you want to control the count of miners, you need to set it through parliament.
+ rpc SetMaximumMinersCount (google.protobuf.Int32Value) returns (google.protobuf.Empty) {
+ }
+
+ // The authority information for SetMaximumMinersCount, by default, is governed by parliament.
+ rpc ChangeMaximumMinersCountController (AuthorityInfo) returns (google.protobuf.Empty) {
+ }
+
+ // Set miner increase interval
+ rpc SetMinerIncreaseInterval (google.protobuf.Int64Value) returns (google.protobuf.Empty){
+ }
+
+ // Election Contract can notify AEDPoS Contract to aware candidate replacement happened.
+ rpc RecordCandidateReplacement (RecordCandidateReplacementInput) returns (google.protobuf.Empty) {
+ }
+
+ // Get the list of current miners.
+ rpc GetCurrentMinerList (google.protobuf.Empty) returns (MinerList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the list of current miners (hexadecimal format).
+ rpc GetCurrentMinerPubkeyList (google.protobuf.Empty) returns (PubkeyList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the list of current miners and current round number.
+ rpc GetCurrentMinerListWithRoundNumber (google.protobuf.Empty) returns (MinerListWithRoundNumber) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get information of the round according to round number.
+ rpc GetRoundInformation (google.protobuf.Int64Value) returns (Round) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the current round number.
+ rpc GetCurrentRoundNumber (google.protobuf.Empty) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the current round information.
+ rpc GetCurrentRoundInformation (google.protobuf.Empty) returns (Round) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the previous round information.
+ rpc GetPreviousRoundInformation (google.protobuf.Empty) returns (Round) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the current term number.
+ rpc GetCurrentTermNumber (google.protobuf.Empty) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the welfare reward the current term.
+ rpc GetCurrentTermMiningReward (google.protobuf.Empty) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the list of miners according to term number.
+ rpc GetMinerList (GetMinerListInput) returns (MinerList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the list of miner in previous term.
+ rpc GetPreviousMinerList (google.protobuf.Empty) returns (MinerList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the amount of mined blocks in previous term.
+ rpc GetMinedBlocksOfPreviousTerm (google.protobuf.Empty) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the miner that produces the next block.
+ rpc GetNextMinerPubkey (google.protobuf.Empty) returns (google.protobuf.StringValue) {
+ option (aelf.is_view) = true;
+ }
+
+ // Check to see if the account address is on the miner list for the current round.
+ rpc IsCurrentMiner (aelf.Address) returns (google.protobuf.BoolValue) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the left time before the next election takes effects (seconds).
+ rpc GetNextElectCountDown (google.protobuf.Empty) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get term information according term number.
+ rpc GetPreviousTermInformation (google.protobuf.Int64Value) returns (Round) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get random hash (Keep this for compatibility).
+ rpc GetRandomHash (google.protobuf.Int64Value) returns (aelf.Hash) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the maximum of tiny blocks produced by a miner each round.
+ rpc GetMaximumBlocksCount (google.protobuf.Empty) returns (google.protobuf.Int32Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the maximum count of miners.
+ rpc GetMaximumMinersCount (google.protobuf.Empty) returns (google.protobuf.Int32Value) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the authority information for SetMaximumMinersCount.
+ rpc GetMaximumMinersCountController (google.protobuf.Empty) returns (AuthorityInfo) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get miner increase interval
+ rpc GetMinerIncreaseInterval (google.protobuf.Empty) returns (google.protobuf.Int64Value){
+ option (aelf.is_view) = true;
+ }
+
+ // Gets the list of miners in the main chain.
+ rpc GetMainChainCurrentMinerList (google.protobuf.Empty) returns (MinerList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Get the list of miners in the previous term.
+ rpc GetPreviousTermMinerPubkeyList (google.protobuf.Empty) returns (PubkeyList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the current mining reward for each block.
+ rpc GetCurrentMiningRewardPerBlock (google.protobuf.Empty) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+}
+
+message InitialAElfConsensusContractInput {
+ // Whether not to change the term.
+ bool is_term_stay_one = 1;
+ // Is a side chain.
+ bool is_side_chain = 2;
+ // The number of seconds per term.
+ int64 period_seconds = 3;
+ // The interval second that increases the number of miners.
+ int64 miner_increase_interval = 4;
+}
+
+message UpdateValueInput {
+ // Calculated from current in value.
+ aelf.Hash out_value = 1;
+ // Calculated from current in value and signatures of previous round.
+ aelf.Hash signature = 2;
+ // To ensure the values to update will be apply to correct round by comparing round id.
+ int64 round_id = 3;
+ // Publish previous in value for validation previous signature and previous out value.
+ aelf.Hash previous_in_value = 4;
+ // The actual mining time, miners must fill actual mining time when they do the mining.
+ google.protobuf.Timestamp actual_mining_time = 5;
+ // The supposed order of mining for the next round.
+ int32 supposed_order_of_next_round = 6;
+ // The tuning order of mining for the next round, miner public key -> order.
+ map tune_order_information = 7;
+ // The encrypted pieces of InValue.
+ map encrypted_pieces = 8;
+ // The decrypted pieces of InValue.
+ map decrypted_pieces = 9;
+ // The amount of produced blocks.
+ int64 produced_blocks = 10;
+ // The InValue in the previous round, miner public key -> InValue.
+ map miners_previous_in_values = 11;
+ // The irreversible block height that miner recorded.
+ int64 implied_irreversible_block_height = 12;
+ // The random number.
+ bytes random_number = 13;
+}
+
+message MinerList {
+ // The miners public key list.
+ repeated bytes pubkeys = 1;
+}
+
+message PubkeyList {
+ // The miners public key list.
+ repeated string pubkeys = 1;
+}
+
+message TermNumberLookUp {
+ // Term number -> Round number.
+ map map = 1;
+}
+
+message Candidates {
+ // The candidate public keys.
+ repeated bytes pubkeys = 1;
+}
+
+message Round {
+ // The round number.
+ int64 round_number = 1;
+ // Current miner information, miner public key -> miner information.
+ map real_time_miners_information = 2;
+ // The round number on the main chain
+ int64 main_chain_miners_round_number = 3;
+ // The time from chain start to current round (seconds).
+ int64 blockchain_age = 4;
+ // The miner public key that produced the extra block in the previous round.
+ string extra_block_producer_of_previous_round = 5;
+ // The current term number.
+ int64 term_number = 6;
+ // The height of the confirmed irreversible block.
+ int64 confirmed_irreversible_block_height = 7;
+ // The round number of the confirmed irreversible block.
+ int64 confirmed_irreversible_block_round_number = 8;
+ // Is miner list different from the the miner list in the previous round.
+ bool is_miner_list_just_changed = 9;
+ // The round id, calculated by summing block producers’ expecting time (second).
+ int64 round_id_for_validation = 10;
+}
+
+message MinerInRound {
+ // The order of the miner producing block.
+ int32 order = 1;
+ // Is extra block producer in the current round.
+ bool is_extra_block_producer = 2;
+ // Generated by secret sharing and used for validation between miner.
+ aelf.Hash in_value = 3;
+ // Calculated from current in value.
+ aelf.Hash out_value = 4;
+ // Calculated from current in value and signatures of previous round.
+ aelf.Hash signature = 5;
+ // The expected mining time.
+ google.protobuf.Timestamp expected_mining_time = 6;
+ // The amount of produced blocks.
+ int64 produced_blocks = 7;
+ // The amount of missed time slots.
+ int64 missed_time_slots = 8;
+ // The public key of this miner.
+ string pubkey = 9;
+ // The InValue of the previous round.
+ aelf.Hash previous_in_value = 10;
+ // The supposed order of mining for the next round.
+ int32 supposed_order_of_next_round = 11;
+ // The final order of mining for the next round.
+ int32 final_order_of_next_round = 12;
+ // The actual mining time, miners must fill actual mining time when they do the mining.
+ repeated google.protobuf.Timestamp actual_mining_times = 13;
+ // The encrypted pieces of InValue.
+ map encrypted_pieces = 14;
+ // The decrypted pieces of InValue.
+ map decrypted_pieces = 15;
+ // The amount of produced tiny blocks.
+ int64 produced_tiny_blocks = 16;
+ // The irreversible block height that current miner recorded.
+ int64 implied_irreversible_block_height = 17;
+}
+
+message AElfConsensusHeaderInformation {
+ // The sender public key.
+ bytes sender_pubkey = 1;
+ // The round information.
+ Round round = 2;
+ // The behaviour of consensus.
+ AElfConsensusBehaviour behaviour = 3;
+}
+
+message AElfConsensusHint {
+ // The behaviour of consensus.
+ AElfConsensusBehaviour behaviour = 1;
+ // The round id.
+ int64 round_id = 2;
+ // The previous round id.
+ int64 previous_round_id = 3;
+}
+
+enum AElfConsensusBehaviour {
+ UPDATE_VALUE = 0;
+ NEXT_ROUND = 1;
+ NEXT_TERM = 2;
+ NOTHING = 3;
+ TINY_BLOCK = 4;
+}
+
+message AElfConsensusTriggerInformation {
+ // The miner public key.
+ bytes pubkey = 1;
+ // The InValue for current round.
+ aelf.Hash in_value = 2;
+ // The InValue for previous round.
+ aelf.Hash previous_in_value = 3;
+ // The behaviour of consensus.
+ AElfConsensusBehaviour behaviour = 4;
+ // The encrypted pieces of InValue.
+ map encrypted_pieces = 5;
+ // The decrypted pieces of InValue.
+ map decrypted_pieces = 6;
+ // The revealed InValues.
+ map revealed_in_values = 7;
+ // The random number.
+ bytes random_number = 8;
+}
+
+message TermInfo {
+ int64 term_number = 1;
+ int64 round_number = 2;
+}
+
+message MinerListWithRoundNumber {
+ // The list of miners.
+ MinerList miner_list = 1;
+ // The round number.
+ int64 round_number = 2;
+}
+
+message TinyBlockInput {
+ // The round id.
+ int64 round_id = 1;
+ // The actual mining time.
+ google.protobuf.Timestamp actual_mining_time = 2;
+ // Count of blocks currently produced
+ int64 produced_blocks = 3;
+ // The random number.
+ bytes random_number = 4;
+}
+
+message VoteMinersCountInput {
+ int32 miners_count = 1;
+ int64 amount = 2;
+}
+
+message ConsensusInformation {
+ bytes value = 1;
+}
+
+message GetMinerListInput {
+ // The term number.
+ int64 term_number = 1;
+}
+
+message RandomNumberRequestInformation {
+ int64 target_round_number = 1;// The random hash is likely generated during this round.
+ int64 order = 2;
+ int64 expected_block_height = 3;
+}
+
+message HashList {
+ repeated aelf.Hash values = 1;
+}
+
+message LatestPubkeyToTinyBlocksCount {
+ // The miner public key.
+ string pubkey = 1;
+ // The count of blocks the miner produced.
+ int64 blocks_count = 2;
+}
+
+message IrreversibleBlockFound {
+ option (aelf.is_event) = true;
+ // The irreversible block height found.
+ int64 irreversible_block_height = 1 [(aelf.is_indexed) = true];
+}
+
+message IrreversibleBlockHeightUnacceptable {
+ option (aelf.is_event) = true;
+ // Distance to the height of the last irreversible block.
+ int64 distance_to_irreversible_block_height = 1;
+}
+
+message MiningInformationUpdated {
+ option (aelf.is_event) = true;
+ // The miner public key.
+ string pubkey = 1 [(aelf.is_indexed) = true];
+ // The current block time.
+ google.protobuf.Timestamp mining_time = 2 [(aelf.is_indexed) = true];
+ // The behaviour of consensus.
+ string behaviour = 3 [(aelf.is_indexed) = true];
+ // The current block height.
+ int64 block_height = 4 [(aelf.is_indexed) = true];
+ // The previous block hash.
+ aelf.Hash previous_block_hash = 5 [(aelf.is_indexed) = true];
+}
+
+message SecretSharingInformation {
+ option (aelf.is_event) = true;
+ // The previous round information.
+ Round previous_round = 1 [(aelf.is_indexed) = true];
+ // The current round id.
+ int64 current_round_id = 2;
+ // The previous round id.
+ int64 previous_round_id = 3;
+}
+
+message MiningRewardGenerated {
+ option (aelf.is_event) = true;
+ // The number of term the mining reward is generated.
+ int64 term_number = 1 [(aelf.is_indexed) = true];
+ // The amount of mining reward.
+ int64 amount = 2;
+}
+
+message MinerReplaced {
+ option (aelf.is_event) = true;
+ // The new miner public key.
+ string new_miner_pubkey = 1;
+}
+
+message RecordCandidateReplacementInput {
+ string old_pubkey = 1;
+ string new_pubkey = 2;
+}
+
+// For compatibility, it is the same as the Round with the addition of the random_number property.
+message NextRoundInput {
+ // The round number.
+ int64 round_number = 1;
+ // Current miner information, miner public key -> miner information.
+ map real_time_miners_information = 2;
+ // The round number on the main chain
+ int64 main_chain_miners_round_number = 3;
+ // The time from chain start to current round (seconds).
+ int64 blockchain_age = 4;
+ // The miner public key that produced the extra block in the previous round.
+ string extra_block_producer_of_previous_round = 5;
+ // The current term number.
+ int64 term_number = 6;
+ // The height of the confirmed irreversible block.
+ int64 confirmed_irreversible_block_height = 7;
+ // The round number of the confirmed irreversible block.
+ int64 confirmed_irreversible_block_round_number = 8;
+ // Is miner list different from the the miner list in the previous round.
+ bool is_miner_list_just_changed = 9;
+ // The round id, calculated by summing block producers’ expecting time (second).
+ int64 round_id_for_validation = 10;
+ // The random number.
+ bytes random_number = 11;
+}
+
+// For compatibility, it is the same as the Round with the addition of the random_number property.
+message NextTermInput {
+ // The round number.
+ int64 round_number = 1;
+ // Current miner information, miner public key -> miner information.
+ map real_time_miners_information = 2;
+ // The round number on the main chain
+ int64 main_chain_miners_round_number = 3;
+ // The time from chain start to current round (seconds).
+ int64 blockchain_age = 4;
+ // The miner public key that produced the extra block in the previous round.
+ string extra_block_producer_of_previous_round = 5;
+ // The current term number.
+ int64 term_number = 6;
+ // The height of the confirmed irreversible block.
+ int64 confirmed_irreversible_block_height = 7;
+ // The round number of the confirmed irreversible block.
+ int64 confirmed_irreversible_block_round_number = 8;
+ // Is miner list different from the the miner list in the previous round.
+ bool is_miner_list_just_changed = 9;
+ // The round id, calculated by summing block producers’ expecting time (second).
+ int64 round_id_for_validation = 10;
+ // The random number.
+ bytes random_number = 11;
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/test/Protobuf/stub/nft_sale_contract.proto b/templates/NftSaleContract/test/Protobuf/stub/nft_sale_contract.proto
new file mode 100644
index 0000000..373588e
--- /dev/null
+++ b/templates/NftSaleContract/test/Protobuf/stub/nft_sale_contract.proto
@@ -0,0 +1,114 @@
+syntax = "proto3";
+
+import "aelf/core.proto";
+import "aelf/options.proto";
+import "google/protobuf/empty.proto";
+import "google/protobuf/wrappers.proto";
+import "Protobuf/reference/acs12.proto";
+// The namespace of this class
+option csharp_namespace = "AElf.Contracts.NftSale";
+
+service NftSale {
+ // The name of the state class the smart contract is going to use to access blockchain state
+ option (aelf.csharp_state) = "AElf.Contracts.NftSale.NftSaleState";
+ option (aelf.base) = "Protobuf/reference/acs12.proto";
+
+ rpc Initialize (NftPrice) returns (google.protobuf.Empty) {
+ }
+
+ rpc Purchase (PurchaseInput) returns (google.protobuf.Empty) {
+ }
+
+ rpc SetPriceAndSymbol (NftPrice) returns (google.protobuf.Empty) {
+ }
+
+ rpc GetPrice (google.protobuf.Empty) returns (Price) {
+ option (aelf.is_view) = true;
+ }
+
+ rpc GetSymbol (google.protobuf.Empty) returns (NftSymbol) {
+ option (aelf.is_view) = true;
+ }
+
+ rpc Withdraw (google.protobuf.Int64Value) returns (google.protobuf.Empty) {
+ }
+
+ rpc Deposit (DepositeInput) returns (google.protobuf.Empty) {
+ }
+
+ rpc TransferOwnership (aelf.Address) returns (google.protobuf.Empty) {
+ }
+
+ rpc GetContractBalance (GetContractBalanceInput) returns (google.protobuf.Int64Value) {
+ option (aelf.is_view) = true;
+ }
+
+ rpc GetOwner (google.protobuf.Empty) returns (google.protobuf.StringValue) {
+ option (aelf.is_view) = true;
+ }
+}
+
+message DepositeInput{
+ string symbol = 1; // nft
+ int64 amount = 2; // token amount
+}
+
+message Price {
+ string symbol = 1; // token type
+ int64 amount = 2; // token price
+}
+
+message NftSymbol {
+ string symbol = 1;
+}
+
+message NftPrice{
+ // // The token symbol price
+ Price price = 1;
+ // The token symbol
+ string symbol = 2;
+}
+
+message PurchaseInput {
+ // The token symbol to transfer.
+ string symbol = 2;
+ // The amount to to transfer.
+ int64 amount = 3;
+ // The memo.
+ string memo = 4;
+ // Transaction information
+ Price price = 5;
+}
+
+message SaleNft {
+ option (aelf.is_event) = true;
+ // The destination address of the transferred token.
+ aelf.Address to = 2 [(aelf.is_indexed) = true];
+ // The symbol of the transferred token.
+ string symbol = 3 [(aelf.is_indexed) = true];
+ // The amount of the transferred token.
+ int64 amount = 4;
+ // The memo.
+ string memo = 5;
+}
+
+message GetContractBalanceInput{
+ aelf.Address address = 1;
+}
+
+// An event that will be emitted from contract method call when Withdraw is called.
+message WithdrawEvent {
+ option (aelf.is_event) = true;
+ int64 amount = 1;
+ aelf.Address from = 2;
+ aelf.Address to = 3;
+}
+
+// An event that will be emitted from contract method call when Deposit is called.
+message DepositEvent {
+ option (aelf.is_event) = true;
+ int64 amount = 1;
+ string symbol = 2;
+ aelf.Address from = 3;
+ aelf.Address to = 4;
+}
diff --git a/templates/NftSaleContract/test/Protobuf/stub/token_contract.proto b/templates/NftSaleContract/test/Protobuf/stub/token_contract.proto
new file mode 100644
index 0000000..d632247
--- /dev/null
+++ b/templates/NftSaleContract/test/Protobuf/stub/token_contract.proto
@@ -0,0 +1,894 @@
+/**
+ * MultiToken contract.
+ */
+syntax = "proto3";
+
+package token;
+
+import "aelf/core.proto";
+import "aelf/options.proto";
+import "google/protobuf/empty.proto";
+import "google/protobuf/wrappers.proto";
+
+option csharp_namespace = "AElf.Contracts.MultiToken";
+
+service TokenContract {
+ // Create a new token.
+ rpc Create (CreateInput) returns (google.protobuf.Empty) {
+ }
+
+ // Issuing some amount of tokens to an address is the action of increasing that addresses balance
+ // for the given token. The total amount of issued tokens must not exceed the total supply of the token
+ // and only the issuer (creator) of the token can issue tokens.
+ // Issuing tokens effectively increases the circulating supply.
+ rpc Issue (IssueInput) returns (google.protobuf.Empty) {
+ }
+
+ // Transferring tokens simply is the action of transferring a given amount of tokens from one address to another.
+ // The origin or source address is the signer of the transaction.
+ // The balance of the sender must be higher than the amount that is transferred.
+ rpc Transfer (TransferInput) returns (google.protobuf.Empty) {
+ }
+
+ // The TransferFrom action will transfer a specified amount of tokens from one address to another.
+ // For this operation to succeed the from address needs to have approved (see allowances) enough tokens
+ // to Sender of this transaction. If successful the amount will be removed from the allowance.
+ rpc TransferFrom (TransferFromInput) returns (google.protobuf.Empty) {
+ }
+
+ // The approve action increases the allowance from the Sender to the Spender address,
+ // enabling the Spender to call TransferFrom.
+ rpc Approve (ApproveInput) returns (google.protobuf.Empty) {
+ }
+
+ rpc BatchApprove (BatchApproveInput) returns (google.protobuf.Empty) {
+ }
+
+ // This is the reverse operation for Approve, it will decrease the allowance.
+ rpc UnApprove (UnApproveInput) returns (google.protobuf.Empty) {
+ }
+
+ // This method can be used to lock tokens.
+ rpc Lock (LockInput) returns (google.protobuf.Empty) {
+ }
+
+ // This is the reverse operation of locking, it un-locks some previously locked tokens.
+ rpc Unlock (UnlockInput) returns (google.protobuf.Empty) {
+ }
+
+ // This action will burn the specified amount of tokens, removing them from the token’s Supply.
+ rpc Burn (BurnInput) returns (google.protobuf.Empty) {
+ }
+
+ // Set the primary token of side chain.
+ rpc SetPrimaryTokenSymbol (SetPrimaryTokenSymbolInput) returns (google.protobuf.Empty) {
+ }
+
+ // This interface is used for cross-chain transfer.
+ rpc CrossChainTransfer (CrossChainTransferInput) returns (google.protobuf.Empty) {
+ }
+
+ // This method is used to receive cross-chain transfers.
+ rpc CrossChainReceiveToken (CrossChainReceiveTokenInput) returns (google.protobuf.Empty) {
+ }
+
+ // The side chain creates tokens.
+ rpc CrossChainCreateToken(CrossChainCreateTokenInput) returns (google.protobuf.Empty) {
+ }
+
+ // When the side chain is started, the side chain is initialized with the parent chain information.
+ rpc InitializeFromParentChain (InitializeFromParentChainInput) returns (google.protobuf.Empty) {
+ }
+
+ // Handle the transaction fees charged by ChargeTransactionFees.
+ rpc ClaimTransactionFees (TotalTransactionFeesMap) returns (google.protobuf.Empty) {
+ }
+
+ // Used to collect transaction fees.
+ rpc ChargeTransactionFees (ChargeTransactionFeesInput) returns (ChargeTransactionFeesOutput) {
+ }
+
+ rpc ChargeUserContractTransactionFees(ChargeTransactionFeesInput) returns(ChargeTransactionFeesOutput){
+
+ }
+
+ // Check the token threshold.
+ rpc CheckThreshold (CheckThresholdInput) returns (google.protobuf.Empty) {
+ }
+
+ // Initialize coefficients of every type of tokens supporting charging fee.
+ rpc InitialCoefficients (google.protobuf.Empty) returns (google.protobuf.Empty){
+ }
+
+ // Processing resource token received.
+ rpc DonateResourceToken (TotalResourceTokensMaps) returns (google.protobuf.Empty) {
+ }
+
+ // A transaction resource fee is charged to implement the ACS8 standards.
+ rpc ChargeResourceToken (ChargeResourceTokenInput) returns (google.protobuf.Empty) {
+ }
+
+ // Verify that the resource token are sufficient.
+ rpc CheckResourceToken (google.protobuf.Empty) returns (google.protobuf.Empty) {
+ }
+
+ // Set the list of tokens to pay transaction fees.
+ rpc SetSymbolsToPayTxSizeFee (SymbolListToPayTxSizeFee) returns (google.protobuf.Empty){
+ }
+
+ // Update the coefficient of the transaction fee calculation formula.
+ rpc UpdateCoefficientsForSender (UpdateCoefficientsInput) returns (google.protobuf.Empty) {
+ }
+
+ // Update the coefficient of the transaction fee calculation formula.
+ rpc UpdateCoefficientsForContract (UpdateCoefficientsInput) returns (google.protobuf.Empty) {
+ }
+
+ // This method is used to initialize the governance organization for some functions,
+ // including: the coefficient of the user transaction fee calculation formula,
+ // the coefficient of the contract developer resource fee calculation formula, and the side chain rental fee.
+ rpc InitializeAuthorizedController (google.protobuf.Empty) returns (google.protobuf.Empty){
+ }
+
+ rpc AddAddressToCreateTokenWhiteList (aelf.Address) returns (google.protobuf.Empty) {
+ }
+ rpc RemoveAddressFromCreateTokenWhiteList (aelf.Address) returns (google.protobuf.Empty) {
+ }
+
+ rpc SetTransactionFeeDelegations (SetTransactionFeeDelegationsInput) returns (SetTransactionFeeDelegationsOutput){
+ }
+
+ rpc RemoveTransactionFeeDelegator (RemoveTransactionFeeDelegatorInput) returns (google.protobuf.Empty){
+ }
+
+ rpc RemoveTransactionFeeDelegatee (RemoveTransactionFeeDelegateeInput) returns (google.protobuf.Empty){
+ }
+
+ rpc SetSymbolAlias (SetSymbolAliasInput) returns (google.protobuf.Empty){
+ }
+
+ // Get all delegatees' address of delegator from input
+ rpc GetTransactionFeeDelegatees (GetTransactionFeeDelegateesInput) returns (GetTransactionFeeDelegateesOutput) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query token information.
+ rpc GetTokenInfo (GetTokenInfoInput) returns (TokenInfo) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query native token information.
+ rpc GetNativeTokenInfo (google.protobuf.Empty) returns (TokenInfo) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query resource token information.
+ rpc GetResourceTokenInfo (google.protobuf.Empty) returns (TokenInfoList) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the balance at the specified address.
+ rpc GetBalance (GetBalanceInput) returns (GetBalanceOutput) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the account's allowance for other addresses
+ rpc GetAllowance (GetAllowanceInput) returns (GetAllowanceOutput) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the account's available allowance for other addresses
+ rpc GetAvailableAllowance (GetAllowanceInput) returns (GetAllowanceOutput) {
+ option (aelf.is_view) = true;
+ }
+
+ // Check whether the token is in the whitelist of an address,
+ // which can be called TransferFrom to transfer the token under the condition of not being credited.
+ rpc IsInWhiteList (IsInWhiteListInput) returns (google.protobuf.BoolValue) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the information for a lock.
+ rpc GetLockedAmount (GetLockedAmountInput) returns (GetLockedAmountOutput) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the address of receiving token in cross-chain transfer.
+ rpc GetCrossChainTransferTokenContractAddress (GetCrossChainTransferTokenContractAddressInput) returns (aelf.Address) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the name of the primary Token.
+ rpc GetPrimaryTokenSymbol (google.protobuf.Empty) returns (google.protobuf.StringValue) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the coefficient of the transaction fee calculation formula.
+ rpc GetCalculateFeeCoefficientsForContract (google.protobuf.Int32Value) returns (CalculateFeeCoefficients) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query the coefficient of the transaction fee calculation formula.
+ rpc GetCalculateFeeCoefficientsForSender (google.protobuf.Empty) returns (CalculateFeeCoefficients) {
+ option (aelf.is_view) = true;
+ }
+
+ // Query tokens that can pay transaction fees.
+ rpc GetSymbolsToPayTxSizeFee (google.protobuf.Empty) returns (SymbolListToPayTxSizeFee){
+ option (aelf.is_view) = true;
+ }
+
+ // Query the hash of the last input of ClaimTransactionFees.
+ rpc GetLatestTotalTransactionFeesMapHash (google.protobuf.Empty) returns (aelf.Hash){
+ option (aelf.is_view) = true;
+ }
+
+ // Query the hash of the last input of DonateResourceToken.
+ rpc GetLatestTotalResourceTokensMapsHash (google.protobuf.Empty) returns (aelf.Hash){
+ option (aelf.is_view) = true;
+ }
+ rpc IsTokenAvailableForMethodFee (google.protobuf.StringValue) returns (google.protobuf.BoolValue) {
+ option (aelf.is_view) = true;
+ }
+ rpc GetReservedExternalInfoKeyList (google.protobuf.Empty) returns (StringList) {
+ option (aelf.is_view) = true;
+ }
+
+ rpc GetTransactionFeeDelegationsOfADelegatee(GetTransactionFeeDelegationsOfADelegateeInput) returns(TransactionFeeDelegations){
+ option (aelf.is_view) = true;
+ }
+
+ rpc GetTokenAlias (google.protobuf.StringValue) returns (google.protobuf.StringValue) {
+ option (aelf.is_view) = true;
+ }
+
+ rpc GetSymbolByAlias (google.protobuf.StringValue) returns (google.protobuf.StringValue) {
+ option (aelf.is_view) = true;
+ }
+}
+
+message TokenInfo {
+ // The symbol of the token.f
+ string symbol = 1;
+ // The full name of the token.
+ string token_name = 2;
+ // The current supply of the token.
+ int64 supply = 3;
+ // The total supply of the token.
+ int64 total_supply = 4;
+ // The precision of the token.
+ int32 decimals = 5;
+ // The address that has permission to issue the token.
+ aelf.Address issuer = 6;
+ // A flag indicating if this token is burnable.
+ bool is_burnable = 7;
+ // The chain id of the token.
+ int32 issue_chain_id = 8;
+ // The amount of issued tokens.
+ int64 issued = 9;
+ // The external information of the token.
+ ExternalInfo external_info = 10;
+ // The address that owns the token.
+ aelf.Address owner = 11;
+}
+
+message ExternalInfo {
+ map value = 1;
+}
+
+message CreateInput {
+ // The symbol of the token.
+ string symbol = 1;
+ // The full name of the token.
+ string token_name = 2;
+ // The total supply of the token.
+ int64 total_supply = 3;
+ // The precision of the token
+ int32 decimals = 4;
+ // The address that has permission to issue the token.
+ aelf.Address issuer = 5;
+ // A flag indicating if this token is burnable.
+ bool is_burnable = 6;
+ // A whitelist address list used to lock tokens.
+ repeated aelf.Address lock_white_list = 7;
+ // The chain id of the token.
+ int32 issue_chain_id = 8;
+ // The external information of the token.
+ ExternalInfo external_info = 9;
+ // The address that owns the token.
+ aelf.Address owner = 10;
+}
+
+message SetPrimaryTokenSymbolInput {
+ // The symbol of the token.
+ string symbol = 1;
+}
+
+message IssueInput {
+ // The token symbol to issue.
+ string symbol = 1;
+ // The token amount to issue.
+ int64 amount = 2;
+ // The memo.
+ string memo = 3;
+ // The target address to issue.
+ aelf.Address to = 4;
+}
+
+message TransferInput {
+ // The receiver of the token.
+ aelf.Address to = 1;
+ // The token symbol to transfer.
+ string symbol = 2;
+ // The amount to to transfer.
+ int64 amount = 3;
+ // The memo.
+ string memo = 4;
+}
+
+message LockInput {
+ // The one want to lock his token.
+ aelf.Address address = 1;
+ // Id of the lock.
+ aelf.Hash lock_id = 2;
+ // The symbol of the token to lock.
+ string symbol = 3;
+ // a memo.
+ string usage = 4;
+ // The amount of tokens to lock.
+ int64 amount = 5;
+}
+
+message UnlockInput {
+ // The one want to un-lock his token.
+ aelf.Address address = 1;
+ // Id of the lock.
+ aelf.Hash lock_id = 2;
+ // The symbol of the token to un-lock.
+ string symbol = 3;
+ // a memo.
+ string usage = 4;
+ // The amount of tokens to un-lock.
+ int64 amount = 5;
+}
+
+message TransferFromInput {
+ // The source address of the token.
+ aelf.Address from = 1;
+ // The destination address of the token.
+ aelf.Address to = 2;
+ // The symbol of the token to transfer.
+ string symbol = 3;
+ // The amount to transfer.
+ int64 amount = 4;
+ // The memo.
+ string memo = 5;
+}
+
+message ApproveInput {
+ // The address that allowance will be increased.
+ aelf.Address spender = 1;
+ // The symbol of token to approve.
+ string symbol = 2;
+ // The amount of token to approve.
+ int64 amount = 3;
+}
+message BatchApproveInput {
+ repeated ApproveInput value = 1;
+}
+
+message UnApproveInput {
+ // The address that allowance will be decreased.
+ aelf.Address spender = 1;
+ // The symbol of token to un-approve.
+ string symbol = 2;
+ // The amount of token to un-approve.
+ int64 amount = 3;
+}
+
+message BurnInput {
+ // The symbol of token to burn.
+ string symbol = 1;
+ // The amount of token to burn.
+ int64 amount = 2;
+}
+
+message ChargeResourceTokenInput {
+ // Collection of charge resource token, Symbol->Amount.
+ map cost_dic = 1;
+ // The sender of the transaction.
+ aelf.Address caller = 2;
+}
+
+message TransactionFeeBill {
+ // The transaction fee dictionary, Symbol->fee.
+ map fees_map = 1;
+}
+
+message TransactionFreeFeeAllowanceBill {
+ // The transaction free fee allowance dictionary, Symbol->fee.
+ map free_fee_allowances_map = 1;
+}
+
+message CheckThresholdInput {
+ // The sender of the transaction.
+ aelf.Address sender = 1;
+ // The threshold to set, Symbol->Threshold.
+ map symbol_to_threshold = 2;
+ // Whether to check the allowance.
+ bool is_check_allowance = 3;
+}
+
+message GetTokenInfoInput {
+ // The symbol of token.
+ string symbol = 1;
+}
+
+message GetBalanceInput {
+ // The symbol of token.
+ string symbol = 1;
+ // The target address of the query.
+ aelf.Address owner = 2;
+}
+
+message GetBalanceOutput {
+ // The symbol of token.
+ string symbol = 1;
+ // The target address of the query.
+ aelf.Address owner = 2;
+ // The balance of the owner.
+ int64 balance = 3;
+}
+
+message GetAllowanceInput {
+ // The symbol of token.
+ string symbol = 1;
+ // The address of the token owner.
+ aelf.Address owner = 2;
+ // The address of the spender.
+ aelf.Address spender = 3;
+}
+
+message GetAllowanceOutput {
+ // The symbol of token.
+ string symbol = 1;
+ // The address of the token owner.
+ aelf.Address owner = 2;
+ // The address of the spender.
+ aelf.Address spender = 3;
+ // The amount of allowance.
+ int64 allowance = 4;
+}
+
+message CrossChainTransferInput {
+ // The receiver of transfer.
+ aelf.Address to = 1;
+ // The symbol of token.
+ string symbol = 2;
+ // The amount of token to transfer.
+ int64 amount = 3;
+ // The memo.
+ string memo = 4;
+ // The destination chain id.
+ int32 to_chain_id = 5;
+ // The chain id of the token.
+ int32 issue_chain_id = 6;
+}
+
+message CrossChainReceiveTokenInput {
+ // The source chain id.
+ int32 from_chain_id = 1;
+ // The height of the transfer transaction.
+ int64 parent_chain_height = 2;
+ // The raw bytes of the transfer transaction.
+ bytes transfer_transaction_bytes = 3;
+ // The merkle path created from the transfer transaction.
+ aelf.MerklePath merkle_path = 4;
+}
+
+message IsInWhiteListInput {
+ // The symbol of token.
+ string symbol = 1;
+ // The address to check.
+ aelf.Address address = 2;
+}
+
+message SymbolToPayTxSizeFee{
+ // The symbol of token.
+ string token_symbol = 1;
+ // The charge weight of primary token.
+ int32 base_token_weight = 2;
+ // The new added token charge weight. For example, the charge weight of primary Token is set to 1.
+ // The newly added token charge weight is set to 10. If the transaction requires 1 unit of primary token,
+ // the user can also pay for 10 newly added tokens.
+ int32 added_token_weight = 3;
+}
+
+message SymbolListToPayTxSizeFee{
+ // Transaction fee token information.
+ repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 1;
+}
+
+message ChargeTransactionFeesInput {
+ // The method name of transaction.
+ string method_name = 1;
+ // The contract address of transaction.
+ aelf.Address contract_address = 2;
+ // The amount of transaction size fee.
+ int64 transaction_size_fee = 3;
+ // Transaction fee token information.
+ repeated SymbolToPayTxSizeFee symbols_to_pay_tx_size_fee = 4;
+}
+
+message ChargeTransactionFeesOutput {
+ // Whether the charge was successful.
+ bool success = 1;
+ // The charging information.
+ string charging_information = 2;
+}
+
+message CallbackInfo {
+ aelf.Address contract_address = 1;
+ string method_name = 2;
+}
+
+message ExtraTokenListModified {
+ option (aelf.is_event) = true;
+ // Transaction fee token information.
+ SymbolListToPayTxSizeFee symbol_list_to_pay_tx_size_fee = 1;
+}
+
+message GetLockedAmountInput {
+ // The address of the lock.
+ aelf.Address address = 1;
+ // The token symbol.
+ string symbol = 2;
+ // The id of the lock.
+ aelf.Hash lock_id = 3;
+}
+
+message GetLockedAmountOutput {
+ // The address of the lock.
+ aelf.Address address = 1;
+ // The token symbol.
+ string symbol = 2;
+ // The id of the lock.
+ aelf.Hash lock_id = 3;
+ // The locked amount.
+ int64 amount = 4;
+}
+
+message TokenInfoList {
+ // List of token information.
+ repeated TokenInfo value = 1;
+}
+
+message GetCrossChainTransferTokenContractAddressInput {
+ // The chain id.
+ int32 chainId = 1;
+}
+
+message CrossChainCreateTokenInput {
+ // The chain id of the chain on which the token was created.
+ int32 from_chain_id = 1;
+ // The height of the transaction that created the token.
+ int64 parent_chain_height = 2;
+ // The transaction that created the token.
+ bytes transaction_bytes = 3;
+ // The merkle path created from the transaction that created the transaction.
+ aelf.MerklePath merkle_path = 4;
+}
+
+message InitializeFromParentChainInput {
+ // The amount of resource.
+ map resource_amount = 1;
+ // The token contract addresses.
+ map registered_other_token_contract_addresses = 2;
+ // The creator the side chain.
+ aelf.Address creator = 3;
+}
+
+message UpdateCoefficientsInput {
+ // The specify pieces gonna update.
+ repeated int32 piece_numbers = 1;
+ // Coefficients of one single type.
+ CalculateFeeCoefficients coefficients = 2;
+}
+
+enum FeeTypeEnum {
+ READ = 0;
+ STORAGE = 1;
+ WRITE = 2;
+ TRAFFIC = 3;
+ TX = 4;
+}
+
+message CalculateFeePieceCoefficients {
+ // Coefficients of one single piece.
+ // The first char is its type: liner / power.
+ // The second char is its piece upper bound.
+ repeated int32 value = 1;
+}
+
+message CalculateFeeCoefficients {
+ // The resource fee type, like READ, WRITE, etc.
+ int32 fee_token_type = 1;
+ // Coefficients of one single piece.
+ repeated CalculateFeePieceCoefficients piece_coefficients_list = 2;
+}
+
+message AllCalculateFeeCoefficients {
+ // The coefficients of fee Calculation.
+ repeated CalculateFeeCoefficients value = 1;
+}
+
+message TotalTransactionFeesMap
+{
+ // Token dictionary that charge transaction fee, Symbol->Amount.
+ map value = 1;
+ // The hash of the block processing the transaction.
+ aelf.Hash block_hash = 2;
+ // The height of the block processing the transaction.
+ int64 block_height = 3;
+}
+
+message TotalResourceTokensMaps {
+ // Resource tokens to charge.
+ repeated ContractTotalResourceTokens value = 1;
+ // The hash of the block processing the transaction.
+ aelf.Hash block_hash = 2;
+ // The height of the block processing the transaction.
+ int64 block_height = 3;
+}
+
+message ContractTotalResourceTokens {
+ // The contract address.
+ aelf.Address contract_address = 1;
+ // Resource tokens to charge.
+ TotalResourceTokensMap tokens_map = 2;
+}
+
+message TotalResourceTokensMap
+{
+ // Resource token dictionary, Symbol->Amount.
+ map value = 1;
+}
+
+message StringList {
+ repeated string value = 1;
+}
+
+message TransactionFeeDelegations{
+ // delegation, symbols and its' amount
+ map delegations = 1;
+ // height when added
+ int64 block_height = 2;
+ //Whether to pay transaction fee continuously
+ bool isUnlimitedDelegate = 3;
+}
+
+message TransactionFeeDelegatees{
+ map delegatees = 1;
+}
+
+message SetTransactionFeeDelegationsInput {
+ // the delegator address
+ aelf.Address delegator_address = 1;
+ // delegation, symbols and its' amount
+ map delegations = 2;
+}
+
+message SetTransactionFeeDelegationsOutput {
+ bool success = 1;
+}
+
+message RemoveTransactionFeeDelegatorInput{
+ // the delegator address
+ aelf.Address delegator_address = 1;
+}
+
+message RemoveTransactionFeeDelegateeInput {
+ // the delegatee address
+ aelf.Address delegatee_address = 1;
+}
+
+message GetTransactionFeeDelegationsOfADelegateeInput {
+ aelf.Address delegatee_address = 1;
+ aelf.Address delegator_address = 2;
+}
+
+message GetTransactionFeeDelegateesInput {
+ aelf.Address delegator_address = 1;
+}
+
+message GetTransactionFeeDelegateesOutput {
+ repeated aelf.Address delegatee_addresses = 1;
+}
+
+message SetSymbolAliasInput {
+ string symbol = 1;
+ string alias = 2;
+}
+
+// Events
+
+message Transferred {
+ option (aelf.is_event) = true;
+ // The source address of the transferred token.
+ aelf.Address from = 1 [(aelf.is_indexed) = true];
+ // The destination address of the transferred token.
+ aelf.Address to = 2 [(aelf.is_indexed) = true];
+ // The symbol of the transferred token.
+ string symbol = 3 [(aelf.is_indexed) = true];
+ // The amount of the transferred token.
+ int64 amount = 4;
+ // The memo.
+ string memo = 5;
+}
+
+message Approved {
+ option (aelf.is_event) = true;
+ // The address of the token owner.
+ aelf.Address owner = 1 [(aelf.is_indexed) = true];
+ // The address that allowance be increased.
+ aelf.Address spender = 2 [(aelf.is_indexed) = true];
+ // The symbol of approved token.
+ string symbol = 3 [(aelf.is_indexed) = true];
+ // The amount of approved token.
+ int64 amount = 4;
+}
+
+message UnApproved {
+ option (aelf.is_event) = true;
+ // The address of the token owner.
+ aelf.Address owner = 1 [(aelf.is_indexed) = true];
+ // The address that allowance be decreased.
+ aelf.Address spender = 2 [(aelf.is_indexed) = true];
+ // The symbol of un-approved token.
+ string symbol = 3 [(aelf.is_indexed) = true];
+ // The amount of un-approved token.
+ int64 amount = 4;
+}
+
+message Burned
+{
+ option (aelf.is_event) = true;
+ // The address who wants to burn token.
+ aelf.Address burner = 1 [(aelf.is_indexed) = true];
+ // The symbol of burned token.
+ string symbol = 2 [(aelf.is_indexed) = true];
+ // The amount of burned token.
+ int64 amount = 3;
+}
+
+message ChainPrimaryTokenSymbolSet {
+ option (aelf.is_event) = true;
+ // The symbol of token.
+ string token_symbol = 1;
+}
+
+message CalculateFeeAlgorithmUpdated {
+ option (aelf.is_event) = true;
+ // All calculate fee coefficients after modification.
+ AllCalculateFeeCoefficients all_type_fee_coefficients = 1;
+}
+
+message RentalCharged {
+ option (aelf.is_event) = true;
+ // The symbol of rental fee charged.
+ string symbol = 1;
+ // The amount of rental fee charged.
+ int64 amount = 2;
+ // The payer of rental fee.
+ aelf.Address payer = 3;
+ // The receiver of rental fee.
+ aelf.Address receiver = 4;
+}
+
+message RentalAccountBalanceInsufficient {
+ option (aelf.is_event) = true;
+ // The symbol of insufficient rental account balance.
+ string symbol = 1;
+ // The balance of the account.
+ int64 amount = 2;
+}
+
+message TokenCreated {
+ option (aelf.is_event) = true;
+ // The symbol of the token.
+ string symbol = 1;
+ // The full name of the token.
+ string token_name = 2;
+ // The total supply of the token.
+ int64 total_supply = 3;
+ // The precision of the token.
+ int32 decimals = 4;
+ // The address that has permission to issue the token.
+ aelf.Address issuer = 5;
+ // A flag indicating if this token is burnable.
+ bool is_burnable = 6;
+ // The chain id of the token.
+ int32 issue_chain_id = 7;
+ // The external information of the token.
+ ExternalInfo external_info = 8;
+ // The address that owns the token.
+ aelf.Address owner = 9;
+}
+
+message Issued {
+ option (aelf.is_event) = true;
+ // The symbol of issued token.
+ string symbol = 1;
+ // The amount of issued token.
+ int64 amount = 2;
+ // The memo.
+ string memo = 3;
+ // The issued target address.
+ aelf.Address to = 4;
+}
+
+message CrossChainTransferred {
+ option (aelf.is_event) = true;
+ // The source address of the transferred token.
+ aelf.Address from = 1;
+ // The destination address of the transferred token.
+ aelf.Address to = 2;
+ // The symbol of the transferred token.
+ string symbol = 3;
+ // The amount of the transferred token.
+ int64 amount = 4;
+ // The memo.
+ string memo = 5;
+ // The destination chain id.
+ int32 to_chain_id = 6;
+ // The chain id of the token.
+ int32 issue_chain_id = 7;
+}
+
+message CrossChainReceived {
+ option (aelf.is_event) = true;
+ // The source address of the transferred token.
+ aelf.Address from = 1;
+ // The destination address of the transferred token.
+ aelf.Address to = 2;
+ // The symbol of the received token.
+ string symbol = 3;
+ // The amount of the received token.
+ int64 amount = 4;
+ // The memo.
+ string memo = 5;
+ // The destination chain id.
+ int32 from_chain_id = 6;
+ // The chain id of the token.
+ int32 issue_chain_id = 7;
+ // The parent chain height of the transfer transaction.
+ int64 parent_chain_height = 8;
+ // The id of transfer transaction.
+ aelf.Hash transfer_transaction_id =9;
+}
+
+message TransactionFeeDelegationAdded {
+ option (aelf.is_event) = true;
+ aelf.Address delegator = 1 [(aelf.is_indexed) = true];
+ aelf.Address delegatee = 2 [(aelf.is_indexed) = true];
+ aelf.Address caller = 3 [(aelf.is_indexed) = true];
+}
+
+message TransactionFeeDelegationCancelled {
+ option (aelf.is_event) = true;
+ aelf.Address delegator = 1 [(aelf.is_indexed) = true];
+ aelf.Address delegatee = 2 [(aelf.is_indexed) = true];
+ aelf.Address caller = 3 [(aelf.is_indexed) = true];
+}
+
+message SymbolAliasAdded {
+ option (aelf.is_event) = true;
+ string symbol = 1 [(aelf.is_indexed) = true];
+ string alias = 2 [(aelf.is_indexed) = true];
+}
+
+message SymbolAliasDeleted {
+ option (aelf.is_event) = true;
+ string symbol = 1 [(aelf.is_indexed) = true];
+ string alias = 2 [(aelf.is_indexed) = true];
+}
\ No newline at end of file
diff --git a/templates/NftSaleContract/test/_Setup.cs b/templates/NftSaleContract/test/_Setup.cs
new file mode 100644
index 0000000..7be808c
--- /dev/null
+++ b/templates/NftSaleContract/test/_Setup.cs
@@ -0,0 +1,34 @@
+using AElf.Contracts.MultiToken;
+using AElf.Cryptography.ECDSA;
+using AElf.Testing.TestBase;
+
+namespace AElf.Contracts.NftSale
+{
+ // The Module class load the context required for unit testing
+ public class Module : ContractTestModule
+ {
+
+ }
+
+ // The TestBase class inherit ContractTestBase class, it defines Stub classes and gets instances required for unit testing
+ public class TestBase : ContractTestBase
+ {
+ // The Stub class for unit testing
+ internal readonly NftSaleContainer.NftSaleStub NftSaleStub;
+ internal readonly TokenContractContainer.TokenContractStub TokenContractStub;
+ // A key pair that can be used to interact with the contract instance
+ private ECKeyPair DefaultKeyPair => Accounts[0].KeyPair;
+
+ public TestBase()
+ {
+ NftSaleStub = GetNftSaleContractStub(DefaultKeyPair);
+ TokenContractStub = GetTester(TokenContractAddress, DefaultKeyPair);
+ }
+
+ private NftSaleContainer.NftSaleStub GetNftSaleContractStub(ECKeyPair senderKeyPair)
+ {
+ return GetTester(ContractAddress, senderKeyPair);
+ }
+ }
+
+}
\ No newline at end of file