ERC-4519: NFTs Tied to Physical Assets
ERC-4519 binds an NFT to a physical device (sneaker chip, luxury watch, NFC-tagged collectible, IoT sensor, smart key) by embedding the device's public key into the NFT and requiring a fresh signature from the device for ownership transfers. The NFT goes through a state machine — WAITING_FOR_OWNER → ENGAGED_WITH_OWNER → WAITING_FOR_TRANSFER → … — that mirrors the physical-asset's authentication state. This makes the NFT a trusted digital twin of the physical asset; selling the NFT requires the physical device to attest the transfer.
Used by:
- Authenticated luxury goods (Hermès, Rolex) where each item has a chip + NFT.
- Smart-key NFTs (Tesla, Aiways) where the NFT holder controls vehicle access.
- IoT device ownership (smart locks, surveillance cameras) where the NFT is the owner-key-of-record.
- Authenticated collectibles (NFC-tagged sports memorabilia, art prints) requiring physical-presence verification on resale.
Required State Machine
WAITING_FOR_OWNER
↓ (owner signs `setUser` with device's signature)
ENGAGED_WITH_OWNER
↓ (owner initiates transfer with `startTransfer`)
WAITING_FOR_TRANSFER
↓ (new owner signs `acceptTransfer` with device's fresh signature)
ENGAGED_WITH_OWNER (new owner)The fresh device signature on each transition prevents replay attacks and ensures the physical device is present (a stolen NFT without the physical chip is useless — you can't get a fresh signature).
Required Interface (abridged)
interface IERC4519 {
enum State { WAITING_FOR_OWNER, ENGAGED_WITH_OWNER, WAITING_FOR_TRANSFER }
event OwnerSet(uint256 tokenId, address oldOwner, address newOwner, State state);
event TransferInitiated(uint256 indexed tokenId, address indexed from, address indexed to);
event TransferAccepted(uint256 indexed tokenId, address indexed from, address indexed to);
function setUser(uint256 tokenId, address newUser, bytes calldata deviceSignature) external;
function startTransfer(uint256 tokenId, address newOwner) external;
function acceptTransfer(uint256 tokenId, bytes calldata deviceSignature) external;
function tokenState(uint256 tokenId) external view returns (State);
function devicePublicKey(uint256 tokenId) external view returns (bytes memory);
}Neo Equivalent: NEP-11 + Device Pubkey + State Storage
The Neo port stores the device's public key plus the current state per tokenId; transitions verify a fresh signature from the device.
public static void AcceptTransfer(ByteString tokenId, ByteString deviceSignature)
{
var state = TokenState(tokenId);
if (state != 2) throw new Exception("RWA:NotWaitingForTransfer");
var pendingNewOwner = GetPendingTransferTo(tokenId);
if (!Runtime.CheckWitness(pendingNewOwner)) throw new Exception("RWA:NotPendingOwner");
var pubKey = DevicePublicKey(tokenId);
var nonce = GetTransferNonce(tokenId);
var msgToSign = ((ByteString)tokenId).Concat(pendingNewOwner).Concat((ByteString)(byte[])nonce.ToByteArray());
if (!(bool)CryptoLib.VerifyWithECDsa(msgToSign, (ECPoint)pubKey, deviceSignature, NamedCurveHash.secp256r1SHA256))
throw new Exception("RWA:InvalidDeviceSignature");
// Complete transfer.
Transfer(pendingNewOwner, tokenId, null);
SetTokenState(tokenId, 1); // ENGAGED_WITH_OWNER
BumpTransferNonce(tokenId);
OnTransferAccepted(tokenId, OwnerOf(tokenId), pendingNewOwner);
}| ERC-4519 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
| Device public key per tokenId | Storage.Put(DeviceKey(tokenId), pubKey) | Stored at mint |
| State machine (3 states) | BigInteger 0/1/2 in storage | Direct port |
setUser(tokenId, newUser, deviceSig) | SetUser(tokenId, newUser, deviceSig) | Verifies device signature |
startTransfer(tokenId, newOwner) | StartTransfer(tokenId, newOwner) | Owner-witness-checked |
acceptTransfer(tokenId, deviceSig) | AcceptTransfer(tokenId, deviceSig) | Verifies fresh device signature |
devicePublicKey(tokenId) | DevicePublicKey(tokenId) view | Returns the bound key |
block.timestamp for nonce freshness | Runtime.Time | Same approach |
Composition
- ERC-3643 — T-REX. Pair: physical-asset NFTs with regulatory compliance (KYC + asset-attestation).
- ERC-7943 — Universal RWA Interface. Wrap ERC-4519 with uRWA capability flags for cross-token-type RWA registries.
- ERC-2981 — royalties. Resale royalties paid to the manufacturer / brand on each accept-transfer.
- ERC-7066 — lockable. Lock the NFT during transit (between
startTransferandacceptTransfer).
Migration Notes
For Solidity ERC-4519 deployments porting to Neo:
- Replace ERC-721 inheritance with NEP-11 base.
- Store device public keys as
secp256r1compressed points (33 bytes); verify withCryptoLib.VerifyWithECDsa. - Bump the transfer nonce on each successful transfer to prevent signature replay.
- The state machine is mechanical — three integer states, three transition methods.
The Neo port has the same physical-asset trust model as Ethereum; neither chain can verify physical possession on its own. The signature fresh-issuance from the device is the only cryptographic guarantee.
