Trust Line Tokens
Fungible tokens issued over trust lines, one issuer account per token
The XRP Ledger is a public blockchain with built-in payments, issued tokens and NFTs. It is one of the two live chains in Smart Engines; Hedera is the other. It runs on the Smart Engines testnet, whose validators HSuite operates, against XRPL testnet.
XRPL is the canonical chain for value. The HSUITE token, the SEMV membership NFTs and the SAS subscription NFTs are issued here and nowhere else. A smart app can also create its own XRPL accounts and tokens as entities, funded by a payer wallet.
flowchart TD
XRPL["XRPL (canonical)"] --> HST["HSUITE token<br/>(trust line)"]
XRPL --> NFT["Membership / subscription<br/>NFTs (XLS-20)"]
XRPL --> COMP["Native compliance"]
COMP --> GF["GlobalFreeze<br/>(pausable)"]
COMP --> TF["Trust-line freeze<br/>(restrictable)"]
COMP --> AUTH["RequireAuth<br/>(compliant)"]
COMP --> CB["Clawback<br/>(wipeable)"]
XRPL --> SL["SignerList multisig<br/>(zero single-sig)"]
Trust Line Tokens
Fungible tokens issued over trust lines, one issuer account per token
Native NFTs
XLS-20 NFTs for membership and subscriptions
Issuer compliance
GlobalFreeze, trust-line freeze, RequireAuth and clawback
SignerList multisig
Every entity account is controlled by a SignerList of validator keys, with its master key disabled
Rule anchor
Each account’s rule reference is written on chain in its first SignerListSet
Live reserves
Funding is sized from the ledger’s current reserve, not a fixed amount
XRPL uses a trust line model for issued currencies. A token is identified as CURRENCY.ISSUER, for example HST.rIssuer....
Capabilities map to XRPL issuer flags and transactions:
| Capability | XRPL Feature | Description |
|---|---|---|
pausable |
GlobalFreeze | Freeze all transfers of the issuer’s tokens |
restrictable |
Trust-line freeze | Freeze one holder’s trust line |
compliant |
RequireAuth | Holders need a trust line the issuer authorized |
wipeable |
AllowTrustLineClawback | The issuer can claw tokens back |
mintable |
Inherent | The issuer mints by sending a payment |
burnable |
Inherent | Tokens sent back to the issuer are destroyed |
transferable |
Always on | Token transfers are always enabled |
XRPL issuer flags apply to the whole account, not to one currency. So each token gets its own issuer account, and compliant and wipeable can only be set when the token is created (see below).
An XRPL token is a token entity. Its issuer is the entity’s own account, derived from its DKG key and funded by the payer. The create runs in two halves, prepare and execute. createToken drives both:
import { xrplWalletFundWith } from '@hsuite/smart-engines-sdk';
// `baas` is a connected BaasClient (see the SDK guide); `wallet` is an xrpl.js Wallet.const token = await baas.entities.createToken({ chain: 'xrpl', name: 'My Token', symbol: 'MTK', currency: 'MTK', // the one currency code this issuer issues ruleRef, compliant: true, // optional: asfRequireAuth wipeable: true, // optional: asfAllowTrustLineClawback fundWith: xrplWalletFundWith(wallet),});currency is a 3-character code (not XRP) or 40 upper-case hex characters. It is required for any token whose rule can mint, and the cluster refuses a mint of any other code. If you send a creationIntent (from signEntityCreateIntent), it must sign the same currency and the same flags.
Finalize order. After the funding payment lands, the execute half finalizes the issuer in this order:
Payment has landed.AccountSet sets exactly the requested flags: SetFlag: 2 (compliant), SetFlag: 16 (wipeable), or SetFlag: 16 plus Flags: tfRequireAuth (both). There is no such step when no flag was requested.SignerListSet installs the validator multisig.AccountSet with asfDisableMaster turns off the master key.The entity’s own DKG key, threshold-signed by the validators, signs each step as the account’s master key. The payer funds everything, and the flags add no owner reserve. The platform never sets NoFreeze: XRPL does not allow NoFreeze and clawback on the same account, and NoFreeze can never be cleared.
Why the flags must be requested at create. XRPL refuses RequireAuth and AllowTrustLineClawback once the account owns any ledger object (tecOWNERS). A SignerList is one, and so is a trust line. So the flags can only be set before the SignerListSet, and a token that did not request them at create cannot get them later.
What validators will co-sign. Validators release signature shares for the flag AccountSet only while the account has no SignerList and its master key is still enabled. It must be on the entity’s own account, and the flags must equal exactly what the create request recorded. Anything else is denied.
A holder needs a trust line to the issuer before it can receive the token. A holder that is an account entity opens it through the SDK:
const prepared = await baas.entities.trustline(accountEntityId, { chain: 'xrpl', currency: 'MTK', issuerAddress: 'rIssuer...', limit: '1000000', // optional});// sign + submit prepared.transactionBytesAny other holder submits a TrustSet from their own wallet.
The entity-scoped methods return prepared bytes for you to sign and submit. The entity is the source, issuer or authority and the fee payer, resolved server-side, and the validators co-sign under its SignerList only after its rule allows the operation.
| Method | What it prepares on XRPL |
|---|---|
transfer / prepareWithdraw |
A Payment of XRP or an issued token from the entity |
compliance with action: 'pause' |
AccountSet GlobalFreeze on the issuer |
compliance with action: 'restrict' |
A trust-line freeze on account |
compliance with action: 'wipe' |
A Clawback of amount from account (needs wipeable) |
trustline |
A TrustSet from the entity |
const prepared = await baas.entities.compliance(tokenEntityId, { action: 'restrict', chain: 'xrpl', tokenId: 'MTK.rIssuer...', account: 'rHolder...',});A rule denial comes back as HTTP 403 rule_rejected; narrow it with isRuleRejected.
XRPL has native NFTs (the XLS-20 standard), minted with NFTokenMint. No trust line is needed to hold one.
subscriptionNft() rule template.tfBurnable only, without tfTransferable, so the ledger refuses a holder-to-holder transfer.| Flag Value | Name | Description |
|---|---|---|
| 1 | tfBurnable | The issuer can burn the NFT |
| 2 | tfOnlyXRP | Offers for the NFT must be in XRP |
| 8 | tfTransferable | The NFT can be transferred |
An XRPL account entity is created payer-funded, like a token:
const account = await baas.entities.createAccount({ chain: 'xrpl', ruleRef, fundWith: xrplWalletFundWith(wallet),});The account’s address is derived from the entity’s DKG key. After the payer funds it, the cluster installs a SignerListSet and disables the master key.
The SignerList lists one XRPL key per validator, each with weight 1, and its quorum is the threshold. In partial security mode the app owner’s key is added with weight 2, and the quorum becomes 2 plus the threshold, so the owner and a threshold of validators must both sign.
The funding amount is the reserve the account needs, read from the ledger at the time of the create. See Reserve Management.
Every XRPL entity account is bound to its rule on chain. Its first SignerListSet carries a memo with MemoType rule and MemoData rule:<topicId>@<consensusTimestamp>:<entityType> (hex-encoded), pointing at the rule’s message on the Hedera rules topic. That transaction is the first one the account signs, and it never changes. Unlike the account’s Domain field, the signers cannot rewrite it later.
Before releasing a signature, the validators resolve the rule from HCS and check it against the entity’s record. A mismatch is denied.
Every prepared XRPL transaction carries a LastLedgerSequence. After that ledger closes, the transaction can no longer be included.
A transaction past its window fails with tefMAX_LEDGER. When it is multi-signed, rippled can report the same expiry as tefBAD_SIGNATURE.
When a token’s rule declares a maxSupply, the validators read the live outstanding supply before authorizing a mint. They deny (fail closed) any mint that would breach the cap. This works for both issued-currency tokens (CURRENCY.ISSUER) and Multi-Purpose Tokens (MPT.<issuanceId>).
Some over-mints are invisible before execution, for example a payment routed past the cap through a third currency. For the HSUITE token, the validators also re-check supply after each confirmed transaction. If the issuer is over its cap, they claw back only the excess, from the holder that gained it in that transaction, and alert an operator.
| Error Code | Cause | Solution |
|---|---|---|
tecNO_LINE |
No trust line exists | Set up a trust line first |
tecUNFUNDED_PAYMENT |
Insufficient XRP balance | Fund the account (1 XRP base + 0.2 XRP per owned object) |
tecNO_AUTH |
Trust line not authorized | Authorize the trust line first |
tecFROZEN |
The account or trust line is frozen | Unfreeze before transacting |
tecINSUFFICIENT_RESERVE |
The account would go below its reserve | Keep enough XRP for the reserve |
tecNO_PERMISSION |
Clawback is not enabled on the issuer | Request wipeable at token create; it cannot be added later |
tefMAX_LEDGER |
The validity window passed | Prepare the transaction again |
Pre-flight destination validation. A payment or transfer prepare whose destination is not a valid classic address is rejected with HTTP 400 before anything is built or signed. A bad base58 checksum, or a 0, which is not in the base58 alphabet, makes an address invalid.
Request wipeable and compliant at token create
XRPL only accepts these flags before the issuer owns any ledger object (its SignerList counts), so they cannot be added later
Use RequireAuth for regulated tokens
Control who can hold the token
Keep your seed yourself
xrplWalletFundWith signs with your wallet; the SDK never holds the seed
Bind every token to a rule
The rule decides what the validators will co-sign
MultiSignReserve amendment). So a multisig account with only its SignerList reserves 1 + 0.2 = 1.2 XRP, and one that also holds a token trust line reserves 1 + 0.4 = 1.4 XRP.server_info) plus 0.1 XRP for fees, so a fee vote that changes the reserve is picked up without a code change. At today’s values that is 1.3 XRP for a SignerList-only account and 1.5 XRP with one trust line.Currency codes
3-character codes (not XRP) or 40 hex characters
Precision
Issued currencies carry 15 significant digits
Declare a supply cap
Set maxSupply in the token’s rule so over-cap mints are denied
| Aspect | Hedera (HTS) | XRPL (Trust Lines) |
|---|---|---|
| Token Creation | A TokenCreate transaction |
An issuer account; the token exists once issued |
| Token ID | Numeric id (e.g. 0.0.12345) |
Currency code + issuer (e.g. MTK.rIssuer...) |
| Holding | The account associates the token | The holder opens a trust line |
| Precision | decimals, set at create |
15 significant digits |
| Multisig | KeyList with a threshold |
SignerList with a quorum, master key disabled |
| NFTs in Smart Engines | None | XLS-20 (membership and subscriptions) |
A fungible token is a token entity with its own issuer account (see Creating Tokens). It is identified as CURRENCY.ISSUER. Holders open a trust line to the issuer, and the issuer, through its SignerList, sends them tokens.
NFTs are minted with NFTokenMint and identified by a 64-character hex id. They need no trust line, and the ledger tracks their owner directly. See NFT Support.