Prerequisites: You need a Risk API key, a
Solana development environment (Anchor), and a backend service that holds an
Ed25519 keypair.
How It Works
Your backend screens the user’s wallet via the Range API, then signs a time-stamped attestation message. The user submits this attestation in a Solana transaction, and the onchain program verifies the signature, signer identity, and freshness, all without any oracle dependency.How It Compares to the Oracle Approach
Security Model
- Ed25519 signature verification: The onchain program verifies the backend’s
signature using Ed25519. This example uses the
brine-ed25519crate for in-program verification. Alternatively, you can use Solana’s native Ed25519 SigVerify precompile , this requires an additional instruction and onchain introspection to validate the signature (similar to what Switchboard does under the hood), but avoids depending on a third-party crate. Either way, forged attestations are rejected. - Signer binding: The message embeds the user’s pubkey. The program verifies it matches the transaction signer, preventing replay attacks.
- Freshness check: Attestations expire after 60 seconds (configurable). The program compares the message timestamp against the onchain clock.
- Audit trail: Each attestation includes a unique
riskCallIdthat links the onchain verification to the original API call. - API keys stay off-chain: The Range API key is only used by your backend, it never appears in transaction data.
Use Cases
Step 1: Screen the Wallet (TypeScript)
Your backend calls the Range API to get the wallet’s risk score and generates a uniqueriskCallId for audit tracking.
The
riskCallId is generated client-side as a random 16-byte hex string. This
ties the onchain attestation to a specific screening event for auditability.Step 2: Sign the Attestation (TypeScript)
After screening, your backend signs a message that binds the risk result to the user’s wallet and a specific point in time.{timestamp}_{pubkey}_{riskCallId} where:
- timestamp: Unix timestamp in seconds (used for freshness check onchain)
- pubkey: The user’s Solana wallet address (base58)
- riskCallId: Unique identifier linking to the API screening result
Step 3: Build and Send the Transaction (TypeScript)
The user submits the backend’s attestation in a Solana transaction. The SDK’sbuildVerifyRiskInstruction constructs the instruction with the Anchor
discriminator, signature, and message, see the
full implementation in the example repo.
Step 4: Verify Onchain (Rust)
The Anchor program verifies the Ed25519 signature, checks that the message signer matches the transaction signer, and enforces a freshness window.Program Entry Point
Verification Logic
Signature Verification and Message Parsing
Step 5: Run the Example
Clone the example repository and set up your own keys.BACKEND_PUBKEY in programs/sigverify/src/constants.rs with your
backend’s public key:
programs/sigverify/src/lib.rs (declare_id!)
and Anchor.toml ([programs.devnet]) with the output, and update
PROGRAM_ID in app/sdk.ts to match.
Customizing for Your Program
Adjusting the Risk Threshold
Modify the screening threshold in your backend to match your risk policy:Adjusting the Attestation Lifetime
Update the constant in your onchain program:Adding the Risk Score Onchain
You can extend the message format to include the risk score itself, allowing your program to enforce thresholds onchain:Production Considerations
- Rotate backend keys: Have a key rotation strategy. Update
BACKEND_PUBKEYin your program via an upgrade or a config PDA. - Use HTTPS everywhere: Ensure your backend-to-Range-API communication uses TLS.
- Log
riskCallId: Store the mapping betweenriskCallIdand screening results for compliance audits.
What’s Next
Address Risk Score
Full endpoint reference for the Risk API used in the screening step.
Onchain Risk Verifier
Oracle-based alternative using Switchboard On-Demand for fully decentralized
verification.
Example Repository
Complete working implementation with Anchor program and TypeScript SDK.
Wallet Integration
For off-chain risk screening in wallets and dApps.