In this post, we will take a deep-dive into developing custom transaction types with our Core GTI (Generic Transaction Interface) technology. add the new develop and deploy distributed application to a blockchain by introducing custom transaction types combined with modules (from previous post). You will: You will learn how to Implement a new transaction type structure and introduce custom fields Implement a new custom transaction builder class Implement a general transaction handler that hooks our newly created transaction type with the blockchain protocol Use existing API interfaces to interact with core blockchain and new transaction types. In the previous post, , we addressed the developing of distributed blockchain applications in general and demonstrated how to apply recommended architectural best practices. Related posts: Part 1 — The Introduction To Blockchain Development with ARK All the steps in this deep-dive are supported with working code samples and access to example implementation. Look for // source-link: comment line in the code snippets below. This post will be followed by separate tutorials and hands-on workshops where we will build a fully working blockchain application (backend and frontend). Read more about our state-of-the-art technology at or check out our public source-code repositories at ARK is an open-source project. https://ark.io https://github.com/ArkEcosystem. A Short Introduction To Custom Transactions — The Core GTI Engine The basic premise of GTI is to provide an easy way to implement and include new transaction types in Core without the need to tediously modify more complex parts of it. By putting some logic behind custom transaction types, we feel this is a much better and more powerful approach to develop stronger use-cases than with conventional smart contracts. GTI was initially designed to assist our developers make implementations of new transaction types easier, maintainable, and standardized across the board. So, What Can Be Built With Custom Transactions? You are probably thinking: “I can develop a custom transaction, introduce new fields, and then add them to the blockchain. Ok, sounds cool, but how does this help me develop better applications and services in general?” Well, let me answer this: “ Most of the real-world interactions are transaction-based/event-based. Having the ability to add your custom functionality on top of existing distributed ledger technology with ease and reuse its benefits — the possibilities are endless. ” For example, we can build: audit log, tracking functionalities (GDPR, ISO27001 support by default), supply chain management transactions, e.g. following specific parts through receiving, manufacturing, quality assurance, packaging, distribution, maintenance, and disposal over the entire product life cycle, healthcare, e.g. tracking of events, combined with storage of large medical data sets via IPFS network, IoT network support, e.g. custom transaction for device registrations and storage of additional sensor data, gaming support, administrative role-based system governed by blockchain, anything that is done by smart contracts, without the hassle of a complex language such as Solidity or Move … and much more — the list is endless. All of the above-listed examples are transactions in the real world and can be implemented with our core GTI engine. Meaning, as a developer, you can add the new business logic to a blockchain by introducing additional custom transaction types tailored to the application. So, the next thing you need to implement is an awesome front end to support your business. Your new application becomes a light-client by default, leveraging the power of the blockchain platform in the background. By using you will be able to follow a streamlined process of creating and securing your new custom transaction type that can be and managed inside a separate core module (plugin). GTI deployed to any ARK based bridgechain A general overview of important classes supporting custom transaction development can be seen in the Class Diagram picture below. Abstract classes and methods in the class diagram are presented with text italic . To develop a custom transaction type we need to implement code-contracts defined by (the blue colored items in the class diagram above). Implementation is pretty straight forward. We override default transaction behavior and add custom business logic, by implementing the , and type classes (the green-colored items in the diagram above). We will implement the following three classes: GTI interfaces and abstract classes Transaction Builder Handler {TransactionName} — introduces your new transaction type structure Transaction {TransactionName} — implements payload building and signing Builder {TransactionName} — handles blockchain protocol and makes your new transaction a fully-fledged member Handler We will explain each of the three class types, their mechanics and purpose in the following sections. The use of the term serde throughout this document refers to the processes of transaction serialization and deserialization. 1.Implement Class BusinessRegistrationTransaction The purpose of this class is to define and implement transaction structure, fields, serde process and set schema validation rules. We need to inherit (extend) base Transaction class to follow GTI rules. (transaction serialization and deserialization) a.) Build Your New Transaction Structure Your custom transaction fields must be defined inside the transaction class. They follow the rules of the inherited class. You can introduce any number of new fields and their respectful types. All new fields will be stored in the base transaction field called . The source-code snippet below introduces custom fields with interfaces. BusinessRegistration Transaction transaction.assets IBusinessRegistrationAsset { name: ; website: ; } export interface string string // source-link: https://github.com/kovaczan/custom-transaction/blob/167bcbd5201282a6d679d9d571eed00bbc1df57c/src/interfaces.ts#L1 The defined interface makes use of new custom transaction fields stricter and is part of the serde process. Our Public API enables searching of transactions with new custom fields by design (no API changes needed). b.) Implement the serde process We need to implement custom serde methods that will take care of the serde process for our newly introduced transaction fields. Abstract methods and are defined by the base class, and are automatically called inside our custom class during the serde process. serialize() deserialize() Transaction BusinessRegistrationTransaction Transactions.Transaction { serialize(): ByteBuffer { { data } = ; businessRegistration = data.asset.businessRegistration IBusinessRegistrationAsset; nameBytes = Buffer.from(businessRegistration.name, ); websiteBytes = Buffer.from(businessRegistration.website, ); buffer = ByteBuffer(nameBytes.length + websiteBytes.length + , ); buffer.writeUint8(nameBytes.length); buffer.append(nameBytes, ); buffer.writeUint8(websiteBytes.length); buffer.append(websiteBytes, ); buffer; } deserialize(buf: ByteBuffer): { { data } = ; businessRegistration = {} IBusinessRegistrationAsset; nameLength = buf.readUint8(); businessRegistration.name = buf.readString(nameLength); websiteLength = buf.readUint8(); businessRegistration.website = buf.readString(websiteLength); data.asset = { businessRegistration }; } } export class extends public const this const as const "utf8" const "utf8" const new 2 true "hex" "hex" return public void const this const as const const // source-link: https://github.com/kovaczan/custom-transaction/blob/167bcbd5201282a6d679d9d571eed00bbc1df57c/src/transactions/BusinessRegistrationTransaction.ts#L48 c.) Define Schema Validation For The New Transaction Fields Each custom transaction is accompanied by enforced schema validation. To achieve this we must extend base and provide rules for the custom field validation (fields introduced in ). Schema is defined with and we access it by calling the method inside your new transaction class, in our case . TransactionSchema IBusinessRegistrationAsset AJV getSchema() BusinessRegistrationTransaction { (): { (schemas.transactionBaseSchema, { $ : , : [ ], : { : { : BUSINESS_REGISTRATION_TYPE }, : { : { : , : } }, : { : , : [ ], : { : { : , : [ , ], : { : { : , : , : }, : { : , : , : }, } } }, }, }, }); } } export class BusinessRegistrationTransaction extends Transactions .Transaction public static getSchema Transactions .schemas .TransactionSchema return schemas .extend id "businessRegistration" required "asset" properties type transactionType amount bignumber minimum 0 maximum 0 asset type "object" required "businessRegistration" properties businessRegistration type "object" required "name" "website" properties name type "string" minLength 3 maxLength 20 website type "string" minLength 3 maxLength 20 // source-link: https://github.com/kovaczan/custom-transaction/blob/167bcbd5201282a6d679d9d571eed00bbc1df57c/src/transactions/BusinessRegistrationTransaction.ts#L15 d.) Define BusinessRegistration Transaction TypeGroup and Type The + are used internally by Core to register a transaction. Non-core transactions have to define the typeGroup otherwise Core won’t be able to categorize them. All transactions (from the release of core v2.6) will be signed with and . By omitting the value, core will fall back to , which is the default Core group. We define t in our class, like this: typeGroup type typeGroup type typeGroup typeGroup: 1 ypeGroup + type BusinessRegistration { public typeGroup = ; public type = BUSINESS_REGISTRATION_TYPE; } export . class BusinessRegistrationTransaction extends Transactions Transaction static 1 static // other code ... // source-link: https://github.com/kovaczan/custom-transaction/blob/167bcbd5201282a6d679d9d571eed00bbc1df57c/src/transactions/BusinessRegistrationTransaction.ts#L10-L11 2.Implement The Class BusinessRegistrationBuilder implements the builder pattern. We use it to Builder class handles versioning, serde process, milestones, dynamic-fee logic and (sign, multisign, second-sign, sign with and without WIF, nonce logic). The following code-snippet shows the actual implementation of the class. This class build and sign our transaction payload. all cryptography related items Builder BusinessRegistrationBuilder Transactions.TransactionBuilder<BusinessRegistrationBuilder> { ( ) { (); .data.type = ; .data.typeGroup = ; .data.version = ; .data.fee = Utils.BigNumber.make( ); .data.amount = Utils.BigNumber.ZERO; .data.asset = { businessRegistration: {} }; } businessAsset(name: , website: ): BusinessRegistrationBuilder { .data.asset.businessRegistration = { name, website, }; ; } getStruct(): Interfaces.ITransactionData { struct: Interfaces.ITransactionData = .getStruct(); struct.amount = .data.amount; struct.asset = .data.asset; struct; } instance(): BusinessRegistrationBuilder { ; } } export class extends constructor super this 100 this 1 this 2 this "5000000000" this this public string string this return this public const super this this return protected return this // source-link: https://github.com/kovaczan/custom-transaction/blob/167bcbd5201282a6d679d9d571eed00bbc1df57c/src/builders/BusinessRegistrationBuilder.ts#L3-L33 Now that we have implemented our builder class, we can use it to build new custom transaction payloads: describe( , { Managers.configManager.setFromPreset( ); Handlers.Registry.registerTransactionHandler(BusinessRegistrationTransactionHandler); it( , { builder = BusinessRegistrationBuilder(); actual = builder .nonce( ) .fee( ) .businessAsset( , ) .sign( ); .log(actual.build().toJson()); expect(actual.build().verified).toBeTrue(); expect(actual.verify()).toBeTrue(); }); }); "Test builder" => () "testnet" "Should verify correctly" => () const new const "3" "100" "google" "www.google.com" "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire" console // source-link: https://github.com/kovaczan/custom-transaction/blob/167bcbd5201282a6d679d9d571eed00bbc1df57c/__tests__/test.test.ts#L7-L22 3.Implement Class BusinessRegistrationHandler The previous two classes,Builder and Transaction, introduced a new transaction type, implemented the serde process, and created signed transaction payload. In this part of custom transaction implementation, we will be handling verification and validation, following strict ( rules, transaction, and block processing). blockchain mechanics consensus By inheriting default behavior we enforce existing GTI rules and provide options to implement additional transaction apply logic. TransactionHandler { public getConstructor(): Transactions.TransactionConstructor { BusinessRegistrationTransaction; } export . class BusinessRegistrationTransactionHandler extends Handlers TransactionHandler return Apply logic consists of basic rules, for example, i.) check if there are enough funds in the wallet, ii.) check for duplicate transactions, iii.) if the received transaction is on the correct network (correct bridgechain), and many, many more. We will explain GTI and the role it plays in our blockchain protocol in the following sections: TransactionHandler a.) How To Define Your Custom Transaction Dependencies We must define the Transaction Type registration order if our custom transaction (e.g. ) depends on other transactions (e.g. MultiSignature )— in short, the MultiSignature transaction must be registered before ours. We define transaction dependencies by using the method call, where we return an array of dependent classes. BusinessRegistrationTransaction dependencies() { public getConstructor(): Transactions.TransactionConstructor { BusinessRegistrationTransaction; } public dependencies(): ReadonlyArray<any> { [MultiSignatureTransaction]; } ... } export . class BusinessRegistrationTransactionHandler extends Handlers TransactionHandler return return // source-link: https://github.com/kovaczan/custom-transaction/blob/167bcbd5201282a6d679d9d571eed00bbc1df57c/src/handlers/BusinessRegistrationTransactionHandler.ts#L12-L14 b.) How To Add Attributes To Global Wallets We defined custom transaction fields and structure in part 1 of this article (see above). Usually, we want to (the class). These properties need to be quickly accessible (memoization) and searchable ( ). . Implement BusinessRegistrationTransaction class add custom properties to our global state walletManager indexed We will accomplish this with the () method, where we define the keys for our wallet attributes. Keys can be set during runtime by calling method. walletAttributes wallet.setAttribute(key, value) The source-code below shows registering of a new wallet attribute with key= . We set the attribute value during the bootstrap() method call. When we are done with custom wallet attribute value changes, a reindex call is recommended on the . business walletManager.reindex(wallet) { public walletAttributes(): ReadonlyArray<string> { [ ]; } public bootstrap(connection: Database.IConnection, : State.IWalletManager): < > { transactions = connection.transactionsRepository.getAssetsByType( .getConstructor().type); ( transaction transactions) { wallet = walletManager.findByPublicKey(transaction.senderPublicKey); wallet.setAttribute<IBusinessRegistrationAsset>( , transaction.asset.businessRegistration); walletManager.reindex(wallet); } } } export . class BusinessRegistrationTransactionHandler extends Handlers TransactionHandler return "business" async walletManager Promise void const await this for const of const "business" // source-link: https://github.com/kovaczan/custom-transaction/blob/167bcbd5201282a6d679d9d571eed00bbc1df57c/src/handlers/BusinessRegistrationTransactionHandler.ts#L25-L29 c.) Tapping Into the Transaction Bootstrap Process . The process evaluates all of the transactions in the local database and applies them to the corresponding wallets. All of the amounts, votes, and other custom properties are calculated and applied to the global state — . Since our new custom transaction follows the same blockchain mechanics, we only need to implement relevant (see code snippet below) apply methods defined by the interface. Bootstrap process is run each time a core node is started walletManager BusinessRegistrationTransaction TransactionHandler { public applyToSender(transaction: Interfaces.ITransaction, : State.IWalletManager): < > { .applyToSender(transaction, walletManager); sender: State.IWallet = walletManager.findByPublicKey(transaction.data.senderPublicKey); sender.setAttribute<IBusinessRegistrationAsset>( , transaction.data.asset.businessRegistration); walletManager.reindex(sender); } public revertForSender(transaction: Interfaces.ITransaction, : State.IWalletManager): < > { .revertForSender(transaction, walletManager); sender: State.IWallet = walletManager.findByPublicKey(transaction.data.senderPublicKey); sender.forgetAttribute( ); walletManager.reindex(sender); } public applyToRecipient(transaction: Interfaces.ITransaction, : State.IWalletManager): < > { ; } public revertForRecipient(transaction: Interfaces.ITransaction, : State.IWalletManager): < > { ; } } export . class BusinessRegistrationTransactionHandler extends Handlers TransactionHandler // ... async walletManager Promise void await super const "business" async walletManager Promise void await super const "business" async walletManager Promise void return async walletManager Promise void return // source-link: https://github.com/kovaczan/custom-transaction/blob/167bcbd5201282a6d679d9d571eed00bbc1df57c/src/handlers/BusinessRegistrationTransactionHandler.ts#L92-L113 d.) How To Implement Transaction-Pool Validation The Transaction Pool serves as a temporary layer where valid and verified transactions are stored locally until it is their turn to be included in the newly forged (created) blocks. Each new custom transaction type needs to be verified and accepted by the same strict limitation rules that are enforced for our core transactions. We need to implement method (see source-code snippet below) to follow the rules and execution structure. The method is called from the core. canEnterTransactionPool() { public canEnterTransactionPool( data: Interfaces.ITransactionData, : TransactionPool.IConnection, : TransactionPool.IProcessor, ): <boolean> { ( .typeFromSenderAlreadyInPool(data, pool, processor)) { ; } ; } } export . class BusinessRegistrationTransactionHandler extends Handlers TransactionHandler // ... async pool processor Promise if this return false // check the link for more validation options TODO: return true // source-link: https://github.com/kovaczan/custom-transaction/blob/167bcbd5201282a6d679d9d571eed00bbc1df57c/src/handlers/BusinessRegistrationTransactionHandler.ts#L55-L91 4.Registration of a Newly Implemented Transaction Type Within Core The final step awaits, and it is the easiest: registration of the newly implemented type. To accomplish this, we need to get access to the core-transactions handler and call ) method (see code below). You made it. BusinessRegistrationTransaction registerTransactionHandler( register(container: Container.IContainer, options) { container.resolvePlugin<Logger.ILogger>( ).info( ); Handlers.Registry.registerTransactionHandler(BusinessRegistrationTransactionHandler); } async "logger" "Registering custom transaction" // source-link: https://github.com/KovacZan/custom-transaction/blob/master/src/plugin.ts#L11-L12 A fully working example is available for you to examine, learn and Your custom transaction type implementation is now COMPLETE. download here. e). This example is implemented as a core module (plugin). You can learn more about core modules at https://docs.ark.io/tutorials/core/plugins/how-to-write-a-core-plugin.html and is using develop branch of the Core to achieve the above results (upcoming v2.6 Cor How To Access New Transaction Types via Our Public Interfaces Our newly implemented transaction type becomes a full member of a node after the registration call — meaning we can query it via existing Public API interfaces, after the plugin is deployed on the blockchain. core . You can read more about our Public API here: https://api.ark.dev We provide twelve (12+) different programming language implementations of our , all accompanied by full cryptography protocol implementation . For more information about our SDKs ( API and crypto) refer to Seamless Integration With ARK Core: API . Simply install the SDK of your choice and start interacting with the blockchain REST https://sdk.ark.dev . We also provide a compliant package, targeting exchanges and other trusted execution environments. JSON-RPC client is meant to run inside a trusted environment. Secure JSON-RPC client: JSON-RPC Conclusion After learning the best practices and architectural approaches in of this series, we took a deep-dive into custom transaction type development with our Core GTI technology. : Part 1 We learned how to Implement a new transaction type structure Implement a new custom transaction builder class Implement a general transaction handler that hooks our newly created transaction type with the blockchain protocol Use existing API interfaces to interact with core blockchain and new transaction types. Your newly implemented transaction type can now be packed into a core module and distributed to any ARK technology-based bridgechain (API and protocol compliant). This series will be followed by separate tutorials and hands-on workshops where we will build a fully working blockchain application — backend and frontend. Stay tuned for exact dates and webinar registration links, and get involved! GET INVOLVED Here’s a quick list of what your next steps might be to get involved with ARK: Join our community on or . Slack Reddit You will get a lot of developer support on our public slack. Contribute and to our code-base earn ARK and your own cryptocurrency in three simple steps with our Deployer Create a blockchain Read our docs: , , general docs and tutorials API docs SDK docs Read our new 2019 whitepaper , Follow us on social media ( Twitter | Facebook | Reddit ), join our community ( Slack | Discord ) and stay tuned to our blog on Medium and Steemit .