Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mkdocs/config/mkdocs.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ nav:
- devbook/accounts/create-from-mnemonic.md
- devbook/accounts/testnet-faucet.md
- devbook/accounts/query-balance.md
- Transactions:
- devbook/transactions/transfer-xem.md
- Reference Guides:
- Python SDK: devbook/reference/py/
- TypeScript SDK: devbook/reference/ts/
Expand Down
222 changes: 222 additions & 0 deletions mkdocs/pages/en/devbook/transactions/transfer-xem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
---
title: Transfer XEM
tutorial_level: beginner
---

# Sending XEM with a Transfer Transaction

Sending <XEM:> from one <account:> to another is the most basic action on the NEM blockchain, and every other type of
<transaction:> follows the same general pattern.

```dot
digraph "Transfer XEM" {
rankdir="LR";
node [fontsize=12];

A [label="A"];
B [label="B"];

A -> B [label="1 XEM"];
}
```

This tutorial shows how to create, sign, and announce a <transfer transaction:> that sends 1 XEM between two accounts,
and then poll the transaction's status until it is confirmed.

## Prerequisites

Before you start, make sure to:

* Set up your development environment.
See [Setting Up a Development Environment](../start/setup.md).
* Create an <account:> to send the transfer transaction, either
[from code](../accounts/create-from-private-key.md) or
[by using a wallet](../../userbook/wallet/create-account.md).
* Obtain <XEM:> to pay for the transaction fee and transfer amount.
See [Getting Testnet Funds from the Faucet](../accounts/testnet-faucet.md).

## Full Code

{% import 'tutorial.jinja2' as tutorial with context %}

{{ tutorial.code_full_tagged('devbook/transactions/transfer_xem', ['py', 'js']) }}

The whole code is wrapped in a single `try` block to provide simple error handling,
but applications will probably want to use more fine-grained control.

## Code Explanation

### Setting Up the Accounts

{{ tutorial.code_snippet_tagged('step-1') }}

Every transfer transaction involves two accounts: a **sender** and a **recipient**.

The **sender** is the <account:> that signs the transaction and pays the fee.
Its private key is loaded from the `SIGNER_PRIVATE_KEY` environment variable.
If not provided, a test key is used as default.

The **recipient** is the account that receives the XEM.
Its <address:> is loaded from the `RECIPIENT_ADDRESS` environment variable.
If not provided, a test address is used as default.

### Defining the Transfer Amount

{{ tutorial.code_snippet_tagged('step-2') }}

The snippet defines the transfer amount in the `xem` variable, loaded as a number from the `XEM_AMOUNT`
environment variable.
If not provided, a default of 1 XEM is used.

The transaction's `amount` field requires atomic units, not whole XEM.
XEM has a <divisibility:> of 6, so one XEM equals one million atomic units.
The snippet derives `amount` by multiplying `xem` by 1'000'000.

### Fetching Network Time

{{ tutorial.code_snippet_tagged('step-3') }}

Every NEM transaction contains two time fields, both expressed in <network time:>,
the number of seconds since the NEM nemesis block:

* `timestamp`: The moment the transaction is created, set here to the current network time.
* `deadline`: How long the network keeps trying to confirm the transaction before discarding it.
It must be after the timestamp and no more than 24 hours later.
Otherwise, the node rejects the transaction.
This example sets it two hours after the timestamp, well within the limit.

Building a transfer therefore needs an accurate network time.

The <get:/time-sync/network-time> endpoint reports the node's current network time.
The node returns this value in milliseconds, so the code divides it by 1000 to obtain the seconds that transactions
expect.

However, applications do not need to query the network time before every transaction.
It can be fetched once and then adjusted using the local system clock when needed.
This provides a good balance between accuracy and performance.

### Calculating the Transaction Fee

{{ tutorial.code_snippet_tagged('step-4') }}

Every transaction pays a fee to the <harvester:> that includes it in a block.
Comment thread
zero4862 marked this conversation as resolved.

NEM uses a fixed fee schedule, so the snippet calculates the fee locally without contacting a node.
For a XEM-only transfer, the fee starts at 0.05 XEM for small amounts and grows with the XEM sent, up to a cap of
1.25 XEM.
See [Fees](../../textbook/transfer_transactions.md#fees) for the full rules, including the mosaic and message costs.

The snippet implements this schedule:

* `fee_steps` is the number of full 10'000-XEM increments in `xem`, clamped between 1 and 25.
* `fee` is `fee_steps` multiplied by 50'000 atomic units, the value of one increment (0.05 XEM).

With the default 1 XEM, the fee falls in the first increment (0.05 XEM).

### Building the Transaction

{{ tutorial.code_snippet_tagged('step-5') }}

The snippet calls <dy:TransactionFactory.create> with a descriptor that supplies every required property of
the transfer transaction:

* `type`: This tutorial uses <ser:TransferTransactionV2>, the current transfer version, which can carry both XEM and
other <mosaics:>.
No mosaics are attached here, so the transaction sends XEM only.

* `signer_public_key`: The signer is the account that will pay the fee.
In a transfer transaction, it is also the source of the transferred XEM.

* `fee`: The value calculated in the previous step. For 1 XEM, this is `50_000` atomic units (0.05 XEM).

* `timestamp` and `deadline`: The values computed in the network time step.

* `recipient_address`: The address that will receive the XEM.

* `amount`: The atomic-unit value computed earlier. For 1 XEM, this is `1_000_000`.

!!! info "Sending a mosaic or a message"

A <ser:TransferTransactionV2> can also carry other <mosaics:> instead of XEM, or include a <message:>, with the fee
Comment thread
zero4862 marked this conversation as resolved.
calculated differently in each case.
See the [Transfer Mosaics](./transfer-mosaics.md) and [Transfer with a Message](./messages.md) tutorials.
Comment thread
segfaultxavi marked this conversation as resolved.

### Signing and Serializing

{{ tutorial.code_snippet_tagged('step-6') }}

Once the transaction is created, it must be signed with the signing account's private key.
Signing ensures the transaction is authentic and authorized by the sender.

<dy:NemFacade.signTransaction> returns a <signature:> encoded as a hexadecimal string.

<dy:TransactionFactory.attachSignature> adds the signature to the transaction and serializes it into a JSON payload
ready to be submitted directly to a node for announcement.

### Announcing the Transaction

{{ tutorial.code_snippet_tagged('step-7') }}

The signed payload is submitted to the <post:/transaction/announce> endpoint of any NEM <node:>.

The node validates the transaction as soon as it is announced and reports the outcome in the response.
A result of `SUCCESS` means the transaction passed this first check and was added to the <unconfirmed pool:>.
Any other result means the node did not accept it, and the response message explains why, for example that the
account does not hold enough XEM to cover the amount and the fee.

!!! warning "Do not rely on unconfirmed transactions"

A `SUCCESS` result only means the transaction reached the unconfirmed pool.
It is not yet guaranteed to be included in a block.
Wait until it is [confirmed](#waiting-for-confirmation), and ideally past the <rewrite limit:>, before relying
on it.

### Waiting for Confirmation

{{ tutorial.code_snippet_tagged('step-8') }}

The snippet above repeatedly queries the <get:/transaction/get> endpoint using the hash of the announced transaction.

!!! note "Polling vs WebSockets"

This step uses polling to check whether the transaction has been confirmed.
Polling is used here for illustration purposes, but it is not the recommended approach for real applications.

[WebSockets](../websockets/listen-transaction-flow.md) provide a more responsive solution without the overhead of
Comment thread
segfaultxavi marked this conversation as resolved.
repeated API calls.

While the transaction is still unconfirmed, the endpoint responds with an error, and the code waits one second
before retrying, for up to 120 attempts (about two minutes).

Once the transaction is included in a block, the endpoint returns it together with the block height, and the loop
ends.

NEM produces a block roughly once per minute, so confirmation usually takes from a few seconds to a couple of minutes.

## Output

The output shown below corresponds to a typical run of the program.

```text
--8<-- 'devbook/transactions/transfer_xem.log'
```

The number of `pending` checks depends on how soon the next block is harvested, so it varies between runs.

To see the transaction from the network's perspective, you can search for the transaction hash on a [NEM testnet
explorer](https://testnet.nem.fyi/).
The hash is printed in the line that says `Waiting for confirmation from /transaction/get?hash=...`.

## Conclusion

This tutorial showed how to:

| Step | Related documentation |
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------|
| [Obtain the network time](#fetching-network-time) | <get:/time-sync/network-time> |
| [Build the transaction](#building-the-transaction) | <dy:TransactionFactory.create> |
| [Sign the transaction](#signing-and-serializing) | <dy:NemFacade.signTransaction><br/><dy:TransactionFactory.attachSignature> |
| [Announce the transaction](#announcing-the-transaction) | <post:/transaction/announce> |
| [Wait for confirmation](#waiting-for-confirmation) | <get:/transaction/get> |

Most other NEM transaction types are created, signed, and announced in the same way.
37 changes: 37 additions & 0 deletions mkdocs/pages/en/textbook/transactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,40 @@ and validation steps, but differ in purpose and required fields.
| `Mosaic Supply Change` | Change the total supply of a mosaic. |

</div>

## Transaction Fees

Every transaction pays a fee that compensates the <harvester account:> that includes it in a block.

NEM fees are not market-driven.
The network publishes a fixed schedule, so the cost of any transaction can be calculated up front without contacting a
node.

### Fee Schedule

The current schedule is:

| Transaction | Cost | Notes |
| --------------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------|
| `Transfer` | From 0.05 XEM | Depends on the XEM amount, attached mosaics, and message length. See [Fees](./transfer_transactions.md#fees). |
| `Account Key Link` | 0.15 XEM | |
| `Multisig Account Modification` | 0.5 XEM | Paid by the multisig account (or, when converting a regular account into a multisig, by that account). |
| `Multisig Cosignature` | 0.15 XEM | Paid by the multisig account, not the cosignatory. |
| `Multisig` (wrapper) | 0.15 XEM | Paid by the multisig account, on top of the inner transaction's fee. |
| `Namespace Registration` | 0.15 XEM | Plus a [lease fee](./namespaces.md#lease-fee) paid to a network sink address. |
| `Mosaic Definition` | 0.15 XEM | Plus a [creation fee](./mosaics.md#creation-fee) paid to a network sink address. |
| `Mosaic Supply Change` | 0.15 XEM | |

### Floor and Bidding

The amounts in the schedule are minimums.
A transaction whose fee is below the minimum is rejected by validators.

A higher fee than the minimum is accepted and increases the chance of inclusion:

* When a harvester builds a block, it picks transactions sorted by fee, highest first.
* During network congestion, a node's spam filter ranks pending transactions by a combination of the signer's
<importance:> and a small fee bonus, so higher-fee transactions are more likely to enter the <unconfirmed pool:>.

`Multisig Cosignature` fees are additionally capped at 1000 XEM, which protects the multisig account from being drained
by a single cosignatory bidding an extreme fee.
83 changes: 83 additions & 0 deletions mkdocs/pages/en/textbook/transfer_transactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,86 @@ plaintext under AES-CBC and **996 bytes** under AES-GCM.
Nodes accept and store both schemes under the same `0x0002` flag without inspecting the payload.
A recipient can only decrypt a secure message when its tooling implements the same scheme the sender used.
Messages produced by GCM tooling cannot be decrypted by CBC-only tooling, and vice versa.

## Fees

A transfer transaction's fee depends on what is sent.
It is the sum of two components:

* The **transfer fee**, based on the XEM amount or the attached mosaics.
* The **message fee**, based on the length of any attached message.

### Transfer Fee

For a XEM-only transfer, the fee scales with the amount sent:

| Amount sent | Cost |
| ---------------------------- | --------- |
| Up to 10'000 XEM | 0.05 XEM |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the formula in AbstractTransactionFeeCalculator.java:51, please double check that this is up to 10'000 and not up to 19'999.

| Each additional 10'000 XEM | +0.05 XEM |
| 250'000 XEM or more | 1.25 XEM |

For a mosaic transfer, the fee is the sum of every attached mosaic's individual fee.
Each mosaic is priced as follows:

* **Tiny, indivisible mosaics** (supply ≤ 10'000 and divisibility 0) pay a flat **0.05 XEM**.
* **All other mosaics** are priced from their **XEM-equivalent value**, derived from the transferred quantity and the
mosaic's total supply.
This value maps to the same 0.05-to-1.25 XEM fee tiers used for XEM-only transfers, with a **supply discount**
that grows as the mosaic's total supply shrinks.

The minimum per-mosaic fee is **0.05 XEM**.

??? info "Mosaic Fee Calculation"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This process is pretty complicated. It would benefit from an example at the end.


Computing a non-tiny mosaic's fee takes three steps: compute the transferred quantity's XEM-equivalent value,
look that value up on the fee tiers to get a base fee, then subtract the supply discount.

**1. XEM-equivalent value**

\[
\text{xem\_equivalent} \approx \frac{8.999 \times 10^9 \cdot \text{atomic\_quantity} \cdot \text{multiplier}}{\text{total\_atomic\_supply}}
Comment thread
zero4862 marked this conversation as resolved.
\]

where:

* `8.999 × 10^9` is the initial XEM supply, in whole units.
Comment thread
zero4862 marked this conversation as resolved.
* `atomic_quantity` is the amount of the mosaic being transferred, in atomic units.
* `multiplier` is the [XEM-amount multiplier](#xem-amount) (typically 1).
* `total_atomic_supply` is the mosaic's total supply, in atomic units: `supply × 10^divisibility`.

**2. Base fee**

The resulting value is then priced on the same 0.05-to-1.25 XEM fee tiers as a XEM-only transfer,
yielding the mosaic's **base fee**.

**3. Supply discount**

A **supply discount** is then subtracted from that base fee:

\[
\text{discount} = \left\lfloor 0.8 \cdot \ln \!\left( \frac{9 \times 10^{15}}{\text{total\_atomic\_supply}} \right) \right\rfloor \cdot 0.05 \text{ XEM}
\]

where `9 × 10^15` is the largest mosaic quantity NEM allows.

Scarcer mosaics receive a larger discount, because the logarithm grows as the supply shrinks.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know the rationale for this? It would be good to explain it, because it does not seem obvious to me.


The final fee is **never less than 0.05 XEM**, even when the discount exceeds the base fee.

### Message Fee

A non-empty message costs **0.05 XEM** as a base, plus **0.05 XEM** for every additional 32 bytes of
payload, up to the 1024-byte maximum:

| Message length | Added cost |
| ---------------------- | ---------- |
| No message | — |
| 1 to 31 bytes | 0.05 XEM |
| 32 to 63 bytes | 0.10 XEM |
| 64 to 95 bytes | 0.15 XEM |
| … | … |
| 1024 bytes (maximum) | 1.65 XEM |

The fee is calculated on the stored payload size, so [secure messages](#secure-message-conventions) are billed on their
encrypted payload, not the plaintext.
34 changes: 34 additions & 0 deletions mkdocs/snippets/devbook/transactions/transfer_xem.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Using node http://libertalia.nemtest.net:7890
Fetching current network time from /time-sync/network-time
Network time: 351947283 s since the nemesis block
Transaction fee: 0.05 XEM
Built transaction:
{
type: 257,
version: 2,
network: 152,
timestamp: 351947283,
signerPublicKey: '462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE',
signature: '17FF7D37A63F3CFC43C790300A7B7F1C8A2A2B1C5D64546007C7D02C771516EE8872A6BBAE414486DC2D4750BBAACB41B3140D45CC319F51B7CBC1D524545C06',
fee: '50000',
deadline: 351954483,
recipientAddress: '5442554C4541554732435A51495355523434324857413655414B47574958484441424A5649505334',
amount: '1000000',
mosaics: []
}
Announcing transaction to /transaction/announce
Result: SUCCESS
Waiting for confirmation from /transaction/get?hash=436A43BDD3CE1F0A5A5EFC40A9027D3732D59AB3C507818A1B436EC7B32BF099
Transaction status: pending
Transaction status: pending
Transaction status: pending
Transaction status: pending
Transaction status: pending
Transaction status: pending
Transaction status: pending
Transaction status: pending
Transaction status: pending
Transaction status: pending
Transaction status: pending
Transaction status: pending
Transaction confirmed in block 626588
Loading