Skip to main content

Asset model

Neurai uses a UTXO model. Asset balances are derived from spendable outputs that embed asset payloads in scriptPubKey. There is no global asset state outside the chain.

Asset types​

Neurai supports the following asset types:

TypeNamePrefix/SuffixCost (XNA)Purpose
0ROOT-1000Top-level namespace
1SUB/ separator200Sub-asset under ROOT
2UNIQUE# separator10Unique/NFT-style asset
3MSGCHANNEL~ separator200Messaging channel (legacy)
4QUALIFIER# prefix2000KYC/identity tag
5SUB_QUALIFIER#/#200Sub-qualifier tag
6RESTRICTED$ prefix3000Restricted/securities asset
7VOTEreserved0Voting asset (future)
8REISSUE-200Reissue operation
9OWNER! suffix0Ownership token
10NULL_ADD_QUALIFIER-0.2Add qualifier to address
12DEPIN& prefix10Soulbound identity/device asset (POSITRONIC)

(Type 11 is INVALID, an internal sentinel and not an issuable asset.)

Naming rules (examples)​

  • ROOT: MYTOKEN, PROJECT_X, GAME.TOKEN
  • SUB: GAME/GOLD, PROJECT/VIP
  • UNIQUE: ART#PIECE_001, TICKETS#ROW_A_SEAT_12
  • RESTRICTED: $COMPANY
  • QUALIFIER: #KYC, #ACCREDITED
  • OWNER: ASSET! (auto-created on issuance)
  • DEPIN: &DEVICE, &FLEET/UNIT001 (sub-DePIN with /)

DePIN assets (soulbound)​

AssetType::DEPIN = 12 is a new asset class introduced by the POSITRONIC upgrade for device identity, memberships and bound credentials. It is soulbound (creator-gated transfers) and doubles as the access key for the encrypted DePIN chat and MCP channels. It is currently enabled on testnet and regtest only.

Naming and parameters​

  • Prefix &: names match &[A-Z0-9._]{3,} (e.g. &DEVICE). Sub-DePIN names use / for fleet hierarchies, e.g. &FLEET/UNIT001.
  • units = 0: there is no fractional supply. Issuance and reissue both reject any other value (DEPIN_ASSET_UNITS); a transfer must move a positive integer amount.
  • Burn cost: mirrors the UNIQUE asset issuance fee — 10 XNA — and uses the same burn address as unique assets (GetIssueUniqueAssetBurnAmount / IssueUniqueAssetBurnAddress).
  • Owner token: issuance creates the matching &NAME! owner token, exactly as for ROOT/UNIQUE assets.

Soulbound transfers​

Every DePIN transfer is co-signed by the creator. The wallet auto-attaches the asset's owner token to the transaction, so a holder cannot move the asset alone (AddressHasDEPINOwnerToken). If the owner token is missing, validation fails with bad-txns-...doesn't have owner token for DEPIN asset.

Revocation flags​

DePIN borrows the restricted-asset null-data machinery (CNullAssetTxData) to manage holder validity, persisted in the restricted DB:

  • Owner revocation (R): the creator can freeze any holder's address, immediately invalidating that holder's DePIN asset. This is validated by VerifyDEPINOwnerChange. The owner token's own address is exempt and can never be frozen.
  • Holder self-revocation (S): a holder can mark their own asset invalid (SELF_RESTRICTED_FLAG = 'S', validated by VerifySelfRestrictionChange). Once self-revoked, the asset stops being valid for that address; only the creator can re-activate it via freeze/unfreeze with flag = 0.

Reissue and sub-issuance​

Reissue requires the owner token to be present at the creator's address and must keep units = 0. Sub-DePIN assets are issued under a parent (&FLEET/UNIT001) for fleet-style structures without polluting the root namespace.

Script encoding​

DePIN assets use the same OP_XNA_ASSET payload framing as other assets (issue rvnq, owner rvno, reissue rvnr, transfer rvnt); the soulbound behaviour and revocation come from the owner-token co-signing rule and the R / S null-data flags above, not from a new script prefix. Detection is via IsAssetNameADEPIN.

Typical uses​

  • Per-unit device identity for IoT / DePIN hardware (one asset per unit, owner-gated).
  • Memberships and access passes the issuer can revoke without burning supply.
  • Soulbound credentials: badges, certifications, reputation tags.
  • Gating the encrypted DePIN chat and MCP-agent channels to the holder set.

Script encoding​

Assets are embedded in the output script using OP_XNA_ASSET:

<base script> OP_XNA_ASSET <pushdata: payload> OP_DROP

OP_XNA_ASSET is defined as 0xc0. The payload begins with a 4-byte prefix:

  • rvnq: new asset (issue)
  • rvno: owner token output
  • rvnr: reissue
  • rvnt: transfer

The remainder is the serialized structure (via CDataStream), depending on the prefix.

Implementation note: CScript::IsAssetScript expects OP_XNA_ASSET after the base P2PKH script (index 25 in the standard layout).

Base P2PKH example​

OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG

Issue/transfer/reissue templates​

OP_DUP OP_HASH160 <20B_HASH160> OP_EQUALVERIFY OP_CHECKSIG
OP_XNA_ASSET <PUSHDATA(payload)> OP_DROP

Payloads:

  • Issue: rvnq + CNewAsset
  • Transfer: rvnt + CAssetTransfer
  • Reissue: rvnr + CReissueAsset

Null-data scripts (qualifier/restricted)​

Certain qualifier and restricted operations use non-spendable scripts:

  1. Address tag / restriction:
OP_XNA_ASSET <hash160(dest)> <PUSHDATA(CNullAssetTxData)>
  1. Verifier string for restricted assets:
OP_XNA_ASSET OP_RESERVED <PUSHDATA(CNullAssetTxVerifierString)>
  1. Global restriction:
OP_XNA_ASSET OP_RESERVED OP_RESERVED <PUSHDATA(CNullAssetTxData)>

These scripts are detected by their prefixes in src/script/script.cpp and are not spendable outputs.

Issuance transaction layout​

A standard issuance has at least three outputs:

  1. Burn output: XNA sent to a type-specific burn address.
  2. Owner output: ASSET! owner token (rvno).
  3. Issue output: asset issuance (rvnq).

Burn amount and burn address are type-specific and enforced in assets.cpp (see GetBurnAmount / GetBurnAddress).

Type-specific burn addresses (examples)​

  • ROOT: NbURNXXXXXXXXXXXXXXXXXXXXXXXT65Gdr
  • SUB: NXissueSubAssetXXXXXXXXXXXXXX6B2JF
  • UNIQUE: NXissueUniqueAssetXXXXXXXXXXUBzP4Z

Special cases​

  • UNIQUE: multiple rvnq outputs (one per unique), plus the owner output and per-unique burn.
  • RESTRICTED: includes a verifier string output (CNullAssetTxVerifierString).
  • QUALIFIER: uses CNullAssetTxData for address tagging, may require extra burn.

Transfer transaction layout​

Transfers embed a CAssetTransfer payload:

<base script> OP_XNA_ASSET <rvnt + CAssetTransfer> OP_DROP

CAssetTransfer fields:

  • asset name
  • amount
  • optional message (text or IPFS/txid)
  • optional expireTime (only serialized when message exists)

The message field is the primary place to attach descriptive metadata to a transfer.

Diagram: asset payload in scriptPubKey​

P2PKH base script Asset payload
----------------- -------------
OP_DUP OP_HASH160 <hash160> OP_EQUALVERIFY OP_XNA_ASSET <pushdata> OP_DROP
OP_CHECKSIG

<pushdata> = prefix + serialized struct
prefix = rvnq | rvno | rvnr | rvnt

Reissue transaction layout​

Reissue outputs embed rvnr + CReissueAsset. Reissue is controlled by the owner token (ASSET!) and can change:

  • total supply (additional amount)
  • decimal units (if allowed)
  • reissuable flag
  • IPFS/txid metadata

End-to-end flow (issue → transfer → reissue)​

  1. Issue (ROOT):
neurai-cli issue "ACME" 1000 "Ndst..." "Nchange..." 2 true false

Outputs: burn, owner (ACME!), issue (ACME).

  1. Transfer:
neurai-cli transfer "ACME" 25.50 "Ndest..." "Invoice 2024-001" 0 "Nchange..." "Nassetchange..."
  1. Reissue:
neurai-cli reissue "ACME" 500 "Ndest..." 2 true "QmMetadata..."

Implementation notes​

  • Asset scripts are detected by CScript::IsAssetScript (expects OP_XNA_ASSET after the base P2PKH script).
  • AssetFromScript, TransferAssetFromScript, ReissueAssetFromScript parse the payload.
  • Burn amounts and addresses are in chainparams and validated during issuance.

Diagram: issuance flow (ROOT)​

Client/RPC Transaction outputs
--------- --------------------
issue ROOT
|
+-- burn output (XNA -> burn address)
+-- owner output (ASSET! with rvno)
+-- issue output (ASSET with rvnq)

Diagram: transfer flow​

Client/RPC Transaction outputs
--------- --------------------
transfer ASSET amount
|
+-- destination output
P2PKH + OP_XNA_ASSET + rvnt + CAssetTransfer
(message/expireTime optional)

Diagram: reissue flow​

Client/RPC Transaction outputs
--------- --------------------
reissue ASSET amount
|
+-- burn output (XNA -> burn address)
+-- reissue output (ASSET with rvnr + CReissueAsset)
+-- optional metadata update (IPFS/txid)