Skip to content

How it works

Rolink is a hosted service. The only piece that runs on your side is the Luau SDK inside your game servers. The linking pages, the game API, the dashboard, the project wallets and the event publisher all run on Rolink.

Player's browser Roblox game servers
/link/<slug> + wallet Luau SDK (server only)
| | ^
| Sign in with Roblox | HTTPS | SubscribeAsync
| + signed message | x-api-key |
v v |
+----------------------------------------------+ |
| Rolink Cloud | |
| linking pages, /v1 game API, dashboard | |
| database: projects, links, tx records | |
+----------------------------------------------+ |
| | | |
| JSON-RPC | sign | publish |
v v v |
Solana RPC Turnkey Open Cloud |
(+ DAS API) (project wallets) MessagingService -----+
(your universe)
PieceRuns onWhat it does
Luau SDKYour Roblox game serversWraps the game API, caches wallet lookups, and turns MessagingService messages into signals. It refuses to run on the client.
Game APIRolink, under /v1Answers your servers. Each request carries a project API key in x-api-key, and everything it reads or writes belongs to that key’s project.
Linking pageRolink, at /link/<project-slug>Signs the player in with Roblox through your game’s own OAuth app, finds their wallets, and asks them to sign the link message. One page per project.
DashboardRolink, at /dashboardWhere you sign in with a Solana wallet, create projects and keys, save your game’s Roblox OAuth app, set the transaction policy, and enter your Open Cloud settings.
Managed walletsTurnkeyOne Solana wallet per project. Turnkey generates the key and keeps it in its secure enclaves. Rolink asks it for signatures.
Solana RPCRolink’s RPC providersBalances, token accounts, mints, signatures and transaction submission, on the cluster your project uses. NFT reads use the DAS API.
Open Cloud MessagingServiceRobloxDelivers events to every live server in your experience, using the universe ID and Open Cloud key you enter in the dashboard.

Some of this can’t be done safely from inside Roblox:

  • There are no wallets in Roblox. A game can’t talk to a browser wallet, so players link from a web page instead.
  • Game servers have no safe place for a signing key. Roblox Secrets can be sent in requests but never read, so you can’t sign with them. Rolink signs through Turnkey and enforces your limits before it asks.
  • HttpService has a quota. Roblox allows about 500 requests per minute per server. Rolink caches chain reads and answers wallet lookups from its own database.
  • Signing players in needs a web server. Sign in with Roblox sends the browser back to a callback URL, and a game can’t serve one. Rolink hosts the callback for every game, and signs players in through each game’s own OAuth app.

A project is one Roblox experience. Everything Rolink keeps is attached to a project:

  • API keys. A key resolves to exactly one project. Every /v1 request is answered with that project’s data and nothing else.
  • Wallet links. A player who links a wallet in one game hasn’t linked it in another. Each project has its own linking page and its own links.
  • Player sign-in. Each project signs players in through its own Roblox OAuth app, and a player session only counts on the linking page of the game it was opened for.
  • The managed wallet and its policy. Each project has its own wallet, SOL and token limits, hourly budgets and transaction history.
  • Quotas. Requests and transactions are counted per project.
  • Events. Each project publishes to its own universe ID, with its own Open Cloud key and topic.
  • The cluster. Each project is on devnet or mainnet, chosen when you create it.

Only the developer who created a project can see or change it in the dashboard.

  1. The player opens your project’s linking page in a browser.
  2. Sign in with Roblox. Rolink runs the OAuth 2.0 authorization code flow with PKCE, with the openid and profile scopes, through your game’s own OAuth app. It reads the player’s user ID, username, display name and avatar, revokes the Roblox access token right away, and sets a signed, HttpOnly session cookie for your game that lasts one hour.
  3. Pick a wallet. The page lists wallets that implement the Wallet Standard with standard:connect and solana:signMessage. If there are none, it falls back to an older injected provider.
  4. Get a challenge. Rolink answers with a message in Sign-In With Solana format. It names the Rolink domain, your linking page, the wallet, the Roblox account, your game and its cluster, with a single-use nonce that expires after 10 minutes.
  5. Sign. The wallet signs the message. Signing is free and can’t move funds. Rolink checks the signature against the wallet’s public key.
  6. Store and announce. Rolink saves the link for your project and publishes wallet_linked. If the wallet was linked to another account in the same project, that link moves, and the other account gets wallet_unlinked.

Link wallets shows the exact message and the rules around it.

  1. The SDK calls the game API, for example GET /v1/wallets/:address/tokens?mint=…, with the x-api-key header.
  2. Rolink serves the answer from its cache or asks a Solana RPC on your project’s cluster. Token balances cover both the Token and Token-2022 programs. NFT and collection reads use the DAS API.
  3. The SDK returns amounts as decimal strings, such as "12.5".

Wallet lookups (GetWallet, GetWallets) never touch the chain. Rolink answers them from its database.

  1. The SDK posts a request, for example TransferToken({ to, mint, amount, idempotencyKey }).
  2. Same key seen before? If the body matches and the earlier attempt didn’t fail, Rolink returns that result and sends nothing new. A different body with the same key gets 409 idempotency_conflict.
  3. Check the policy and the quota. The project must have a managed wallet. The asset must be allowed and the amount within its per-transaction limit. Then Rolink checks the hourly limits per recipient wallet and per player, the asset’s hourly budget, and the project’s beta transaction quota. You set the policy in the dashboard.
  4. Reserve the key. Rolink records the request as pending in the same step as the limit checks, so two requests can’t both slip under a limit.
  5. Sign and send. Rolink builds the transaction with the project wallet as fee payer, asks Turnkey to sign it, and verifies the signature before it trusts it. If the recipient has no token account for the mint yet, the transaction creates one. Rolink saves the signature before it sends, so an interrupted send can still be reconciled.
  6. Wait, then keep tracking. The request waits up to 15 seconds for confirmation and answers confirmed, failed or submitted. A submitted request keeps being tracked, and its final result arrives as a tx event or through GetTransaction.

A transaction is reported as expired only when the finalized chain is past its blockhash and an up-to-date node has no record of it. A failed request moved no funds, so retrying it under the same key is safe. Send transactions covers the policy and the statuses.

  1. Something happens in your project: a wallet is linked or unlinked, a transaction reaches a final status, or a watched address has a new transaction.
  2. Rolink publishes a small JSON message ({ "v": 1, "type": … }) to your project’s topic in your universe through Open Cloud, with your Open Cloud key. It retries rate limits and server errors a few times, and trims long transaction errors to fit MessagingService’s 1 kB message limit.
  3. Each game server’s SDK is subscribed to the same topic. It decodes the message and fires WalletLinked, WalletUnlinked, TransactionUpdated or ChainActivity.

The four event types are wallet_linked, wallet_unlinked, tx and watch. Their fields are listed in the Events reference. Without a universe ID and an Open Cloud key, nothing is published and everything else still works.

Rolink polls each address you add in the dashboard every 5 seconds. The first poll only records where history ends, so old transactions are never replayed. After that, every new transaction is published as a watch event, oldest first, and the SDK fires ChainActivity. See Watch addresses.

Rolink keeps public data, hashes and encrypted credentials. It never holds a private key in its database.

DataHolds
DevelopersThe wallet address you sign in to the dashboard with, and when you first and last signed in.
ProjectsName, slug, cluster, universe ID, event topic, your Roblox OAuth app’s client ID, transaction policy, watched addresses, and the managed wallet’s Turnkey ID and public address.
Open Cloud keysEncrypted with AES-256-GCM. Decrypted only to publish your events or send a test event.
Roblox OAuth client secretsEncrypted with AES-256-GCM. Decrypted only to sign players in on your linking page.
API keysA name, the first 16 characters, and a SHA-256 hash. The full key is shown once and never stored.
Wallet linksPer project: the player’s Roblox user ID and username, the wallet address and the link time.
Link challengesPending link messages and their nonces. Each is single-use and expires after 10 minutes.
TransactionsOne record per idempotency key: kind, recipient, asset, amount in base units, signature, status, error and timestamps.
Watch cursorsThe last signature seen on each watched address.
UsageRequests and transactions per project per UTC day.

What it doesn’t store:

  • Players’ private keys. Rolink never sees them. A player only sends a public address and a signature.
  • Project wallet keys. They’re generated and kept inside Turnkey. Rolink stores the wallet’s ID and address.
  • Roblox tokens. The access token is revoked as soon as Rolink has read the user ID.
  • Sessions and pending sign-ins. They live in signed cookies, not in the database.

Two caches keep traffic down. Neither one ever holds a private key.

CacheWhereWhatHow long
Chain readsRolinkSOL balances, token balances, assetsUp to 10 seconds
Wallet lookupsEach game serverGetWallet and GetWallets answers, including “no wallet”walletCacheSeconds, 60 seconds by default

Rolink also merges identical reads that arrive at the same time into one RPC call. Signature status and transaction routes are never cached.

On game servers, wallet_linked and wallet_unlinked events update the wallet cache as soon as they arrive. A lookup that started before an event can’t overwrite the newer answer. GetWallets fetches up to 200 users per request.

MessagingService delivers on a best-effort basis. A message can be lost, and a server that starts later never sees messages published before it subscribed. Use events to react quickly, and read the API when correctness matters:

  • Wallets. Call GetWallet when a player joins rather than waiting for WalletLinked.
  • Payments. Treat TransactionUpdated as a nudge, and confirm with GetTransaction(idempotencyKey) before you grant anything that depends on the result.
  • Linking. Rolink never makes the player wait on Open Cloud. If publishing fails, the link is saved anyway.