-
Notifications
You must be signed in to change notification settings - Fork 31
[mkdocs] docs: add transfer xem tutorial #883
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: new-docs
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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. | ||
|
|
||
| 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 | ||
|
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. | ||
|
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 | ||
|
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. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given the formula in |
||
| | 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" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}} | ||
|
zero4862 marked this conversation as resolved.
|
||
| \] | ||
|
|
||
| where: | ||
|
|
||
| * `8.999 × 10^9` is the initial XEM supply, in whole units. | ||
|
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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
| 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 |
Uh oh!
There was an error while loading. Please reload this page.