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)