VT: SportsPort Scanner
Privacy policy.
What the Scanner app reads, sends, keeps and shows. The app is used by league officials and gate volunteers; the people the data is about are the athletes, most of them children, who hold up a SportsPort QR code.
No account and no sign-in; every verdict comes from the public blockchain, not from a server of ours
No analytics, crash reporter, advertising or advertising identifier
The camera reads QR codes only; no picture is saved or uploaded
Only an athlete id or league id goes to public blockchain services; our portal gets the league code, for its logo
After a yes, our portal gets a one-way hash (never the athlete id) for 24 hours, so other gates can spot a second check-in
Live codes are checked on the phone; the only personal detail in one is the athlete's initials
Recent scans stay on the phone for 12 hours at most
The app never sees a name, date of birth, photo or school
Who we are
VT: SportsPort Scanner (also listed as VTSP Scanner) is published by Vivid Tokenz. It is the gate app for VT: SportsPort: an official scans an athlete's SportsPort QR code and reads an eligibility verdict (ELIGIBLE, NOT ELIGIBLE or CAN'T CHECK) from the VT: SportsPort contract on the Polygon blockchain.
In one paragraph: the camera reads a QR code; only the decoded text is used, and only a valid SportsPort athlete id goes any further. That id is sent to a public Polygon network service as part of a read of the blockchain, and the answer is shown. When a league is loaded, its code — never an athlete's — is sent once to the SportsPort portal, our own server, for the league's logo and colours. After a yes, a one-way hash of the athlete id, league, division and day goes to the portal, which keeps it for 24 hours so every gate at an event can warn of a second check-in; the athlete id itself is never sent to it. A live code is checked on the phone, sending nothing. The phone keeps the gate setup, that look, and a short list of recent scans. There is no account, no analytics, no crash reporter and no advertising identifier.
The camera
- The camera is used to read QR codes only. No picture is saved, uploaded or logged; the app receives the text in the code and nothing else.
- Text that is not a SportsPort code (a card's code, a sandbox pass, a wi-fi poster) is refused with a fixed sentence. It is not stored, not sent and not shown on screen.
- The app does not use the microphone or the photo library.
- On iPhone, codes are read with Apple's on-device barcode detection, which sends nothing. On Android, see “Android and Google ML Kit” below.
Live gate codes
A family's phone can show a live code instead of the plain one: the same SportsPort address with one more part, made again on the phone every 15 seconds, so a screenshot of it is out of date by the time it reaches a gate. Besides the athlete id, a live code carries:
- the address of a key the family's phone made for this purpose (random, and not the family's wallet);
- when the portal's sign-off inside it runs out (five minutes after it was issued), and two signatures, the portal's and the phone key's;
- the athlete's initials (first and last, two capital letters such as “MJ”), which the portal signed, so the official can compare them with the roster;
- in newer codes, the address of the credential contract the code is for.
The code is checked on the phone, and nothing about that check is sent anywhere: the signatures against the portal's key, which is built into the app, and the time against the phone's clock. The eligibility read that follows is the same blockchain read as for a plain code, with the athlete id alone. The initials are shown on the verdict card and are not kept in the recent list or anywhere else on the phone.
What is sent, and to whom
The app reads the blockchain through public Polygon RPC services (the network services that answer questions about the chain). The store version uses two: Polygon's public RPC (polygon-rpc.com) and dRPC (polygon.drpc.org). Test versions of the app, which are not in the stores, use PublicNode (operated by Allnodes) and dRPC on the test network.
- Every scan: the athlete id, with the division's rules (its ruleset id, age band and required documents). After a NOT ELIGIBLE answer only, the athlete id is sent once more to choose the sentence explaining the “no”.
- Loading a league, and while the scan screen is open: the league id (a hash of the league code the official typed). While a gate is open this is re-checked every 30 minutes, every minute while the setup is not confirmed, on returning to the app after more than a minute, and when the official taps “Check again”.
- Technical checks that name nobody: requests for the latest block, the chain's id, the contracts' public code and, for a contract that can be upgraded, which code currently answers for it. These contain no athlete or league data.
- After each ELIGIBLE (or AGE NOT CHECKED), to our portal: a one-way hash and a random id for this install, described under “The shared check-in record”. Never the athlete id, and never in a demonstration.
- Every request: the phone's IP address and the time, as for any HTTPS request.
The league's look. A league that partners with us can put its logo and two colours on file, and the app shows them at that league's gate, above the camera. To get them, the app sends the league code (the code printed on the event paperwork, which names the league and nobody else) to the SportsPort portal at sportsport.vividtokenz.io, Vivid Tokenz's own server. It is sent once when a league is loaded and once when the app opens on a saved gate, then the logo picture is fetched from the same server, if the league has one. It is never sent on a scan, and the verdict never waits for it. The portal sees the league code and what any web server sees (the phone's IP address and the time); this request carries no athlete id and nothing about a scan. The look never changes a verdict's colour or wording.
Which operator gets the athlete id. The question goes to one service at a time. The other is asked only if the first has failed or has not answered within 4 seconds. So a scan's athlete id normally reaches one operator, but on a slow connection (one bar, a crowded venue's wi-fi) many scans' ids reach both. The technical checks that name nobody go to both.
What an operator can learn. Each scan tells the operator asked that a phone at this IP address, at this time, asked about this athlete id under this league's rules. The league re-check works like a heartbeat: even when nobody is scanned, an operator can see that a phone at this IP address was running this league's gate, and for how long. The athlete id is a stable pseudonymous identifier that anyone can also look up on the public chain, so an operator that keeps logs could assemble which athletes were checked at which venue and when, across events. These are free public services with no data-processing agreement with us; their logging practices are their own.
Nothing else is contacted by the app's own code beyond the blockchain services and the portal (the league's look above, and the shared check-in record below). There is no analytics, crash reporting, remote configuration, over-the-air update check or advertising SDK.
The Privacy policy and Help links in the app open this website in the phone's own browser, and only when tapped. This website then sees what any web server sees: the phone's IP address, the time and the browser. The app sends it nothing else; no athlete or league id is in the link.
The shared check-in record
Each phone already warns when it has let the same athlete through earlier the same day. So that every gate at an event can do the same for the gate next to it, after each ELIGIBLE (and each AGE NOT CHECKED) the app tells the SportsPort portal it let the athlete through, and the portal answers with any earlier check-ins. The card then says, for example, “Already checked in at 9:14 at another gate”.
- What is sent: a one-way hash of the athlete id, the league code, the division and the date (in UTC), and a random id made once for this install. The random id names nobody; it only lets the portal tell this phone's check-ins from another's. For a league that uses a credential other than the SportsPort, the hash also includes that credential's contract address, so its check-ins are counted separately.
- What is not sent: the athlete id itself, a name, an age, or the verdict's details. A demonstration sends nothing, and the verdict never waits for the answer (the app gives up after 3 seconds).
- How long it is kept: 24 hours, then deleted. The portal keeps nothing that names the official or the phone's owner.
Said plainly: our portal issued every athlete id, so it could work out which athlete a hash belongs to by trying them all. The hash keeps the athlete id out of the request, not out of our reach. We use the record only to warn of a second check-in, and delete it after 24 hours.
Android and Google ML Kit
On Android, the camera library the app uses reads barcodes with Google ML Kit. Detection runs on the phone, and no image leaves it.
Google documents that ML Kit also logs usage and performance metrics (device, app and API-call information, not image content) and uploads them to Google. The app is built to remove that upload, and to remove a request that would have Google Play services download Google's code-scanner screen, which the app never uses. That removal has not yet been confirmed on a device, so until it is, treat it this way: Android's on-device scanner may contact Google as described by Google.
What is kept on the phone
Stored on the phone only, separately for each network:
- The gate setup: the league code and name, the division chosen, and its rules (ruleset id, age band, required documents and cutoff year). Kept until replaced, so the official does not retype it between games, or until a gate has run 15 minutes without its league being confirmed on the blockchain, when it is deleted.
- The chosen network, in test versions that have a network switch.
- The league's look: the league code it belongs to, its two colours and the address of its logo on the portal, kept beside the gate setup until the league is changed, the portal says the look is gone, or the gate setup is deleted. Nothing about an athlete.
- This install's random id for the shared check-in record: 32 random characters made once, naming nobody.
- Recent scans: at most 20 per network, newest first, each kept for 12 hours and then forgotten. It is removed within a minute while the app is open, or the next time the app is opened if it was not running when the 12 hours ran out. Each entry holds only the athlete id, the answer (including whether a yes was shown as AGE NOT CHECKED), the time of the check, the network, and the league code and division the gate was running. No age, grade, graduation year, expiry or error text is kept. A check that was stopped is not kept, and demonstration scans are never saved. The list exists so a disputed call can be looked at again without the family queueing twice.
- Clear removes every network's list at once. The screen says “Cleared” only once the phone has confirmed it; if not, the list is shown again with a way to retry.
- Lists kept by earlier versions of the app, which held more detail, are deleted every time the app starts.
Backups and new phones. On Android, cloud backup is turned off and the app's data is excluded from device-to-device transfer, so none of it moves to another phone. On iPhone, the app's storage is excluded from iCloud and computer backups. Uninstalling the app removes all of it.
What is shown
The verdict: ELIGIBLE, NOT ELIGIBLE, CAN'T CHECK, or AGE NOT CHECKED (a yes in a league whose divisions are named for ages but which has set no age rule, so only documents were checked; the official is asked to check photo ID). Then the athlete id shortened to its first ten and last six characters, and what the contract returned: the league age (for a league with an age rule), the expiry, and the grade and graduation year only for a league whose rules ask about school. The contract holds no name, date of birth, photo or school, so the app cannot show one.
For a live code, the card also shows how old the code is and the athlete's initials signed into it. When the shared check-in record shows the athlete was already let in at another gate that day, the card says so, with the time.
Errors are shown as plain sentences, not raw technical output. When a network service is named on screen, only its main domain is shown (such as drpc.org), since a gate's screen may be photographed.
The recent list shows the shortened id, the answer, the division (and the league code when it is not the gate's current league) and the time.
The app switcher
Phones save a picture of an app's last screen for the app switcher. Whenever the Scanner stops being in front, it draws a plain cover over itself, so that picture is not a verdict card with an athlete's id, age and answer. On iPhone the cover is what the picture shows. On Android 13 and later, the app switcher shows a blank card for the app.
On Android 12 and older the app tries to do the same, but some phone makers take the picture too early for it to work, and we do not yet claim it works there. The app does not block screenshots or screen recordings while it is in front, which an official may need for a disputed call.
Logs
The app's code writes nothing to the phone's system log, and store versions strip logging out at build time. The only thing that can reach the system log is React Native's own report of an app error, and that is scrubbed first of web addresses, host names, key-like strings and long hexadecimal values, so an athlete id or service address in an error does not reach the log. The app sends no crash reports of its own.
Children's data
Most SportsPort holders are children, and the app is built for the adult official holding the phone. The athlete id the app reads is a pseudonymous token id: it carries no name, date of birth, photo or school. The app shows age, grade and graduation year only as the blockchain returns them, and only where the league's rules call for them, and a live code's initials only on the verdict card. Beyond the read of the blockchain, the app sends only the league code for its look and the one-way hash for the shared check-in record, both described above. The recent-scans list on the phone keeps no age, grade, graduation year or initials.
Third parties
- Polygon RPC operators: Polygon's public RPC and dRPC (PublicNode and dRPC in test versions), as described in “What is sent, and to whom”. They receive the athlete id or league id being checked, the phone's IP address and the time. Their logging practices are their own.
- Google ML Kit, on Android: barcode detection runs on the phone; ML Kit may contact Google as described by Google (see “Android and Google ML Kit”).
- Apple and Google, as platforms: the App Store and Google Play deliver and update the app, and the phone's operating system runs it, under Apple's and Google's own privacy terms. Where a user has chosen to share analytics with developers, Apple and Google may pass us crash reports through App Store Connect and the Play Console.
The app contains no advertising, analytics or tracking SDKs.
Retention and deletion
There is no account and no server-side record of the official. The only record of a scan kept by us is the shared check-in record: a one-way hash and the install's random id, deleted after 24 hours. Everything else the app holds is on the phone: recent scans are forgotten after 12 hours, Clear removes them sooner, and uninstalling removes everything, including the install's random id. What an RPC operator logged is held by that operator, not by us.
Changes to this policy
If a change to the app makes anything on this page untrue, we update this page and the effective date at the top. The version published here is the current one.
Contact
Questions about this policy or the Scanner app: admin@vividtokenz.io. For help with the app at a gate, see Scanner support.
