Introduction
The current IOTA Client libraries have various ways of doing things, sometimes with a lot of overhead for trivial, repeating tasks that often don’t need most of the flexibility of the lower level API. This document proposes a first version of a common standard for high level functionality in the Client libraries that should work the same regardless of the language it’s implemented in or the specific implementation of the IOTA protocol now or in the (near) future. The idea behind this proposal is that the functions are generic enough for a longer term usage regardless of the underlying technology. The same function calls can be used pre- and post-coordicide even though they may do a lot of different things under the hood.
This is a work-in-progress design document that should be converted into a RFC once the initial model has been fleshed out. This proposal comes with another proposal for a generic, more future proof address format covered in a seperate topic.
Overall requirements
The higher level functionality should:
- Work the same cross-implementation in terms of naming and arguments, regardless of the language of the client library (with the exception of language specific code style enforcing a certain way of naming functions).
- Use as many sensible defaults as possible so that the least amount of configuration has to be done to work with it. For example depth can default to 3, MWM to 14.
- Be implemented in a way that the basic functionality is always the same regardless of the protocol below it, so that it will still work in the exact same way even if we change the core protocol, transaction format or have coordicide implemented.
- Have specific arguments well described in the documentation, for example “MWM stands for Minimum Weight Magnitude” is not enough, it should be obvious for any coder, regardless of IOTA experience, what a certain argument like MWM or Depth means.
- Cover the most common use cases when working with IOTA as defined in the functionality below. If a certain functionality is not available for a given language (like for example MAM in Python) it should still implement the higher level function but with a NotImplemented error raised when called and a remark in the documentation.
- Live next to the existing codebase, they are shortcuts that can be optionally used for faster development. The existing API can still be used for cases where you can’t use the higher level functionality.
- Be as generic as possible, you should not even have to know about trytes in most cases in order to use it.
- Have a seperate section in the documentation so people don’t get lost in all the details when starting out; Keeping it simple.
- Have sensible error messages on exceptions describing exactly what’s wrong in a language any coder can understand or in case of specific IOTA related terms a reference to more information.
- Provide a summary of the wrapped functionality so in case of custom solutions that don’t fit within the higher level functionality that can be used as a base instead. This can just be a small, maybe even hidden by default, remark in the docs.
- Be extended once new functionality arrives, a good first additional candidate would be a higher level implementation for MAM once the new version is released.
Implementation proposal
Initializer
Initialization can be the same or very similar to the current API.
I prefer a very simple syntax for this that can easily be remembered so I prefer aliasing composeAPI in JS/Go to just IOTA and in Java
IotaAPI api = new IotaAPI.Builder().protocol("https").host("nodes.devnet.thetangle.org").port("443").build();
Can probably be a lot less bloated and easier to understand/remember if you can just do:
IotaAPI api = new IOTA(‘https://nodes.iota.org:443’);
Or even simpler with automated node selection and possible quorum:
IotaAPI api = new IOTA();
If we need to do value transactions we need to provide a seed:
IotaAPI api = new IOTA(‘SEED9999...’);
Optionally we can add additional default values for further calls here as well, for example default_mwm
, since in most cases you always have the same MWM everywhere in your code, by using a default we can prevent code duplication in calling those functions.
We should add other optional arguments as well for state persistence when using the Account module internally, but those can have defaults like a iota.db
state file or a path for state objects with the preferred adapter for storage. We need to make sure the state of the library is well documented with the higher level functionality so that people are very aware that they need both the state file and the seed in order to use it.
Seed is an optional argument for the initializer and is only used for value transactions, it’s optional for data only transactions.
Node quorum functionality is high on the wishlist. Provide some sensible defaults and if multiple nodes are given or no node at all it will pick X nodes and compares the results.
GetNodeInfo()
Already available in most libraries but useful for debugging purposes.
Only relevant for single node implementations without quorum.
Implementation as follows:
api = new IOTA()
print(api.getNodeInfo())
This returns an object with the node information as provided by the IRI API.
(private) NodeInSync([max_milestone_difference=0])
Returns True if the chosen node is in sync, False if not. It has one optional argument, max_milestone_difference with a default of 0, that is used to decide if a node is in sync or not. With the default value of 0 it is only marked as in sync if the milestone on the node is exactly the last issued milestone, but if you provide for example 2 it can be behind 2 milestones and still return True.
Mostly used internally for node quorum, maybe we should not even expose this in the public API.
Open question: What would be a sensible default for this, 0 might return out of sync if you call it at just the right time triggering a False without a good reason, but 1 or 2 is technically not fully in sync either.
SendValue(address, amount, [message], [tag])
A shortcut function to send a value transaction to a single address. Address can either be an address with checksum or a valid CDA (needs implemented checks that raise errors if invalid). The message is automatically converted to Trytes from a string, the tag only accepts a trytestring and raises an exception with a reference to what a valid tag can be if this is not passed.
Implementation as follows:
api = new IOTA()
bundle = api.sendValue(‘iota://ADDRESS9999...’, 1000, message=’Hi’)
print(bundle.hash)
In case of async this can work similar but with promises.
SendData(address, message, [tag], [encoding=’ascii’])
A shortcut function to send ASCII data to the tangle. In case the message is too long it will automatically split over several transactions in a bundle to the same address. The message is automatically converted to Trytes without the user having to know about this process.
Open question: This is what we currently support in the client libraries with asciiToTrytes; We currently do not have a built-in, generic way to send Unicode. This means that special characters (Japanese, Chinese, Russian, Emoji’s, etc) are not supported with this. Since we currently don’t have a generic way to send Unicode data I don’t think we should implement this yet in the higher level functions; But I do think we need a standardised way to do this a.s.a.p. Any thoughts on this? I’d suggest to just convert the UTF-8 bytes to trytes and use that as the standard. We might even want to think about replacing the default encoding method of AsciiToTrytes with this (even though it has overhead for just ascii data) so that messages are always in the same encoding by default regardless of characters/encoding used.
Implementation as follows:
api = new IOTA()
bundle = api.sendData(‘iota://ADDRESS9999...’, message=’Hey there’)
print(bundle.hash)
In case of async this can work similar but with promises.
GetData(address/bundle hash/transaction hash, [offset=0], [limit=100], [encoding=’ascii’])
A shortcut function to receive data from the tangle. This function will automatically stitch together multiple transactions from the same bundle in the right order to support longer messages and handle the automatically detected encoding if none is given. By default it returns the last 100 messages but limit and offset can be adjusted to your needs to get more or other sections.
Implementation as follows:
api = new IOTA()
messages = api.fetchData(‘iota://ADDRESS9999...’)
for message in messages:
print(message) # This is just a string
In case of async this can work similar but with promises.
GenerateAddress([timeout_at], [multi_use], [expected_amount], [security_level=2], [legacy_format=False])
Returns a magnet link address, a new standard for sharing addresses with a checksum based on both address and optional arguments. This magnet link can be compatible with CDA’s or regular addresses making it forward compatible with a possible implementation of re-usable addresses.
Implementation as follows:
api = new IOTA(‘SEED9999...’);
addr = api.generateAddress()
# addr = ‘iota://ADDRESS99999...’
Or for a CDA:
api = new IOTA(‘SEED9999...’);
addr = api.generateAddress(timeout_at=’2020-01-01 00:00:00’, multi_use=False)
# addr = ‘iota://MBR….D/?timeout_at=1548337187&multi_use=0’
If you provide the legacy_format argument you’ll get the raw address with checksum (not the magnet link); this can be used in the transition period between old addresses and magnet links to still allow people to generate addresses for exchanges for example.
GetBalance()
Returns the balance in iota for the given seed, internally it checks the balances for addresses belonging to this seed and adds them up, based on the state.
Implementation as follows:
api = new IOTA(‘SEED9999...’);
balance = api.getBalance()
# balance = 1337
GetBalanceForAddress(address)
Returns the balance in iota for the given address.
Implementation as follows:
api = new IOTA();
balance = api.getBalanceForAddress(‘iota://ADDRESS9999...’)
# balance = 1337
Suggetions, thoughts and improvements to this proposal are very welcome!