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.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, 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.
Example Repository
Complete working implementation with Anchor program and TypeScript SDK.
Wallet Integration
For off-chain risk screening in wallets and dApps.