{"id":"ERC-7439","name":"Ticket Status and Transfer Policy","status":"Final","chain":"both","category":{"id":"security","name":"Security & Trust","description":"Helping users verify, control, and protect their assets"},"journeyStages":[{"id":"specialized","name":"Specialized Interactions","description":"Physical items, AI, social, gaming"}],"uxImpact":"ERC-7439 extends ERC-721 with ticket status, signature, expiry, and role-gated state changes intended to restrict transfers to authorized accounts. Its interface does not define ticket prices, resale caps, fees, reseller addresses, or a universal marketplace router; those policies must be read from the deployed implementation.","officialUrl":"https://ercs.ethereum.org/ERCS/erc-7439","lastReviewed":"2026-09-18","lastUpdated":"2026-09-18","kind":"proposal","hasDetailedContent":true,"content":{"id":"ERC-7439","sources":[{"label":"Official specification","url":"https://ercs.ethereum.org/ERCS/erc-7439","type":"official-spec"}],"lastReviewed":"2026-09-18","lastUpdated":"2026-09-18","summary":"ERC-7439 extends ERC-721 with ticket status, signature, expiry, and role-gated state changes intended to restrict transfers to authorized accounts. Its interface does not define ticket prices, resale caps, fees, reseller addresses, or a universal marketplace router; those policies must be read from the deployed implementation.","designerTakeaways":["Show the deployed contract's TokenStatus, signature, expiry, and authorized transfer policy before purchase.","Route to an authorized resale path only when the target implementation identifies one; label price/cap/fee rules separately.","Show ticket lifecycle status so fans know whether a pass is Sold, Resell, Void, or Redeemed."],"applicability":{"whenToUse":["A ticketing implementation uses ERC-721 status/signature/expiry and role-gated transfer policy.","The product can read the deployed contract before showing resale, price, fee, or authorized-route terms.","Implementation-specific partner or cap rules are separately verified and disclosed."],"whenToAvoid":["Collectible tickets meant to trade freely on open marketplaces.","The target contract does not implement the ERC-7439 interface and its required transfer restrictions.","Jurisdiction forbids transfer restrictions you must disclose."]},"prototypeFirst":[{"screen":"Ticket purchase with resale policy","why":"Fans must accept resale rules before paying.","covers":["Policy summary","Authorized partners"],"include":["TokenStatus","signature policy","expireTime","Implementation-specific resale terms"]},{"screen":"Blocked peer transfer","why":"A blocked transfer needs the target contract's role and status policy, not an assumed universal partner route.","covers":["Transfer blocked","Education modal"],"include":["Current status","Authorized account or route","Implementation-specific price/fee terms","Failure recovery"]},{"screen":"Configured resale listing","why":"A resale detail screen should show the configured route and any separately enforced price or fee policy.","covers":["Resale available","Implementation-specific price/fee policy","Sold through configured route"],"include":["Price or cap only if enforced","Configured partner/route","Listing confirmation"]},{"screen":"Venue redemption scan","why":"Door staff and fans need Redeemed vs Valid at a glance.","covers":["Valid","Redeemed","Void"],"include":["Large status badge","QR display","Scan timestamp"]}],"mentalModel":[{"label":"Ticket NFT","description":"A token representing event entry with lifecycle state, not just art."},{"label":"Authorized resale","description":"A ticket can carry Sold, Resell, Void, or Redeemed status and an expiry value; the implementation decides which roles may change state or transfer."},{"label":"Transfer block","description":"A transfer may be restricted to authorized accounts, but the contract does not by itself prove an official reseller, cap, or fee policy."},{"label":"Redemption","description":"Scanning can mark a ticket Redeemed; whether redemption ends resale depends on the deployed implementation and issuer policy."},{"label":"Void state","description":"Cancelled or fraudulent tickets must show unusable clearly."}],"statesToDesign":[{"state":"Valid — not yet used","trigger":"Ticket owned, event upcoming.","userNeed":"See date, seat, and resale options.","designResponse":"Show Valid and a resale action only when the contract and configured route allow it."},{"state":"Listed on configured resale route","trigger":"User lists through a configured authorized route and the implementation changes status.","userNeed":"Track listing without losing entry proof.","designResponse":"Listed for resale status with cancel listing action."},{"state":"Transfer blocked","trigger":"User attempts unauthorized send.","userNeed":"Understand why and what to do.","designResponse":"Explain the target contract's restriction and link its configured route only when one exists."},{"state":"Redeemed at venue","trigger":"Ticket scanned.","userNeed":"Confirm entry used; no false hope of resale.","designResponse":"Redeemed badge; gray card in wallet."},{"state":"Void or cancelled","trigger":"Event cancelled or ticket invalidated.","userNeed":"Know refund or replacement path.","designResponse":"Void badge with issuer message and refund CTA if applicable."}],"designDecisions":[{"question":"How to handle unauthorized transfer attempts?","recommendation":"Block with an educational policy modal and configured route when available; do not imply a universal official reseller.","rationale":"Fans blame the app unless policy is explained at failure moment."},{"question":"Show price caps in UI?","recommendation":"Show a resale price or cap only when the target implementation exposes and enforces that rule.","rationale":"ERC-7439 has no standardized price, cap, fee, or reseller parameter; its TokenInfo contains signature, status, and expireTime."},{"question":"Peer-to-peer send button at all?","recommendation":"Hide or replace peer-to-peer Send only when the deployed policy blocks it, and provide the configured route or reason.","rationale":"Visible but disabled Send feels like a bug."}],"problemsSolved":[{"problem":"Tickets need a recognizable lifecycle state","oldWay":"Marketplaces infer validity from a listing or image","newWay":"TokenStatus and TokenStatusChanged expose Sold, Resell, Void, and Redeemed states","impact":"high"},{"problem":"Ticket transfers may need role restrictions","oldWay":"Any ERC-721 transfer path is open","newWay":"The implementation can restrict transfers and state changes to authorized accounts","impact":"high"},{"problem":"Buyers need expiry and signature context","oldWay":"Ticket validity is hidden in an app database","newWay":"TokenInfo can expose signature, status, and expireTime for the chosen implementation","impact":"medium"}],"uxPatterns":[{"name":"Authorized Resale Route","description":"Replace or gate transfer controls using the deployed contract's status and authorized-account policy.","components":["ResaleBadge","PartnerLink","PricePolicyLabel"],"userFlow":["User opens a ticket","App reads TokenStatus, expireTime, and implementation resale policy","Shows the action that the target contract supports","User reviews price/fee only when separately enforced","Receipt updates the status"]},{"name":"Transfer Block Education","description":"Explain a blocked transfer and show the authorized route when the target implementation supplies one.","components":["BlockModal","PolicySummary","ResaleCTA"],"userFlow":["User tries to transfer","App reads the contract error or role/status policy","Explains why the transfer is blocked","Offers the configured authorized route or support path","Does not invent a universal reseller or cap"]}],"antiPatterns":[{"pattern":"Showing a protocol-guaranteed max resale price","why":"The interface has no price or cap field.","instead":"Display a cap only after reading a separate contract rule or marketplace policy.","severity":"critical"},{"pattern":"Routing every resale through an assumed official reseller","why":"ERC-7439 restricts roles but does not define a universal router or reseller address.","instead":"Show the actual authorized account and implementation route.","severity":"high"},{"pattern":"Treating status as a complete authenticity guarantee","why":"Signature meaning and ticket policy are implementation-defined.","instead":"Show TokenStatus, signature verification details, and issuer policy separately.","severity":"high"}],"vocabulary":[{"use":"Configured resale route","avoid":"Whitelisted transfer destination","why":"Avoids claiming that ERC-7439 itself names an official reseller."},{"use":"Ticket used","avoid":"Redeemed state flag","why":"Venue language over enum names."},{"use":"Price or cap when enforced","avoid":"Max transfer price parameter","why":"Policy users understand from traditional ticketing."}],"specConstraints":[{"id":"sold-status","constraint":"A sold ticket must have Sold status and remains valid in that status.","appliesTo":"application","normativity":"normative","specQuote":"* `Sold`: When a token is sold, it MUST change to `Sold`. The token is valid in this status.","specSection":"Specification > Interface","specUrl":"https://ercs.ethereum.org/ERCS/erc-7439#interface","designImplication":"Show Sold as an active lifecycle state and distinguish it from Void or an unissued ticket."},{"id":"resell-status","constraint":"A ticket in the secondary market must use Resell status and remains valid in that status.","appliesTo":"application","normativity":"normative","specQuote":"* `Resell`: When a token is in the secondary market, it MUST be changed to Resell. The token is valid in this status.","specSection":"Specification > Interface","specUrl":"https://ercs.ethereum.org/ERCS/erc-7439#interface","designImplication":"Use a clear resale state and route users through only the authorized secondary-market path exposed by the deployment."},{"id":"void-status","constraint":"A ticket involved in an illegal transaction must be set to Void and is invalid in that status.","appliesTo":"application","normativity":"normative","specQuote":"* `Void`: When the token owner engages in an illegal transaction, the token status MUST be set to Void, and the token is invalid in this status.","specSection":"Specification > Interface","specUrl":"https://ercs.ethereum.org/ERCS/erc-7439#interface","designImplication":"Block purchase/use actions for Void tickets and explain that the token is not valid for entry or transfer."},{"id":"ticketing-market-controls","constraint":"The rationale expects an open primary channel and a controlled fair secondary mechanism with authorized partner roles.","appliesTo":"application","normativity":"rationale","specQuote":"* For ticketing companies, selling out all tickets is crucial. Sometimes, to create a vibrant sales environment, ticketing companies may even collaborate with scalpers. This practice can be detrimental to both the audience and performers. To prevent such situations, there must be an open and transparent primary sales channel, as well as a fair secondary sales mechanism. In the `safeMint` function, which is a public function, we hope that everyone can mint tickets transparently at a listed price by themselves. At that time, `TokenInfo` adds a signature that only the buyer account or agent can resolve depending on the mechanism, to prove the ticket validity. And the token `status` is `Sold`. Despite this, we must also consider the pressures on ticketing companies. They aim to maximize the utility of every valid ticket, meaning selling out each one. In the traditional mechanism, ticketing companies only benefit from the initial sale, implying that they do not enjoy the excess profits from secondary sales. Therefore, we have designed a secondary sales process that is manageable for ticketing companies. In the `_beforeTokenTransfer()` function, you can see that it is an accessControl function, and only the `PARTNER_ROLE` `mint` or `burn` situation can transfer the ticket. The `PARTNER_ROLE` can be the ticket agency or a legal secondary ticket selling platform, which may be a state supervision or the ticket agency designated platform. To sustain the fair ticketing market, we cannot allow them to transfer tickets themselves, because we can’t distinguish whether the buyer is a scalper.","specSection":"Rationale","specUrl":"https://ercs.ethereum.org/ERCS/erc-7439#rationale","designImplication":"Show primary versus secondary channel, authorized resale route, and deployment-specific partner policy; do not invent universal prices, caps, or fees."},{"id":"open-pricing","constraint":"The rationale treats an open pricing system as necessary for consumer protection.","appliesTo":"application","normativity":"rationale","specQuote":"* An open pricing system is a must to make sure consumers are protected.","specSection":"Rationale","specUrl":"https://ercs.ethereum.org/ERCS/erc-7439#rationale","designImplication":"Make ticket price and pricing authority legible and keep any anti-scalping policy separate from the ERC-7439 status label."},{"id":"erc721-compat","constraint":"The ticket-status standard is compatible with ERC-721.","appliesTo":"application","normativity":"backwards-compatibility","specQuote":"## Backwards Compatibility\n\nThis standard is compatible with [ERC-721](./eip-721.md).","specSection":"Backwards Compatibility","specUrl":"https://ercs.ethereum.org/ERCS/erc-7439#backwards-compatibility","designImplication":"Reuse ERC-721 ownership/display patterns while presenting ticket-specific status and authorization context."},{"id":"redeemed-status","constraint":"When a ticket is used, the implementation is recommended to change its status to Redeemed.","appliesTo":"application","normativity":"normative","specQuote":"* `Redeemed`:  When the token is used, it is RECOMMENDED to change the token status to `Redeemed`.","specSection":"Specification > Interface","specUrl":"https://ercs.ethereum.org/ERCS/erc-7439#interface","designImplication":"Show Redeemed as a used-ticket state and keep it distinct from Sold, Resell, and Void. Treat the source wording as a recommendation, not an unconditional protocol rule."}],"technicalNotes":"ERC-7439 extends ERC-721 with TokenStatus { Sold, Resell, Void, Redeemed }, TokenInfo { bytes signature, TokenStatus tokenStatus, uint256 expireTime }, safeMint, and role-gated changeState. The standard restricts transferability through the implementation and emits TokenStatusChanged; it does not standardize price, cap, fee, reseller address, or marketplace routing.","relatedStandards":[{"id":"ERC-721","relationship":"ERC-7439 extends ERC-721; transfer restriction logic belongs to the ticket implementation."},{"id":"ERC-6454","relationship":"Transfer restriction detection complement"}],"partialDeployment":{"body":"This guide describes an interface-specific integration. A live deployment has not been verified here. Confirm the target contract, network, and client support before offering these actions; Final describes the specification, not product availability."}},"sources":[{"label":"Official specification","url":"https://ercs.ethereum.org/ERCS/erc-7439","type":"official-spec"},{"label":"Discussion search","url":"https://ethereum-magicians.org/search?q=ERC-7439","type":"discussion"}],"urls":{"canonical":"https://www.eipsfordesigners.com/standards/ERC-7439","markdown":"https://www.eipsfordesigners.com/standards/ERC-7439/content.md","agent":"https://www.eipsfordesigners.com/standards/ERC-7439/agent.md","api":"https://www.eipsfordesigners.com/api/standards/ERC-7439","official":"https://ercs.ethereum.org/ERCS/erc-7439","discussion":"https://ethereum-magicians.org/search?q=ERC-7439"},"freshness":{"lastReviewed":"2026-09-18","lastUpdated":"2026-09-18"}}