Paymaster
How gas sponsorship and protocol fee collection work in LazorKit — the Paymaster service, fee_payer economics, on-chain FeeRecord tracking, and using a paymaster with the SDKs.
Paymaster
The Paymaster is the entity that sponsors gas for LazorKit transactions. It acts as the transaction's fee_payer on Solana — signs the transaction, pays the SOL required for gas, and submits it on-chain.
In practice, the developer is the Paymaster
Most LazorKit integrations do not use a third-party Paymaster service. The developer (integrator) is the fee_payer — they fund their users' wallet creation and execution directly, usually through a Kora relayer they run or use.
The developer recoups the cost through app-level monetization — on-ramp markups, subscription fees, product fees, in-app purchases, or direct token collection. LazorKit doesn't prescribe this layer; it only cares about the fee_payer pubkey on-chain.
The developer's fee_payer pubkey is tracked in FeeRecord — that's the basis for early contributor rewards at token launch. If you sponsor your users' fees, you earn.
This page describes protocol v2 (devnet today; see Networks & versions).
Economic Flow
- User pays — the developer, through app-level monetization. The mechanism is the dev's choice.
- Developer pays (as
fee_payer) — SOL for gas + LazorKit's protocol fee. Recouped from the user via the app. - LazorKit earns — the protocol fee, recorded against the developer's
fee_payerpubkey.
The end user never needs to hold SOL. The developer acts as the economic bridge: takes value from the user in whatever form their app supports (USDC, fiat on-ramp, subscription, product fees), and pays protocol costs in SOL.
Protocol Fee Collection
LazorKit captures a protocol fee on CreateWallet, Execute and ExecuteDeferred. The
fee is deducted from the transaction's fee_payer — the Paymaster, not the end user.
- Charged when configured. The fee is collected when
ProtocolConfigexists, is enabled and sets a fee. Otherwise collection is skipped and the transaction proceeds, so a disabled or missing config can never block a wallet's funds. Devnet v2 has noProtocolConfigtoday, so no fee is charged there. - Capped. Neither fee can be set above 0.01 SOL (
FeeExceedsMaximum, 4014). - Every payer is tracked. Each
fee_payergets aFeeRecord, created on its first fee-paying transaction: the SDK prepends a permissionlessRegisterPayerinstruction (about 0.00112 SOL of rent, paid by the payer, once), and the program also creates the canonical record inline when a client passes it uncreated. A record at any other address is refused (InvalidFeeRecord, 4011). In protocol v1, only payers registered beforehand were tracked. - The fee accounts are mandatory. The three instructions must carry the four fee
accounts even when no fee is charged; without them the program answers
FeeAccountsRequired(4008). The SDK clients always add them.
Flow per transaction:
- The SDK appends 4 accounts (
ProtocolConfig, the payer'sFeeRecord, aTreasuryShard,SystemProgram) to every fee-eligible instruction, and prependsRegisterPayeron the payer's first fee-paying transaction. - The entrypoint checks that each account is the canonical one, creates the
FeeRecordif it is still empty, and — when a fee is configured — transfers SOL from thefee_payerto the treasury shard. - It bumps the
FeeRecord'stotal_fees_paid,tx_countandwallet_count. - The 4 accounts are stripped before the instruction runs.
Architecture
Sharded Treasury
Fees route to one of N shards (e.g. 16), selected randomly per transaction. This eliminates write contention — parallel transactions hit different shards without blocking each other.
Admin calls WithdrawTreasury once per shard to sweep accumulated SOL to the treasury address.
On-Chain Accounts
ProtocolConfig — ["lk2:protocol_config"]
Global configuration. Set once via InitializeProtocol, whose signer is fixed in the
program binary.
| Field | Description |
|---|---|
admin | Protocol admin pubkey |
pending_admin | Admin rotation in progress: the new admin accepts it (two steps) |
treasury | Withdrawal destination |
creation_fee | Lamports per CreateWallet (at most 0.01 SOL) |
execution_fee | Lamports per Execute / ExecuteDeferred (at most 0.01 SOL) |
enabled | 0 = disabled (collection skipped), 1 = enabled |
num_shards | Number of treasury shards |
FeeRecord — ["lk2:fee_record", fee_payer_pubkey]
Created by the permissionless RegisterPayer (the SDK adds it for you) or inline by the
program. One per fee_payer — typically one per Paymaster operator.
| Field | Description |
|---|---|
total_fees_paid | Cumulative lamports paid — basis for early contributor rewards |
tx_count | Fee-eligible transaction count |
wallet_count | Wallets created via this fee_payer |
registered_at | Registration slot |
FeeRecord currently tracks the fee_payer — the developer (or Paymaster service) that sponsored the transaction. This data is the basis for rewarding early contributors: developers that funded protocol activity before the token launched.
Future: a parallel tracking mechanism may be added for end users, so that users can also be recognized for their protocol usage.
TreasuryShard — ["lk2:treasury_shard", shard_id]
8-byte account. SOL accumulates as lamport balance. Created once per shard via InitializeTreasuryShard.
Protocol v1 used the same seeds without the lk2: prefix.
Fee Amounts
Fees are set by the protocol admin; read them from ProtocolConfig (each is capped at
0.01 SOL). Devnet v2 has no fee configured today.
Per fee-eligible transaction, the fee accounts add 4 account keys (about 128 bytes without a lookup table).
Early Contributor Rewards
FeeRecord.total_fees_paid tracks cumulative fees per fee_payer proportional to the volume of activity they sponsored. When the protocol token launches, early contributor rewards are distributed based on this data:
- Snapshot all
FeeRecordaccounts viagetProgramAccounts(filter by discriminator0x26in v2) - Calculate share:
contributor_share = total_fees_paid / sum_all_fees - Distribute via merkle airdrop or direct transfer
The intent — for developers: if you build on LazorKit and pay for your users' wallet creation and transactions, your fee_payer pubkey accumulates SOL paid into FeeRecord. That's your reward claim. The more user activity you sponsor in the early phase, the larger your share of the token launch.
This is a direct growth incentive: LazorKit does not take a cut from your app-level revenue, and it rewards you in protocol tokens for the protocol fees you funded on behalf of your users. User-level tracking may be added later so end users can share in this as well.
Using a paymaster with the SDKs
The React and React Native SDKs send every transaction to a Kora-compatible JSON-RPC
endpoint: paymasterConfig (web) / configPaymaster (mobile), default
https://kora.devnet.lazorkit.com.
- Requests.
getPayerSignerfor the fee payer, thensignAndSendTransactionwith{ transaction, signer_key }, wheresigner_keyis the fee payer the transaction names. apiKeyis sent as thex-api-keyheader. It ships inside your web bundle or app, so anyone can read it: it identifies a client, it does not protect the relayer. Protect the relayer with what Kora enforces server-side (a lamport cap,require_one_of_programs). Two of Kora's controls need something neither SDK sends yet, so leave them off for now: reCAPTCHA on the signing methods needs anx-recaptcha-tokenheader, and on Kora 2.2.0-beta.8 with free pricing, usage limits need auser_idon every signing request. Turned on, either one refuses every SDK transaction.- Confirmation. The wallet waits for each transaction to confirm, whatever the relayer
answers. Kora confirms before it answers by default, so this costs one status read; a
relayer configured to answer early (
respond_after"sent") makes the wallet poll. - Failures reject with
PaymasterError(code,data,httpStatus,signature,maybeSent). The web SDK resends a failed send with the same signed bytes, up to 3 attempts in all, so the transaction can land at most once; it does not resend LazorKit's 3006 or 3014, nor a 4018 (since 3.3.0), nor a 3037 or 3038 (since 3.4.0). The React Native SDK never resends. When the relayer may have sent the transaction but no answer came back (on web, from any attempt), the call rejects withTransactionOutcomeUnknownError. A send request may take 90 s, other calls 30 s. - v1 wallets need a relayer that sponsors the v1 program:
v1PaymasterConfig(web) /v1ConfigPaymaster(mobile). LazorKit's v2 relayer does not, by design: sponsoring the full v1 program lets any v1 transaction pull the fee payer into its inner calls.
feeToken is forwarded to the relayer as fee_token by the React Native SDK only; the
web SDK (3.4.0) ignores it. LazorKit's relayer sponsors for free.
Running your own Kora
Configuration for the v2 programs, with the reasoning for each value, is in
deploy/kora
in the protocol repo. In short:
allowed_programsmust include the LazorKit v2 program id and the Secp256r1 precompile (Secp256r1SigVerify1111111111111111111111111), plus whatever your users' transactions call.require_one_of_programsset to the LazorKit program, so the relayer refuses transactions that never touch it.system.allow_transferandsystem.allow_create_accountstaytrue: the program pays rent and the protocol fee through System CPIs with the fee payer as source.- Add the v1 id only once it runs the sunset binary.
Current Status
- Protocol v1's fee system is live on Solana mainnet.
- Protocol v2's fee system is deployed on devnet, with no fee configured yet; the v2 mainnet program is not deployed.
- Fee collection operates in native SOL.
- Protocol token for governance and fee settlement is not yet deployed.
- Future: open Paymaster competition where multiple providers compete on sponsorship terms (rates, supported tokens, SLAs).