Custom extrinsic formats (docs) (#1429)

* Custom extrinsic formats (docs)

* Typos

* Typos (other docs)
This commit is contained in:
Jaco Greeff
2019-09-30 14:42:06 +02:00
committed by GitHub
parent 7b2cf6dcb0
commit af682bc23b
12 changed files with 181 additions and 22 deletions
+1
View File
@@ -43,6 +43,7 @@ module.exports = {
['start/api.tx.wrap.md', 'Complex transactions'],
['start/types.basics.md', 'Type basics'],
['start/types.extend.md', 'Extending types'],
['start/extrinsic.extend.md', 'Extending extrinsics'],
['start/typescript.md', 'TypeScript interfaces'],
['start/FAQ.md', 'FAQ']
]
+2 -2
View File
@@ -33,7 +33,7 @@ const momentPrev = await api.query.timestamp.now.at(lastHdr.parentHash);
The `.at` queries are all single-shot, i.e. there are no subscription option to these, since the state for a previous block should be static. (This is true to a certain extent, i.e. when blocks have been finalized).
An additional point to take care of (briefly mentioned above), is state pruning. By default a Polkadot/Substrate node will only keep state for the last 256 blocks, unless it is explicitly run in archive mode. This means that querying state further back than the pruning period will result in an error returned from the Node. (Generaly most public RPC nodes only run with default settings, which includes aggressive state pruning)
An additional point to take care of (briefly mentioned above), is state pruning. By default a Polkadot/Substrate node will only keep state for the last 256 blocks, unless it is explicitly run in archive mode. This means that querying state further back than the pruning period will result in an error returned from the Node. (Generally most public RPC nodes only run with default settings, which includes aggressive state pruning)
## State entries
@@ -69,7 +69,7 @@ console.log(`query key: ${api.query.balances.freeBalance.key(ADDR)}`);
The `section` & `method` is an indication of where it is exposed on the API. In addition the `meta` holds an array with the metadata documentation for the entry.
The `key` endpoint requires some explanation. In the chain state, the key values (identified by the module, method & params) are hashed and this is used as a lookup. So underlying a single-shot query would utilize the `api.rpc.state.getStorage` entry, passing the output of `key` (which is a hashed representation of the values). Apart from the hashing, the API also takes care of type formatting, handling optional values and merging results accross multiple subscriptions.
The `key` endpoint requires some explanation. In the chain state, the key values (identified by the module, method & params) are hashed and this is used as a lookup. So underlying a single-shot query would utilize the `api.rpc.state.getStorage` entry, passing the output of `key` (which is a hashed representation of the values). Apart from the hashing, the API also takes care of type formatting, handling optional values and merging results across multiple subscriptions.
## Let's transact already!
+1 -1
View File
@@ -2,7 +2,7 @@
Previously we explained the concepts between `api.query`. In this section we will expand on that knowledge to introduce subscriptions (akin to what we found in `api.rpc`) to stream results from the state, as it changes between blocks.
## Subcriptions
## Subscriptions
As in the case with `api.rpc` subscriptions, query subscriptions follow exactly the same form - an actual call is augmented with a callback to return the current state value that is updated as the underlying value changes. As an example, we can extend on what we had previously -
+1 -1
View File
@@ -29,7 +29,7 @@ The result for this call (we will deal with subscriptions in a short while), is
Despite the single-line format of `signAndSend`, there is a lot happening under the hood (and all of this can be manually provided) -
- Based on the sender, the API will retrieve the `system.accountNonce` to determine the next nonce to use
- The API will retrieve the current block hash and use it to create a mortal transaction, i.e. the transaction will only be valid for a limited number of blocks (by default this is 5 mins at 6s blocktimes)
- The API will retrieve the current block hash and use it to create a mortal transaction, i.e. the transaction will only be valid for a limited number of blocks (by default this is 5 mins at 6s block times)
- It will construct a payload and sign this, this includes the `genesisHash`, the `blockHash` for the start of the mortal era as well as the current chain `specVersion`
- The transaction is submitted to the node
+2 -2
View File
@@ -1,6 +1,6 @@
# Complex transactions
Up till now we have focussed on the base operation of transactions. There are however some more complex operations that deserve some more information, for instance when doing either democracy proposals or excuting sudo calls, in both these cases the transaction wraps a call or proposal to be evaluated.
Up till now we have focussed on the base operation of transactions. There are however some more complex operations that deserve some more information, for instance when doing either democracy proposals or executing sudo calls, in both these cases the transaction wraps a call or proposal to be evaluated.
## Sudo use
@@ -41,4 +41,4 @@ In the above example, all we need to provide is a the fields for the `ValidatorP
## Understanding types
As has been very apparent in all the preceding sections, the management of types is what allows the API to communicate with the node. Most values are in a [binary SCALE-encoded format](https://github.com/paritytech/parity-scale-codec) and it is the reponsibility of the API is to encode and decode these. In the next section we will [take a look at what interfaces the API provides around types](types.basics.md).
As has been very apparent in all the preceding sections, the management of types is what allows the API to communicate with the node. Most values are in a [binary SCALE-encoded format](https://github.com/paritytech/parity-scale-codec) and it is the responsibility of the API is to encode and decode these. In the next section we will [take a look at what interfaces the API provides around types](types.basics.md).
+1 -1
View File
@@ -16,7 +16,7 @@ Additionally the metadata also provides information on [events](../substrate/eve
## Types
The metadata defiends the calls with all the type names used in the various interfaces. At the moment (this is undergoing investigations and could improve in future versions of metadata), this also means that the types between the API and the node need to be aligned. For instance, by default Substrate defines a `BlockNumber` type as a `u32` and the API follows the Substrate defaults - if a chain has a different definitions, the API needs to be aware of this so it can actually decode (and encode) the type.
The metadata defines the calls with all the type names used in the various interfaces. At the moment (this is undergoing investigations and could improve in future versions of metadata), this also means that the types between the API and the node need to be aligned. For instance, by default Substrate defines a `BlockNumber` type as a `u32` and the API follows the Substrate defaults - if a chain has a different definitions, the API needs to be aware of this so it can actually decode (and encode) the type.
At this point just be aware of it, we will touch on types, custom chains and their impacts in a later section.
+1 -1
View File
@@ -15,7 +15,7 @@ const api = await ApiPromise.create({ provider: wsProvider });
console.log(api.genesisHash.toHex());
```
We will have some explanation on the ES2015 syntax used next, but just a small note on the above - where other code is included (or just some previous boilerplate is used), you will see `...` in most of the examples. This is not due to lazyness, but rather just to keep things straight and to the point.
We will have some explanation on the ES2015 syntax used next, but just a small note on the above - where other code is included (or just some previous boilerplate is used), you will see `...` in most of the examples. This is not due to laziness, but rather just to keep things straight and to the point.
## ES2015 Usage and examples
+158
View File
@@ -0,0 +1,158 @@
# Extending extrinsics
On some chains, the need may arise to customize the extrinsic format. In this section we will explain what extrinsics and their payloads contain, explain how signed extensions work and provide a walk through of an advanced use-case where extrinsics are customized for a specific Substrate chain.
## Extensions
In Substrate (which forms the base of Polkadot and a number of custom chains), an extrinsic has a specific version, flag to indicating if it has been signed, the address, signature, extra data as well as the actual method with params. In addition, the signature is generated over the call and may include values that are not encoded into the final extrinsic.
For instance, with version 3 extrinsics, the signature payload contains the runtime spec version as well as the `genesisHash` and `blockHash` (the latter being equivalent to the `genesisHash` in case of immortal transactions), however while these 3 fields are signed together with the method data, they do not actually appear in the extrinsic itself. Rather the chain executing the transaction will retrieve this information, add it to the method data and compare the signatures thereof.
So in both the cases of the `genesisHash` and `specVersion`, if these do not match the signer version, the transaction won't be accepted - however the extrinsic doesn't explicitly carry this information in the data being transmitted, rather only implicitly as part of the signature. And it always forms part of the validation.
## Default extrinsics
With the above in-mind, the extrinsic format is explicitly defined as having the following structure in Substrate -
```rust
pub struct UncheckedExtrinsic<Address, Call, Signature, Extra>
where Extra: SignedExtension
{
pub signature: Option<(Address, Signature, Extra)>,
pub function: Call,
}
```
The `Option` is here encoded such that is conveys both the extrinsic version `0x03` for Substrate 2.x chains (`0x01` for Substrate 1.x chains) as well as a high-bit that indicates if the transaction is signed or unsigned. (For unsigned the `signature` details therefore does not appear). The `SignedExtension` part defined both the data in the actual extrinsic as well as data, i.e. `AdditionalSigned`, that appears in the payload for the signature, but is not explicitly contained in the extrinsic.
The default `SignedExtension` for Substrate 2.x with extrinsic version 3 is defined as follow -
```rust
pub type SignedExtra = (
system::CheckVersion<Runtime>,
system::CheckGenesis<Runtime>,
system::CheckEra<Runtime>,
system::CheckNonce<Runtime>,
system::CheckWeight<Runtime>,
balances::TakeFees<Runtime>,
contracts::CheckBlockGasLimit<Runtime>,
);
```
Some of these are only checks, requiring no additional data in the payload or extrinsic itself, i.e. the contracts checks do exactly that. It only applies checks and invalidates when those checks are invalid. This is a powerful concept, for instance on the [initial Kusama chain this was used to limit the types of calls allowed](https://github.com/paritytech/polkadot/blob/f52c714ec3411eea58647d0f5176f4eb81660188/runtime/src/lib.rs#L117-L139).
## Extension deep-dive
For each of the default extensions, we will take a look through to understand the implications of the specific extension and how it relates to both the data contained in the extrinsic as well as the signature payload.
- `system::CheckVersion` - This checks that the spec version matches between the extrinsic and the chain. It takes no parameters which means that these is no explicit data in the extrinsic format for this field, however it has `type AdditionalSigned = u32` which means that a `u32` containing the runtime spec version is part of the signature payload.
- `system::CheckGenesis` - This checks that the `genesisHash` matches between extrinsic and chain. Like the previous check, no additional data is added to the extrinsic, however with `type AdditionalSigned = T::Hash`, the `genesisHash` is part of the signature payload.
- `system::CheckEra` - This checks the era (mortal or immortal) for the transaction being sent. It checks both the `era: Era` as part of the actual extrinsic and the `blockHash` via the `type AdditionalSigned = T::Hash`. This means that the extrinsic era is both in the data being signed and the extrinsic itself, while the `blockHash` the era applies to is only available in the signature payload.
- `system::CheckNonce` - This checks the nonce for the sending account. Unlike the preceding checks, it has no payload-specific data, however the `nonce: Compact<T::Index>` (`Index` is default `u32`) it applicable to both the extrinsic and therefore the actual signature payload as well.
- `system::CheckWeight` - This checks the weight and length of the block and ensure that it does not exceed the limits. It does not have any specific data attached to either the extrinsic nor payload, but rather just does calculations based on the weights and type of transaction received.
- `balances::TakeFees` - Consumes fees proportional to the length and weight of the transaction. It operates on the `fee: Compact<T::Balance>`, which means this value is included in both the extrinsic and subsequent payload being signed.
- `contracts::CheckBlockGasLimit` - As explained briefly above, this extension add not data, however ensures that the transaction does not exceeds the block gas limit.
## Extrinsic and signature payloads
With the above extension, the following formats for the extrinsic and payloads are the outcome of the application of the signed extension. For the extrinsic the following data is [always encoded for v3 extrinsics](https://github.com/polkadot-js/api/blob/8b0ef159c05bcb5d9b664546d0e7289e79b5c9d5/packages/types/src/primitive/Extrinsic/v3/Extrinsic.ts#L27) -
```js
class ExtrinsicV3 extends Struct {
constructor (value) {
super({
signer: 'Address',
signature: 'Signature',
era: 'ExtrinsicEra', // extra via system::CheckEra
nonce: 'Compact<Index>', // extra via system::CheckNonce
tip: 'Compact<Balance>', // extra via balances::TakeFees
method: 'Call'
}, value);
}
...
```
The signature payload will contain the same information as the extrinsic, with the following [additional information](https://github.com/polkadot-js/api/blob/8b0ef159c05bcb5d9b664546d0e7289e79b5c9d5/packages/types/src/primitive/Extrinsic/v3/ExtrinsicPayload.ts#L33) as expected by the `AdditionalSigned` portions of the extensions -
```js
class ExtrinsicPayloadV3 extends Struct {
constructor (value) {
super({
method: 'Bytes',
era: 'ExtrinsicEra', // extra via system::CheckEra
nonce: 'Compact<Index>', // extra via system::CheckNonce
tip: 'Compact<Balance>', // extra via balances::TakeFees
specVersion: 'u32', // additional via system::CheckVersion
genesisHash: 'Hash', // additional via system::CheckGenesis
blockHash: 'Hash' // additional via system::CheckEra
}, value);
}
...
```
As per the above structures, it means that both the extrinsic sent on-chain as well as the data being signed to generate the signature is tied by the hip based on the logic the chain expects via `SignedExtension`. The API is only aware of the version of the extrinsic being used on-chain (it determines this on connection) and therefore only knows about the specific logic that has been coded for the extrinsic version.
## Extending existing or implementing new
When the API encodes or decodes an extrinsic, it uses the first `Option` byte to determine the version. Once it has this value, it will create a specific extrinsic via `createType('ExtrinsicV3', value)`. This means that at any point, you can supply your own version of either the `Extrinsic` or `ExtrinsicPayload` and you can do so via 2 avenues -
- If you are extending/replacing the existing version, you can inject your own types for both `ExtrinsicV3` and `ExtrinsicV3Payload` (assuming you are replacing v3)
- If you are adding a new version, you can add a handler for both `ExtrinsicUnknown` and `ExtrinsicPayloadUnknown`. These will be constructed when the version the API is aware of does not match with the on-chain version.
While we will not provide a full example of all the code here, the above links will show the existing implementations. However, assuming we have a chain where neither the nonce or tip is applicable (or we just don't care) and we are ignoring the check to the runtime versioning.
Additionally assuming that we have made the required `SignedExtension` updates by removing `system::CheckVersion`, `system::CheckNonce` and `balances::TakeFees`, we can do the following -
```js
...
class OwnExtrinsic extends Struct {
constructor (value) {
super({
signer: 'Address',
signature: 'Signature',
era: 'ExtrinsicEra', // extra via system::CheckEra
method: 'Call'
}, value);
}
...
}
class OwnExtrinsicPayload extends Struct {
constructor (value) {
super({
method: 'Bytes',
era: 'ExtrinsicEra', // extra via system::CheckEra
genesisHash: 'Hash', // additional via system::CheckGenesis
blockHash: 'Hash' // additional via system::CheckEra
}, value);
}
...
// signing logic needs to be included, as per existing
}
...
// inject our types at API construction
const api = ApiPromise.create({
types: {
'ExtrinsicV3': OwnExtrinsic,
'ExtrinsicV3Payload': OwnExtrinsicPayload,
}
})
```
The above example is certainly an advanced example, but it shows that all data types in the API can be adjusted and these adjustment can be provided to the API. In all cases, if you made updates to the formats and types of the actual runtime, you need to ensure that the API is aware of these changes.
In in the above example, should these updates only be made on the node side, without the required API adjustments, the API will generate invalid transactions for the node since it is unaware of the changes and adjusted formats. Making the adjustments on only one side will mean that the signature verification can fail and that the format will not be decodable via the node.
(These extensions are not exposed via metadata at all, and would be quite difficult to do as well - since each of these have specific logic as well as data types assigned.)
## Using with TypeScript
The API is built with TypeScript (as are all projects in the [polkadot-js organization](https://github.com/polkadot-js/)) and as such allows developers using TS to have access to all the type interfaces defined on the chain, as well as having access to typings on interacting with the `api.*` namespaces. In the next section we will provide an overview of [what is available in terms of types and TypeScript](typescript.md).
+2 -2
View File
@@ -4,11 +4,11 @@ Yes, it really is as simple as [installing from npm](https://www.npmjs.com/packa
`yarn add @polkadot/api`
And it will be added and ready for use. The above will always install the latest stable release, which should allow you to connect to testsnets and local nodes that are tracking versioned releases for [Polkadot](https://github.com/paritytech/polkadot) and [Substrate](https://github.com/paritytech/substrate).
And it will be added and ready for use. The above will always install the latest stable release, which should allow you to connect to test networks and local nodes that are tracking versioned releases for [Polkadot](https://github.com/paritytech/polkadot) and [Substrate](https://github.com/paritytech/substrate).
## Betas
For users who have a slightly higher appetite for risk, or are using bleeding-edge master branches of either Polkadot/Substrate, we also publish a beta version as soon as anything is merged into the API master branch. This version really contains all the latest fixes and features and is the version we actually use inside the polkadot-js projects - eating our own dogfood.
For users who have a slightly higher appetite for risk, or are using bleeding-edge master branches of either Polkadot/Substrate, we also publish a beta version as soon as anything is merged into the API master branch. This version really contains all the latest fixes and features and is the version we actually use inside the polkadot-js projects - eating our own dog food.
To install a beta version, either to test or for support of a feature that is available in Substrate master (and has not yet made it to a stable api release), you can install it via the `@beta` tag, i.e.
+4 -4
View File
@@ -6,7 +6,7 @@ This section will give a quick introduction into the Keyring, including the addi
They [@polkadot/keyring](https://github.com/polkadot-js/common/tree/master/packages/keyring) keyring is included directly with the API as a dependency, so it is directly importable (since the 0.92 version) alongside the API.
If you do opt to install it seperately, ensure that the version of `@polkadot/util-crypto` that is included with the API matches with the version of `@polkadot/keyring` installed. So if the API depends on `util-crypto 1.4.1`, it would make sense to include `keyring 1.4.1` as the installed version. (This helps in making sure extra versions of the libraries are not included as duplicates, especially in the case where bundles are created. Additionally, this makes sure that weird side-effects in the WASM initialization is avoided.)
If you do opt to install it separately, ensure that the version of `@polkadot/util-crypto` that is included with the API matches with the version of `@polkadot/keyring` installed. So if the API depends on `util-crypto 1.4.1`, it would make sense to include `keyring 1.4.1` as the installed version. (This helps in making sure extra versions of the libraries are not included as duplicates, especially in the case where bundles are created. Additionally, this makes sure that weird side-effects in the WASM initialization is avoided.)
## Creating a keyring instance
@@ -27,7 +27,7 @@ In the above example, the import is self-explanatory. Upon creation we pass thro
So effectively, when creating an account and not specifying a type, it will be `sr25519` by default based on the above construction params, however we can also add an `ed25519` account and use it transparently in the same keyring.
One "trick" that is done implictly in the above sample is that that keyring is only initialized after the API. In the case of `sr25519` the keyring relies on a [WASM build](https://github.com/polkadot-js/wasm) of the [schnorrkel libraries](https://github.com/w3f/schnorrkel). Since the API inlitialization is already async, it initializes the WASM libraries are part of the setup.
One "trick" that is done implicitly in the above sample is that that keyring is only initialized after the API. In the case of `sr25519` the keyring relies on a [WASM build](https://github.com/polkadot-js/wasm) of the [schnorrkel libraries](https://github.com/w3f/schnorrkel). Since the API inlitialization is already async, it initializes the WASM libraries are part of the setup.
However, this initialization can also be done explicitly, mostly for more advances use-cases, or in cases where the API won't be attached until much later -
@@ -52,7 +52,7 @@ The recommended catch-all approach to adding accounts is via `.addFromUri(<suri>
// Some mnemonic phrase
const PHRASE = 'entire material egg meadow latin bargain dutch coral blood melt acoustic thought';
// Add an account, straight menemonic
// Add an account, straight mnemonic
const newPair = keyring.addFromUri(PHRASE);
// (Advanced) add an account with a derivation path (hard & soft)
@@ -62,7 +62,7 @@ const newDeri = keyring.addFromUri(`${PHRASE}//hard-derived/soft-derived`);
const alice = keyring.addFromUri('//Alice', { name: 'Alice default' });
```
The above additions cater for most of the usecases and aligns with the you would find in the Substrate `subkey`. Be very wary of the last "dev-seed" option, it is explicitly added for `subkey` compatibility and implies using the "known-everywhere" dev seed. It is however useful when running Polkadot/Substrate with a `--dev` flag.
The above additions cater for most of the use cases and aligns with the you would find in the Substrate `subkey`. Be very wary of the last "dev-seed" option, it is explicitly added for `subkey` compatibility and implies using the "known-everywhere" dev seed. It is however useful when running Polkadot/Substrate with a `--dev` flag.
## Adding accounts with raw seeds
+3 -3
View File
@@ -4,7 +4,7 @@ We've touched upon types in most previous sections, i.e. that these are driven b
## Everything is a type
Just to re-iterate from the above. Eveything returned by the API is a type and has a consistent interface. This means that a `Vec<u32>` (an array of `u32` values) as well as a `Struct` (an pre-defined object) or an `Enum` has the same consistent base interface. Specific types types will have values, based on the type - decorated and available.
Just to re-iterate from the above. Everything returned by the API is a type and has a consistent interface. This means that a `Vec<u32>` (an array of `u32` values) as well as a `Struct` (an pre-defined object) or an `Enum` has the same consistent base interface. Specific types types will have values, based on the type - decorated and available.
As a minimum, anything returned by the API, be it a `Vec<...>`, `Option<...>`, `Struct` or any normal type will always have the following methods -
@@ -16,7 +16,7 @@ As a minimum, anything returned by the API, be it a `Vec<...>`, `Option<...>`, `
Additionally, the following getters and utilities are available -
- `.isEmpty` - `true` if the value is an all-empy value, i.e. `0` in for numbers, all-zero for Arrays (or anything `Uint8Array`), `false` is non-zero
- `.isEmpty` - `true` if the value is an all-empty value, i.e. `0` in for numbers, all-zero for Arrays (or anything `Uint8Array`), `false` is non-zero
- `.hash` - a `Hash` (once again with all the methods above) that is a `blake2-256` representation of the contained value
## Comparing types
@@ -63,7 +63,7 @@ As a real-world example, when an extrinsic is applied, the `Phase` enum has one
An `Option<Type>` attempts to mimic the Rust approach of having `None` and `Some` available. This means the following getters & methods are available on an `Option` -
- `.isNone` - is `true` if no underlying values is warpped, effectively the same as `.isEmpty`
- `.isNone` - is `true` if no underlying values is wrapped, effectively the same as `.isEmpty`
- `.isSome` - this is `true` is a value is wrapped, i.e. if a `Option<u32>` has an actual underlying `u32`
- `.unwrap()` - when `isSome`, this will return the wrapped value, i.e. for `Option<u32>`, this would return the `u32`. When the value is `isNone`, this call will throw an exception.
- `.unwrapOr(<default value>)` - this extends `unwrap()`, returning the wrapped value when `isSome` and in the case of `isNone` it will return the `<default value>` passed.
+5 -5
View File
@@ -4,7 +4,7 @@ Circling back to metadata, by default the metadata information (at this point in
Therefore to cater for all types, a mapping in done on the [@polkadot/types library](https://github.com/polkadot-js/api/tree/master/packages/types/src/interfaces) to define each of the types and align with their underlying structures as it maps to a default Polkadot or Substrate chain.
Additionally, the API contains some logic for chain type detection, for instance in the case of Substrate 1.x based chains, it will define `BlockNumber` & `Index` (nonce) as a `u64`, while for current-generation chains, these will be defined as `u32`. Some of the work in maintaining the API for Polkadot/Substrate is the addition of types as they appear and gets used in the Rust codebases.
Additionally, the API contains some logic for chain type detection, for instance in the case of Substrate 1.x based chains, it will define `BlockNumber` & `Index` (nonce) as a `u64`, while for current-generation chains, these will be defined as `u32`. Some of the work in maintaining the API for Polkadot/Substrate is the addition of types as they appear and gets used in the Rust codebase.
There is a the [recommendation](install.md#betas) to use a `@polkadot/api@beta` should you wish to track the master branches of Polkadot or Substrate, since master changes for the addition of new types do not make it into a stable release immediately.
@@ -24,7 +24,7 @@ const api = await ApiPromise.create({
});
```
The above introduces the `types` registry, effectively allowing overrides and the definition of new types. The override above would mean that immediately the API will treat all occurences of `Balance` not as the default, but rather as the defined size.
The above introduces the `types` registry, effectively allowing overrides and the definition of new types. The override above would mean that immediately the API will treat all occurrences of `Balance` not as the default, but rather as the defined size.
## User-defined types
@@ -52,7 +52,7 @@ const api = await ApiPromise.create({
})
```
The example above defines non-primitive types (as found in the specific implementation) as structs. Additionally it also shows the user-defined types can depend on other user-defined types with `Transaction` referencing both `TransactionInput` and `TransactionOutput`. Here you can reference any known types, i.e. in the above we have referenced primitives such as `u32` and `Signature` (itself an alias for `H512`).
The example above defines non-primitive types (as found in the specific implementation) as structures. Additionally it also shows the user-defined types can depend on other user-defined types with `Transaction` referencing both `TransactionInput` and `TransactionOutput`. Here you can reference any known types, i.e. in the above we have referenced primitives such as `u32` and `Signature` (itself an alias for `H512`).
One form of types that appear regularly is enums, these can be defined as follow -
@@ -137,6 +137,6 @@ const api = await ApiPromise.create({
`Balance` would be defined as an `u128` at the end. Effectively based on the flow it is first registered as a `u32`, then overridden as a `u64` and finally overridden once more as a `u128` by the chain types.
## Using with TypeScript
## Extending extrinsics
The API is built with TypeScript (as are all projects in the [polkadot-js organization](https://github.com/polkadot-js/)) and as such allows developers using TS to have access to all the type interfaces defined on the chain, as well as having access to typings on interacting with the `api.*` namespaces. In the next section we will provide an overview of [what is available in terms of types and TypeScript](typescript.md).
On customized chains, it is possible to [extend the format of the extrinsics and extrinsic payload as customized on the chain](extrinsic.extend.md), next up we will look at this advanced use-case.