Link wallets
A Roblox experience can’t talk to a wallet, so linking happens on a web page. Rolink hosts one for each project. The player signs in with Roblox, then proves they own a wallet by signing a short text message. Signing is free and sends no transaction. Rolink stores the pair (Roblox user ID and public address) for your project, and your game reads it with rolink:GetWallet(player.UserId).
You don’t build or host anything for this. Your project’s page exists as soon as the project does:
https://api.rolink.tech/link/<project-slug>The dashboard shows the exact URL on the project’s Overview, and rolink:GetProject().linkUrl returns it in game. Players can sign in on it once you’ve connected your game’s Roblox OAuth app.
Set up Roblox sign-in
Section titled “Set up Roblox sign-in”Players sign in with Roblox through your game’s own OAuth 2.0 app. Rolink has no Roblox app of its own: you create the app in Creator Dashboard under your name, Roblox reviews it, and Roblox’s consent screen shows it to your players. Rolink uses it only to read who the player is.
-
Create the app. In Creator Dashboard, open OAuth 2.0 Apps and create an app for your game.
-
Add the redirect URL. Every game uses the same one, and your project’s Settings page shows it under Roblox sign-in with a copy button:
https://api.rolink.tech/auth/roblox/callbackIt must match exactly. Roblox only sends players back to a redirect URL registered on the app.
-
Allow the
openidandprofilescopes. Rolink asks for nothing else. -
Save the app in Rolink. In the dashboard, open your project’s Settings, then Roblox sign-in. Paste the app’s client ID and client secret, and choose Save. The card shows on once both are saved.
-
Get the app reviewed. Until Roblox reviews it, the app works for up to 10 users, which is enough to test with your own accounts. Get it reviewed before you send players to your linking page.
Rolink stores the client secret encrypted with AES-256-GCM and never shows it again. To replace it, paste the new one and save. To delete it, tick Remove the saved secret and save.
Until both the client ID and the secret are saved, the linking page shows “This game hasn’t set up Roblox sign-in yet.” and players can’t sign in. The project’s Overview lists Set up Roblox sign-in in its setup checklist until it’s done.
The player flow
Section titled “The player flow”-
The player opens your linking page. The top of the page shows your project’s name and its cluster, devnet or mainnet.
-
They sign in with Roblox. Continue with Roblox sends them to Roblox’s authorization page for your game’s OAuth app, so Roblox’s consent screen shows your app. Rolink asks only for the
openidandprofilescopes. The flow uses a randomstateand a PKCES256code challenge. -
Roblox sends them back. Rolink checks the
state, exchanges the code with your app’s client ID and secret, reads the user ID, username, display name and avatar, then revokes the Roblox access token right away. It only needed the identity. The player gets a one-hour session for your game and returns to your linking page. -
They choose a wallet. The page lists the Solana wallets installed in that browser. See Wallet discovery below.
-
Rolink issues a challenge. Once the wallet connects, Rolink builds the message below with a fresh single-use nonce and a 10-minute expiry, saves it, and sends it to the page.
-
The player signs the message. The wallet shows the text and asks for approval. The page sends the ed25519 signature back with the nonce.
-
Rolink verifies and links. It consumes the nonce, checks that it was issued to this Roblox account in this project and hasn’t expired, and verifies the signature against the address it was issued for. If everything matches, it saves the link and publishes
wallet_linkedto your game servers.
The page then shows Wallet linked and tells the player to head back to the game. It also offers Copy for the address, Use another wallet, and Unlink, which asks for a second click to confirm. Sign out ends the browser session without touching the link.
The message players sign
Section titled “The message players sign”The message follows the Sign-In With Solana layout, so wallets that understand it can check that the domain on the first line matches the site asking for the signature, and warn the player if it doesn’t.
{Rolink API host} wants you to sign in with your Solana account:{wallet address}
Link this wallet to Roblox account @{username} ({userId}) in {project name}. Signing is free and does not send a transaction.
URI: {linking page URL}Version: 1Chain ID: {project cluster}Nonce: {nonce}Issued At: {issued at, ISO 8601}Expiration Time: {issued at + 10 minutes, ISO 8601}For a project named “My Roblox Game” with the slug my-roblox-game on mainnet, a player sees something like this:
api.rolink.tech wants you to sign in with your Solana account:8vRfQeDk3NnB2pT6yW9sLcHx4aZmJ7uG5qE1oKd2VfXr
Link this wallet to Roblox account @BlockyBuilder (1234567) in My Roblox Game. Signing is free and does not send a transaction.
URI: https://api.rolink.tech/link/my-roblox-gameVersion: 1Chain ID: mainnetNonce: 4f9c1e0b7a3d2e5f8a6b9c0d1e2f3a4bIssued At: 2026-10-08T14:03:12.481ZExpiration Time: 2026-10-08T14:13:12.481Z| Field | Where it comes from | Why it’s there |
|---|---|---|
| First line | The Rolink API’s host | Lets the wallet compare it with the page’s real origin. |
| Address | The wallet the player connected | The signature only verifies for this address. |
| Statement | Username, user ID and your project’s name | Tells the player which account and which game they’re linking to. Line breaks in these values are flattened to spaces. |
URI | Your project’s linking page | The game the link is for. |
Chain ID | Your project’s cluster | Shows which network the project uses. |
Nonce | 16 random bytes, hex | Single use. A signed message can’t be replayed. |
Issued At / Expiration Time | Now, and now + 10 minutes | A signature that sits around for longer is refused. |
Renaming the project in the dashboard changes the name in new messages. The URI keeps the slug, which never changes.
Wallet discovery
Section titled “Wallet discovery”The linking page loads no third-party scripts or styles. It finds wallets in two ways:
- Wallet Standard. The page announces itself with the
wallet-standard:app-readyevent and listens forwallet-standard:register-wallet. It lists a wallet only if it supportsstandard:connectandsolana:signMessageon at least onesolana:chain. Phantom, Solflare, Backpack and most current Solana wallets register this way. - Legacy fallback. If no Wallet Standard wallet shows up, the page looks for an injected provider at
window.phantom.solanaorwindow.solanathat has asignMessagemethod, and lists it as “Phantom” or “Browser wallet”.
If the browser has neither, the page says so and links to the Phantom, Solflare and Backpack download pages. Players install one and reload.
Linking only needs a message signature, so the wallet’s own network setting doesn’t matter.
The rules Rolink enforces
Section titled “The rules Rolink enforces”Links belong to a project. A player who links a wallet in your game hasn’t linked it in any other game, and the rules below apply within one project:
- One wallet per player. Linking a new wallet replaces the old one. Your game receives
WalletLinkedwith the new address, and noWalletUnlinkedfor the old one. - One player per wallet. If a second Roblox account proves ownership of a wallet already linked in your project, the wallet moves to that account. Your game receives
WalletUnlinkedfor the previous account andWalletLinkedfor the new one. Whoever proved ownership last wins. - Single-use nonce. Verifying deletes the challenge as it’s read, whether or not the signature turns out to be valid. Each attempt needs a new challenge, which the page requests automatically.
- 10-minute expiry. A challenge older than 10 minutes, or one issued to a different Roblox account or project, is refused with
challenge_expired. - Exact signature check. The signature must be a 64-byte ed25519 signature over the exact message bytes, from the address the challenge was issued for. Anything else is
invalid_signature.
Unlinking
Section titled “Unlinking”Players unlink from the same page: Unlink, then Confirm unlink. Rolink deletes the link and publishes wallet_unlinked. Neither the game API nor the dashboard can unlink a player: a link goes away only when the player unlinks it, or when another account proves it owns the same wallet.
Sessions
Section titled “Sessions”The player’s session is a signed, HttpOnly, Secure cookie bound to the Rolink API’s origin. It lasts one hour and holds only their user ID, username, display name and avatar URL, and the project they signed in to. Rolink keeps no session table and never stores Roblox tokens.
A session only counts on the linking page of the game it was opened for, because the player gave consent to that game’s app. Signing in on one game’s page doesn’t sign the player in on another: there, they sign in again through that game’s own app, and its routes answer not_signed_in until they do. A browser holds one player session at a time, so signing in on another game’s page replaces the first session.
The page’s POST requests must come from the Rolink origin, and the page can’t be framed by another site, so other sites can’t submit requests on a player’s behalf. See the Security model.
How your game learns about links
Section titled “How your game learns about links”Your game never talks to the linking page. It reads links from the API and listens for live events.
local Players = game:GetService("Players")local ServerScriptService = game:GetService("ServerScriptService")
-- A ModuleScript that returns one shared Rolink.new(...) client for this server.local rolink = require(ServerScriptService.RolinkClient)
-- Server-side only. Don't replicate addresses to other players without a reason.local walletByPlayer: { [Player]: string? } = {}
local function setWallet(player: Player, address: string?) walletByPlayer[player] = address -- Update whatever depends on the wallet here: UI state, perks, a leaderboard...end
Players.PlayerAdded:Connect(function(player) local ok, result = pcall(rolink.GetWallet, rolink, player.UserId) if ok then setWallet(player, result) else warn(result) -- e.g. "[Rolink] network_error: ..." endend)
Players.PlayerRemoving:Connect(function(player) walletByPlayer[player] = nilend)
-- Live events fire on every server. `player` is set only where that player is.rolink.WalletLinked:Connect(function(_userId, address, player) if player then setWallet(player, address) endend)
rolink.WalletUnlinked:Connect(function(_userId, _address, player) if player then setWallet(player, nil) endend)GetWalletis cached. The SDK caches each answer, including “not linked”, forwalletCacheSeconds(60 by default).WalletLinkedandWalletUnlinkedupdate the cache as they arrive, so a lookup right after an event returns the new value.- Events are hints. MessagingService delivers on a best-effort basis. If a server misses an event, its cache expires within
walletCacheSecondsand the nextGetWalletcall asks Rolink again. ReadGetWalletwhenever the answer matters, such as before paying a player. - Without live events, linking still works. Servers notice new links when their cache expires. See Live events.
The dashboard’s Players page lists every linked player, with search by username, user ID or address.
Pointing players to the page
Section titled “Pointing players to the page”Players have to reach your linking page in a browser, and an experience can’t open a web page for them. How you tell them about it is up to you, within Roblox’s rules.
Roblox restricts which off-platform links an experience can show, where, and to whom, and those rules change over time. Before you display the linking page URL in your experience, in its description, or anywhere else players will see it, read the current Terms of Use and Community Standards. Use Rolink responsibly collects the questions worth asking first.
A few things help whatever channel you use:
- Tell players what to check. The first line of the message shows
api.rolink.tech, and the statement names your game. Ask them to check both before they sign. - Make linking optional. Your experience should work for players who never link a wallet.
- Explain what linking does, in your own words: it proves which wallet is theirs, it’s free, and it can’t move funds.
Test the flow
Section titled “Test the flow”Use a devnet project with Roblox sign-in set up. Open its linking page, sign in with your own Roblox account, and link a wallet. Before Roblox reviews your app, up to 10 accounts can sign in, so your own test accounts are enough. In Studio, your player has your own user ID, so GetWallet(player.UserId) returns that address. The Quickstart walks through it.
To try a wallet moving between accounts, link the same wallet from a second Roblox account. Your game receives wallet_unlinked for the first account and wallet_linked for the second.
Next steps
Section titled “Next steps”- Read on-chain data: balances, tokens and NFTs for a linked address.
- Live events: set up Open Cloud so
WalletLinkedreaches every server within seconds. - Security model: what protects the linking flow and what Rolink stores.
- Troubleshooting: sign-in errors, missing wallets and expired challenges.
