555x402-agg
v0.1.0
Published
Solana payment aggregation SDK with intelligent facilitator routing and instant USDC settlement
Maintainers
Readme
@555x402/agg
Solana payment aggregation SDK with intelligent facilitator routing and instant USDC settlement
Overview
The AGG (Aggregator) SDK enables developers to accept Solana payments through a unified API that automatically routes to the optimal payment facilitator based on latency, fees, and health. Supports USDC and SOL with instant settlement and webhook notifications.
Features
- ✅ Intelligent Routing: Automatically selects best facilitator (PayAI, Conduit, Internal)
- ✅ Instant Settlement: Sub-second transaction confirmation on Solana
- ✅ Low Fees: 0.5-1% facilitator fees + gasless
- ✅ Webhook Notifications: HMAC-signed callbacks for payment status
- ✅ TypeScript: Full type safety with comprehensive type definitions
- ✅ Framework Agnostic: Works with Next.js, Express, Fastify, vanilla Node
- ✅ Idempotent: Built-in deduplication via idempotency keys
Installation
npm install @555x402/agg
# or
pnpm add @555x402/agg
# or
yarn add @555x402/aggQuick Start
Basic Usage
import { AggClient } from '@555x402/agg';
const client = new AggClient(
'your-api-key',
'https://agg.rendernet.work/pub/v1' // optional, defaults to production
);
// Verify and settle a payment
const result = await client.verifyAndSettle(
paymentHeaderB64, // from X-Payment header
{
amount: 1.5, // $1.50 USDC
mint: new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), // USDC
recipient: new PublicKey('555Tm1cfV52SrBQmnxXiHMUMrpci8miW3CkLP1Qbmtd7')
}
);
console.log('Payment settled:', result.id);Express.js Example
import express from 'express';
import { AggClient } from '@555x402/agg';
import { PublicKey } from '@solana/web3.js';
const app = express();
const agg = new AggClient(process.env.AGG_API_KEY!);
app.post('/premium-api', async (req, res) => {
const paymentHeader = req.headers['x-payment'] as string;
if (!paymentHeader) {
return res.status(402).json({
error: 'Payment required',
price: { amount: 0.01, asset: 'USDC' }
});
}
try {
await agg.verifyAndSettle(paymentHeader, {
amount: 0.01,
mint: new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
recipient: new PublicKey(process.env.TREASURY_WALLET!)
});
// Payment verified and settled - serve the content
res.json({ data: 'premium content' });
} catch (error) {
res.status(402).json({ error: 'Payment verification failed' });
}
});
app.listen(3000);Next.js API Route
// app/api/protected/route.ts
import { AggClient } from '@555x402/agg';
import { PublicKey } from '@solana/web3.js';
import { NextRequest, NextResponse } from 'next/server';
const agg = new AggClient(process.env.AGG_API_KEY!);
export async function GET(request: NextRequest) {
const paymentHeader = request.headers.get('x-payment');
if (!paymentHeader) {
return NextResponse.json(
{ error: 'Payment required', price: { amount: 0.05, asset: 'USDC' }},
{ status: 402 }
);
}
try {
const payment = await agg.verifyAndSettle(paymentHeader, {
amount: 0.05,
mint: new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
recipient: new PublicKey(process.env.TREASURY_WALLET!)
});
return NextResponse.json({
data: 'protected data',
paymentId: payment.id
});
} catch {
return NextResponse.json(
{ error: 'Invalid payment' },
{ status: 402 }
);
}
}API Reference
Class: AggClient
Constructor
new AggClient(apiKey: string, apiBase?: string)Parameters:
apiKey(string, required): Your 555x402 API key from the dashboardapiBase(string, optional): API base URL. Defaults tohttps://agg.rendernet.work/pub/v1
Example:
const client = new AggClient('pk_live_abc123');
// Or for devnet testing:
const devClient = new AggClient('pk_test_xyz789', 'https://agg-devnet.rendernet.work/pub/v1');Methods
verifyAndSettle(paymentHeaderB64, expected): Promise<PaymentResult>
Verifies a payment header and settles the transaction through the optimal facilitator.
Parameters:
paymentHeaderB64(string): Base64-encoded payment header fromX-PaymentHTTP headerexpected(object):amount(number): Expected payment amount in token units (e.g., 1.5 for $1.50 USDC)mint(PublicKey): Token mint address (USDC, SOL, etc.)recipient(PublicKey): Wallet address to receive funds
Returns: Promise
interface PaymentResult {
id: string; // Payment order ID
status: string; // 'pending' | 'confirmed' | 'failed'
txSignature?: string; // Solana transaction signature (if confirmed)
facilitator?: string; // Which facilitator processed the payment
}Throws:
PaymentVerificationError- Invalid payment header or signatureInsufficientAmountError- Payment amount less than expectedNetworkError- API unreachable or timeout
Example:
try {
const result = await client.verifyAndSettle(
'eyJhbGc...', // from req.headers['x-payment']
{
amount: 2.50,
mint: new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
recipient: new PublicKey('555Tm1cfV52SrBQmnxXiHMUMrpci8miW3CkLP1Qbmtd7')
}
);
console.log(`Payment ${result.id} settled via ${result.facilitator}`);
console.log(`Tx: ${result.txSignature}`);
} catch (error) {
if (error instanceof InsufficientAmountError) {
console.error('Payment too low:', error.message);
}
}getStatus(paymentId): Promise<PaymentStatus>
Retrieves the current status of a payment.
Parameters:
paymentId(string): Payment order ID fromverifyAndSettleresponse
Returns: Promise
interface PaymentStatus {
id: string;
status: 'pending' | 'confirmed' | 'failed';
amount: number;
mint: string;
recipient: string;
sender: string;
txSignature?: string;
createdAt: string; // ISO 8601 timestamp
settledAt?: string; // ISO 8601 timestamp
}Example:
const status = await client.getStatus('pay_abc123xyz');
if (status.status === 'confirmed') {
console.log(`Payment confirmed in tx: ${status.txSignature}`);
} else if (status.status === 'pending') {
console.log('Waiting for confirmation...');
}Webhook Verification
verifyWebhookSignature(body, timestamp, signature, secret): boolean
Verifies HMAC signature of webhook payloads from the AGG service.
Parameters:
body(any): Raw request body (string or object)timestamp(string): Value fromX-555-Timestampheadersignature(string): Value fromX-555-Signatureheader (format:sha256=<hex>)secret(string): Your webhook secret
Returns: boolean - true if signature valid, false otherwise
Example (Express):
import { verifyWebhookSignature } from '@555x402/agg';
app.post('/webhooks/agg', express.json(), (req, res) => {
const timestamp = req.headers['x-555-timestamp'] as string;
const signature = req.headers['x-555-signature'] as string;
const secret = process.env.WEBHOOK_SECRET!;
if (!verifyWebhookSignature(req.body, timestamp, signature, secret)) {
return res.status(401).send('Invalid signature');
}
// Process webhook
const { event, data } = req.body;
if (event === 'agg.payment.settled') {
console.log(`Payment ${data.id} settled to ${data.to}`);
// Update your database, send confirmation email, etc.
}
res.sendStatus(200);
});Webhook Payload Format:
{
"event": "agg.payment.pending | agg.payment.settled | agg.payment.failed",
"data": {
"id": "pay_abc123",
"status": "settled",
"amount": 1500000,
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"to": "555Tm1cfV52SrBQmnxXiHMUMrpci8miW3CkLP1Qbmtd7",
"from": "sender_wallet_address",
"txSignature": "4vJ9J..."
}
}Advanced Usage
Polling for Settlement
async function waitForSettlement(paymentId: string, maxWaitMs = 30000): Promise<PaymentStatus> {
const start = Date.now();
while (Date.now() - start < maxWaitMs) {
const status = await client.getStatus(paymentId);
if (status.status === 'confirmed') {
return status;
}
if (status.status === 'failed') {
throw new Error('Payment failed');
}
await new Promise(r => setTimeout(r, 1000)); // Poll every second
}
throw new Error('Settlement timeout');
}Custom Facilitator Selection
The AGG router automatically selects the optimal facilitator, but you can influence routing by registering preferred facilitators for your recipient wallet.
Contact support to configure facilitator preferences.
Error Handling
import { AggClient, PaymentError } from '@555x402/agg';
try {
await client.verifyAndSettle(...);
} catch (error) {
if (error instanceof PaymentError) {
switch (error.code) {
case 'INVALID_SIGNATURE':
// Payment header signature doesn't match
break;
case 'INSUFFICIENT_AMOUNT':
// Payment amount < expected
break;
case 'INVALID_RECIPIENT':
// Recipient mismatch
break;
case 'FACILITATOR_UNAVAILABLE':
// All facilitators down (rare)
break;
case 'NETWORK_ERROR':
// Solana RPC or API unreachable
break;
}
}
}Token Support
Supported Tokens
| Token | Mint Address | Decimals |
|-------|--------------|----------|
| USDC (mainnet) | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | 6 |
| USDC (devnet) | 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU | 6 |
| SOL (wrapped) | So11111111111111111111111111111111111111112 | 9 |
Amount Conversion
USDC uses 6 decimals:
- $1.00 =
1000000micro-units - $0.01 =
10000micro-units
Helper (built-in):
import { usdToMicroUsdc, microUsdcToUsd } from '@555x402/agg/utils';
const microUnits = usdToMicroUsdc(2.50); // 2500000
const usd = microUsdcToUsd(2500000); // 2.5Examples
1. Pay-Per-Request API
import { AggClient } from '@555x402/agg';
import { PublicKey } from '@solana/web3.js';
const agg = new AggClient(process.env.AGG_API_KEY!);
const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
app.get('/api/premium-data', async (req, res) => {
const payment = req.headers['x-payment'] as string;
if (!payment) {
return res.status(402).json({
error: 'Payment required',
price: { amount: 0.10, asset: 'USDC' }
});
}
try {
const result = await agg.verifyAndSettle(payment, {
amount: 0.10,
mint: USDC_MINT,
recipient: new PublicKey(process.env.TREASURY!)
});
// Log for analytics
console.log(`Payment ${result.id} from ${result.sender}`);
// Serve premium content
const data = await fetchPremiumData();
res.json({ data });
} catch (error) {
res.status(402).json({ error: 'Invalid payment' });
}
});2. Webhook Handler (Async Settlement Notification)
import express from 'express';
import { verifyWebhookSignature } from '@555x402/agg';
const app = express();
app.post('/webhooks/payments',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['x-555-signature'] as string;
const timestamp = req.headers['x-555-timestamp'] as string;
const body = req.body.toString();
// Verify signature
if (!verifyWebhookSignature(
body,
timestamp,
signature,
process.env.WEBHOOK_SECRET!
)) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(body);
// Handle events
switch (payload.event) {
case 'agg.payment.pending':
console.log(`Payment ${payload.data.id} pending...`);
break;
case 'agg.payment.settled':
console.log(`Payment ${payload.data.id} confirmed!`);
console.log(`Tx: ${payload.data.txSignature}`);
// Update your database
await db.payments.update({
where: { id: payload.data.id },
data: {
status: 'settled',
txSignature: payload.data.txSignature
}
});
// Send confirmation email
await sendEmail(payload.data.to, 'Payment received!');
break;
case 'agg.payment.failed':
console.error(`Payment ${payload.data.id} failed`);
// Handle failure (refund, notify user, etc.)
break;
}
res.sendStatus(200);
}
);3. Polling Pattern (No Webhooks)
async function processPaymentWithPolling(paymentHeader: string) {
// Initiate settlement
const result = await agg.verifyAndSettle(paymentHeader, {
amount: 1.00,
mint: USDC_MINT,
recipient: TREASURY
});
console.log(`Payment ${result.id} initiated...`);
// Poll for confirmation
for (let i = 0; i < 30; i++) {
await new Promise(r => setTimeout(r, 1000)); // Wait 1s
const status = await agg.getStatus(result.id);
if (status.status === 'confirmed') {
console.log(`Confirmed! Tx: ${status.txSignature}`);
return status;
}
if (status.status === 'failed') {
throw new Error('Payment failed');
}
}
throw new Error('Timeout waiting for confirmation');
}4. Username-Based Payments
// Pay to a username instead of wallet address
const result = await agg.verifyAndSettle(paymentHeader, {
amount: 5.00,
mint: USDC_MINT,
recipient: '@alice' // AGG resolves username to wallet
});Configuration
Environment Variables
# Required
AGG_API_KEY=pk_live_your_api_key_here
# Optional
AGG_API_BASE=https://agg.rendernet.work/pub/v1 # defaults to prod
WEBHOOK_SECRET=whsec_your_webhook_secret # for webhook verificationAPI Keys
Get your API key from the 555x402 Dashboard.
Key types:
pk_live_*- Production keyspk_test_*- Devnet/testnet keys
Rate Limits
- Default: 20 requests/second per API key
- Burst: 40 requests
- Contact support for higher limits
Webhook Configuration
Setting Up Webhooks
- Go to Dashboard → Settings → Webhooks
- Add your endpoint URL
- Copy the webhook secret
- Test with the webhook tester
Webhook Security
Always verify signatures:
import crypto from 'crypto';
function verifyWebhook(body: string, timestamp: string, signature: string, secret: string): boolean {
const sig = signature.replace(/^sha256=/, '');
const hash = crypto.createHash('sha256').update(body).digest();
const mac = crypto.createHmac('sha256', secret)
.update(timestamp)
.update('.')
.update(hash)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(mac, 'hex'),
Buffer.from(sig, 'hex')
);
} catch {
return false;
}
}Replay attack protection:
- Check
X-555-Timestampis within 5 minutes of current time - Store processed webhook IDs to prevent duplicates
Webhook Retry Logic
Failed webhooks are retried with exponential backoff:
- Attempt 1: Immediate
- Attempt 2: 60s later
- Attempt 3: 5min later
- Attempt 4: 15min later
- Attempt 5: 1hr later
- Attempt 6: 6hr later
- After 6 attempts: moved to dead-letter queue
Testing
Unit Tests
npm testIntegration Tests
Set up a test recipient wallet and API key:
import { AggClient } from '@555x402/agg';
describe('AGG Integration', () => {
const client = new AggClient(
process.env.AGG_TEST_API_KEY!,
'https://agg-devnet.rendernet.work/pub/v1'
);
it('should settle USDC payment', async () => {
const result = await client.verifyAndSettle(mockPaymentHeader, {
amount: 0.01,
mint: DEVNET_USDC_MINT,
recipient: TEST_WALLET
});
expect(result.status).toBe('pending');
expect(result.id).toBeDefined();
});
});Troubleshooting
Common Errors
"Invalid API key"
- Verify your API key is correct
- Check you're using the right environment (live vs test keys)
"Payment verification failed"
- Ensure payment header is base64-encoded
- Check the signature matches the payment data
- Verify sender has sufficient balance
"No healthy facilitator available"
- Rare; indicates all payment facilitators are down
- Retry after 30 seconds
- Check status page
"Amount mismatch"
- Payment amount doesn't match expected amount
- Ensure you're using correct decimal places (USDC = 6, SOL = 9)
Debug Mode
Enable detailed logging:
const client = new AggClient(apiKey, apiBase, { debug: true });This logs all API requests/responses to console.
Type Definitions
Full TypeScript definitions are included. Import types:
import type {
PaymentResult,
PaymentStatus,
Expected,
WebhookPayload
} from '@555x402/agg';Changelog
v0.1.0 (2025-11-11)
- Initial release
verifyAndSettleandgetStatusmethods- Webhook signature verification
- TypeScript support
Contributing
Contributions welcome! Please read CONTRIBUTING.md first.
License
MIT © 555x402
Support
- Documentation: https://docs.rendernet.work
- API Reference: https://docs.rendernet.work/api/agg
- Issues: https://github.com/Render-Network-OS/555x402-agg-sdk/issues
- Discord: https://discord.gg/555x402
- Email: [email protected]
Related Packages
- @555x402/hyperlink - Payment link generation
- @vap/sdk - Verifiable attention protocol
- @555x402/solana402-express - Express middleware
Built with ❤️ for the Solana ecosystem
