The Abraham API provides two ways to submit blessings:
- Gasless (Backend-Signed): POST
/blessings- Backend submits blessing on behalf of user - User-Signed: POST
/blessings/prepare- User signs and submits their own transaction
📘 Looking for smart contract documentation? See Seeds Contract Reference for a complete guide to all AbrahamSeeds contract functions with code examples.
- Authentication
- Gasless Blessings
- User-Signed Blessings
- Delegation
- Query Endpoints
- Configuration Endpoints
- Error Handling
- Environment Variables
All blessing endpoints require authentication via Privy. Include the Privy auth token in the Authorization header:
Authorization: Bearer <privy_token>
Submit a blessing with backend signing (gasless for user).
Requirements:
- User must be authenticated
- User must own FirstWorks NFTs (verified via Merkle proof)
- User must not have used all daily blessings
- Backend must have OPERATOR_ROLE OR user must approve backend as delegate
Request:
POST /blessings
Authorization: Bearer <privy_token>
Content-Type: application/json
{
"seedId": 0
}Success Response (200):
{
"success": true,
"data": {
"seedId": 0,
"txHash": "0x123...",
"blessingCount": 42,
"message": "Blessing submitted successfully",
"blockExplorer": "https://sepolia.basescan.org/tx/0x123..."
}
}Error Responses:
400 - Bad Request
{
"success": false,
"error": "seedId is required"
}{
"success": false,
"error": "You have already blessed this seed"
}{
"success": false,
"error": "Cannot bless a minted seed"
}403 - Not Eligible
{
"success": false,
"error": "All blessings used for this period",
"data": {
"nftCount": 5,
"maxBlessings": 5,
"usedBlessings": 5,
"remainingBlessings": 0,
"periodEnd": "2025-11-09T00:00:00.000Z"
}
}403 - Not Authorized (Needs Delegation)
{
"success": false,
"error": "Backend not authorized to bless on your behalf",
"message": "Please approve the backend as your delegate first using /blessings/prepare-delegate endpoint",
"data": {
"backendAddress": "0xBackend...",
"needsApproval": true
}
}404 - Seed Not Found
{
"success": false,
"error": "Seed not found"
}503 - Service Unavailable
{
"success": false,
"error": "Backend blessing service not configured",
"message": "RELAYER_PRIVATE_KEY not set - use /blessings/prepare endpoint for client-side signing"
}Prepare a blessing transaction for client-side signing (user pays gas).
Requirements:
- User must be authenticated
- User must own FirstWorks NFTs
- User must have remaining blessings today
Request:
POST /blessings/prepare
Authorization: Bearer <privy_token>
Content-Type: application/json
{
"seedId": 0
}Success Response (200):
{
"success": true,
"data": {
"transaction": {
"to": "0x0b95d25463b7a937b3df28368456f2c40e95c730",
"data": "0x...",
"from": "0xUser...",
"chainId": 84532
},
"seedInfo": {
"id": 0,
"title": "My Seed",
"creator": "0xCreator...",
"currentBlessings": 41
},
"userInfo": {
"address": "0xUser...",
"nftCount": 5,
"remainingBlessings": 3
},
"instructions": {
"step1": "Send this transaction using your wallet",
"step2": "Wait for transaction confirmation",
"step3": "Your blessing will be recorded on-chain"
}
}
}Frontend Implementation Example:
// Using ethers.js
const response = await fetch('/blessings/prepare', {
method: 'POST',
headers: {
'Authorization': `Bearer ${privyToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ seedId: 0 })
});
const { data } = await response.json();
// Send transaction
const tx = await signer.sendTransaction({
to: data.transaction.to,
data: data.transaction.data
});
// Wait for confirmation
const receipt = await tx.wait();
console.log('Blessing confirmed:', receipt.transactionHash);// Using viem + wagmi
import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
const { data: hash, writeContract } = useWriteContract();
// Prepare transaction
const response = await fetch('/blessings/prepare', {
method: 'POST',
headers: {
'Authorization': `Bearer ${privyToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ seedId: 0 })
});
const { data } = await response.json();
// Submit transaction
writeContract({
to: data.transaction.to,
data: data.transaction.data
});
// Wait for confirmation (using the hook)
const { isSuccess } = useWaitForTransactionReceipt({ hash });Error Responses: Same as POST /blessings (400, 403, 404)
Check the delegation status of the authenticated user. This endpoint returns whether the user has approved the backend as their delegate, which is required for gasless blessings.
Request:
GET /blessings/delegation-status
Authorization: Bearer <privy_token>Success Response (200):
{
"success": true,
"data": {
"userAddress": "0xUser...",
"backendAddress": "0xBackend...",
"isDelegateApproved": true,
"canUseGaslessBlessings": true,
"message": "You have approved gasless blessings. The backend can submit blessings on your behalf."
}
}Response when not approved:
{
"success": true,
"data": {
"userAddress": "0xUser...",
"backendAddress": "0xBackend...",
"isDelegateApproved": false,
"canUseGaslessBlessings": false,
"message": "You have not yet approved gasless blessings. Call POST /blessings/prepare-delegate to get started."
}
}Response when backend not configured:
{
"success": true,
"data": {
"userAddress": "0xUser...",
"backendAddress": null,
"isDelegateApproved": false,
"canUseGaslessBlessings": false,
"message": "Backend relayer not configured. Gasless blessings are not available."
}
}Frontend Implementation Example:
// Check delegation status before attempting gasless blessing
const checkDelegationStatus = async () => {
const response = await fetch('/blessings/delegation-status', {
method: 'GET',
headers: {
'Authorization': `Bearer ${privyToken}`
}
});
const { data } = await response.json();
if (data.canUseGaslessBlessings) {
console.log('Gasless blessings enabled!');
return true;
} else {
console.log('Need to approve delegation first');
return false;
}
};Prepare a delegate approval transaction. Users must approve the backend as their delegate to enable gasless blessings.
Request:
POST /blessings/prepare-delegate
Authorization: Bearer <privy_token>
Content-Type: application/json
{
"approved": true // Optional, defaults to true
}Success Response (200):
{
"success": true,
"data": {
"transaction": {
"to": "0x0b95d25463b7a937b3df28368456f2c40e95c730",
"data": "0x...",
"from": "0xUser...",
"chainId": 84532
},
"delegateAddress": "0xBackend...",
"approved": true,
"currentStatus": "Not yet approved",
"message": "Sign this transaction to approve gasless blessings"
}
}Frontend Implementation Example:
// 1. Check if user needs to approve delegate
const checkDelegation = async () => {
const response = await fetch('/blessings/delegation-status', {
method: 'GET',
headers: {
'Authorization': `Bearer ${privyToken}`
}
});
const { data } = await response.json();
return data.canUseGaslessBlessings;
};
// 2. If not approved, prompt user to approve
const approveDelegate = async () => {
const response = await fetch('/blessings/prepare-delegate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${privyToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ approved: true })
});
const { data } = await response.json();
// Send transaction
const tx = await signer.sendTransaction({
to: data.transaction.to,
data: data.transaction.data
});
await tx.wait();
console.log('Delegation approved!');
};
// 3. Complete blessing flow
const blessWithGaslessOption = async (seedId: number) => {
const isApproved = await checkDelegation();
if (!isApproved) {
// Prompt user to approve
await approveDelegate();
}
// Now submit gasless blessing
await fetch('/blessings', {
method: 'POST',
headers: {
'Authorization': `Bearer ${privyToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ seedId })
});
};Check if the authenticated user is eligible to bless.
Request:
GET /blessings/eligibility
Authorization: Bearer <privy_token>
Response:
{
"success": true,
"data": {
"eligible": true,
"nftCount": 5,
"maxBlessings": 5,
"usedBlessings": 2,
"remainingBlessings": 3,
"periodEnd": "2025-11-09T00:00:00.000Z"
}
}Get blessing statistics for the authenticated user.
Request:
GET /blessings/stats
Authorization: Bearer <privy_token>
Response:
{
"success": true,
"data": {
"nftCount": 5,
"maxBlessings": 5,
"usedBlessings": 2,
"remainingBlessings": 3,
"periodStart": "2025-11-08T00:00:00.000Z",
"periodEnd": "2025-11-09T00:00:00.000Z"
}
}Get all blessings for a specific seed (from blockchain events).
Request:
GET /blessings/seed/0
Response:
{
"success": true,
"data": {
"seedId": 0,
"seed": {
"title": "My Seed",
"creator": "0xCreator...",
"blessings": 42,
"score": 100
},
"blessings": [
{
"blesser": "0xUser1...",
"timestamp": 1699564800
},
{
"blesser": "0xUser2...",
"timestamp": 1699564900
}
],
"count": 42
}
}Get all blessings by a specific user (from blockchain events).
Request:
GET /blessings/user/0x1234...
Response:
{
"success": true,
"data": {
"address": "0x1234...",
"blessings": [
{
"seedId": 0,
"timestamp": 1699564800
},
{
"seedId": 1,
"timestamp": 1699564900
}
],
"count": 2
}
}Get total number of blessings across all seeds.
Request:
GET /blessings/total
Response:
{
"success": true,
"data": {
"totalBlessings": 1234
}
}Get the current FirstWorks NFT snapshot data.
Request:
GET /blessings/firstworks/snapshot
Response:
{
"success": true,
"data": {
"contractAddress": "0x...",
"contractName": "FirstWorks",
"totalSupply": 100,
"timestamp": "2025-11-08T12:00:00.000Z",
"blockNumber": 12345678,
"holders": [...],
"totalHolders": 50,
"holderIndex": {...}
}
}Get all seeds (paginated).
Request:
GET /seeds?page=1&limit=20
Response:
{
"success": true,
"data": {
"seeds": [...],
"total": 100,
"page": 1,
"limit": 20
}
}Get total seed count.
Response:
{
"success": true,
"data": {
"count": 100
}
}Get seed statistics.
Response:
{
"success": true,
"data": {
"totalSeeds": 100,
"eligibleSeeds": 85,
"currentRound": 15,
"timeUntilRoundEnd": 43200
}
}Get current contract configuration.
Response:
{
"success": true,
"data": {
"roundMode": { "value": 0, "name": "ROUND_BASED" },
"tieBreakingStrategy": { "value": 0, "name": "LOWEST_SEED_ID" },
"eligibleSeedsCount": 85,
"blessingsPerNFT": 1,
"votingPeriod": 86400
}
}Get a specific seed by ID.
Response:
{
"success": true,
"data": {
"id": 0,
"creator": "0x...",
"ipfsHash": "Qm...",
"blessings": 42,
"score": 100,
"commandmentCount": 5,
"createdAt": 1699564800,
"submittedInRound": 1,
"creationRound": 0,
"isRetracted": false
}
}Get current contract configuration parameters.
Request:
GET /config
Response:
{
"success": true,
"data": {
"votingPeriod": 86400,
"votingPeriodHours": 24,
"votingPeriodDays": 1,
"blessingsPerNFT": 1,
"currentRound": 5,
"timeUntilPeriodEnd": 43200,
"contract": {
"address": "0x0b95d25463b7a937b3df28368456f2c40e95c730",
"network": "Base Sepolia"
}
}
}Frontend Implementation Example:
// Display current configuration to users
const { data } = await fetch('/config').then(r => r.json());
console.log(`Voting period: ${data.votingPeriodDays} day(s)`);
console.log(`Blessings per NFT: ${data.blessingsPerNFT}`);
console.log(`Time until round ends: ${data.timeUntilPeriodEnd}s`);All endpoints follow a consistent error response format:
{
"success": false,
"error": "Human-readable error message",
"details": "Technical error details (development only)"
}- 200 OK: Request successful
- 400 Bad Request: Invalid input (missing/invalid seedId, etc.)
- 401 Unauthorized: Missing or invalid authentication token
- 403 Forbidden: User not eligible (no NFTs, quota exceeded, not authorized)
- 404 Not Found: Seed does not exist
- 500 Internal Server Error: Server error
- 503 Service Unavailable: Backend service not configured
No NFTs Owned:
{
"success": false,
"error": "No NFTs owned",
"data": {
"nftCount": 0,
"maxBlessings": 0,
...
}
}Blessing Quota Exceeded:
{
"success": false,
"error": "All blessings used for this period",
"data": {
"usedBlessings": 5,
"maxBlessings": 5,
"remainingBlessings": 0,
"periodEnd": "2025-11-09T00:00:00.000Z"
}
}Backend Not Authorized (Needs Delegation):
{
"success": false,
"error": "Backend not authorized to bless on your behalf",
"message": "Please approve the backend as your delegate first",
"data": {
"backendAddress": "0x...",
"needsApproval": true
}
}# Authentication
PRIVY_APP_ID=your_privy_app_id
PRIVY_APP_SECRET=your_privy_secret
# Blockchain Network
NETWORK=baseSepolia # or "base" for mainnet
L2_RPC_URL=https://base-sepolia.g.alchemy.com/v2/YOUR_KEY
L2_SEEDS_CONTRACT=0x0b95d25463b7a937b3df28368456f2c40e95c730
L2_GATING_CONTRACT=0x46657b69308d90a4756369094c5d78781f3f5979
# FirstWorks NFT (L1)
MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
FIRSTWORKS_CONTRACT_ADDRESS=0x9734c959A5FEC7BaD8b0b560AD94F9740B90Efd8# Backend Relayer (for gasless blessings)
RELAYER_PRIVATE_KEY=0x... # Backend wallet private keyNote: If RELAYER_PRIVATE_KEY is not set, only client-side signing (/blessings/prepare) will work. The gasless endpoint (/blessings) will return a 503 error.
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────────┐
│ Client │ │ API │ │ Backend │ │ Blockchain │
└────┬────┘ └────┬────┘ └────┬────┘ └─────┬──────┘
│ │ │ │
│ 1. POST /blessings │ │
├───────────────>│ │ │
│ {seedId: 0} │ │ │
│ │ │ │
│ 2. Check eligibility │ │
│ ├─────────────────┤ │
│ │ (NFTs, quota)│ │
│ │<────────────────┤ │
│ │ │ │
│ 3. Verify Merkle proof │ │
│ ├────────────────────────────────────>│
│ │ │ verify() │
│ │<───────────────────────────────────┤
│ │ │ │
│ 4. Check authorization │ │
│ ├────────────────────────────────────>│
│ │ │ isDelegate?
│ │<───────────────────────────────────┤
│ │ │ │
│ 5. Submit blessing │ │
│ │─────────────────>│ │
│ │ │ 6. Sign & send tx │
│ │ ├─────────────────>│
│ │ │ │
│ │ │ 7. Tx confirmed │
│ │ │<─────────────────┤
│ │ │ │
│ 8. Return success │ │
│<───────────────┤ │ │
│ {txHash: ...} │ │ │
┌─────────┐ ┌─────────┐ ┌────────────┐
│ Client │ │ API │ │ Blockchain │
└────┬────┘ └────┬────┘ └─────┬──────┘
│ │ │
│ 1. POST /blessings/prepare │
├───────────────>│ │
│ {seedId: 0} │ │
│ │ │
│ 2. Check eligibility │
│ │ │
│ 3. Build transaction │
│ │ │
│ 4. Return transaction data │
│<───────────────┤ │
│ {transaction} │ │
│ │ │
│ 5. User signs & sends tx │
├──────────────────────────────────>│
│ │ │
│ 6. Transaction confirmed │
│<──────────────────────────────────┤
┌─────────┐ ┌─────────┐ ┌────────────┐
│ Client │ │ API │ │ Blockchain │
└────┬────┘ └────┬────┘ └─────┬──────┘
│ │ │
│ 1. POST /blessings/prepare-delegate
├───────────────>│ │
│ {approved: true} │
│ │ │
│ 2. Check current status │
│ ├─────────────────>│
│ │ isDelegate?│
│ │<────────────────┤
│ │ │
│ 3. Return approval tx data │
│<───────────────┤ │
│ {transaction} │ │
│ │ │
│ 4. User signs & approves delegate │
├──────────────────────────────────>│
│ │ │
│ 5. Approval confirmed │
│<──────────────────────────────────┤
│ │ │
│ 6. Now gasless blessings enabled! │
-
Always check eligibility first before attempting to bless
const { data } = await fetch('/blessings/eligibility'); if (!data.eligible) { alert(data.reason); return; }
-
Handle delegation gracefully - Prompt users to approve backend only when needed
try { await blessGasless(seedId); } catch (error) { if (error.data?.needsApproval) { // Prompt user to approve delegation await approveDelegate(); // Retry blessing await blessGasless(seedId); } }
-
Provide clear feedback about gas costs
- Gasless option: "Free blessing (no gas required)"
- User-signed: "You'll pay a small gas fee (~$0.01)"
-
Handle errors gracefully
try { const response = await bless(seedId); showSuccess(`Blessed! Tx: ${response.data.txHash}`); } catch (error) { if (error.status === 403) { showError(`Not eligible: ${error.error}`); } else if (error.status === 400) { showError(`Invalid: ${error.error}`); } else { showError('Something went wrong. Please try again.'); } }
-
Secure the relayer private key
- Use environment variables
- Never commit to version control
- Consider using AWS KMS, Azure Key Vault, or similar
-
Monitor relayer gas balance
const balance = await publicClient.getBalance({ address: relayerAddress }); if (balance < parseEther('0.01')) { alert('Relayer balance low!'); }
-
Rate limit blessing submissions beyond NFT quotas
// Additional rate limiting if (await redis.get(`rate:${userAddress}`)) { return { error: 'Too many requests' }; } await redis.setex(`rate:${userAddress}`, 60, '1');
-
Log all blessing submissions for debugging and monitoring
logger.info('Blessing submitted', { user: userAddress, seedId, txHash: result.txHash, timestamp: new Date().toISOString() });
The AbrahamSeeds contract is an ERC1155-based NFT contract that:
- Manages seed submissions and blessings
- Uses quadratic (sqrt) scoring for anti-whale protection
- Mints ERC1155 editions when seeds win
- Integrates with MerkleGating for cross-chain NFT verification
The MerkleGating module verifies FirstWorks NFT ownership using Merkle proofs:
- Snapshot of L1 NFT ownership is taken periodically
- Merkle tree is generated from snapshot
- Users prove ownership with Merkle proofs on L2
| Role | Description |
|---|---|
DEFAULT_ADMIN_ROLE |
Full admin access, can grant/revoke roles |
CREATOR_ROLE |
Can submit seeds |
OPERATOR_ROLE |
Can perform operations (relayer functions, winner selection) |
For issues or questions:
- GitHub: https://github.com/your-org/abraham-api
- Discord: https://discord.gg/abraham
- Email: support@abraham.xyz