When an agent attempts an operation that exceeds its policy limits or requires explicit human approval, the x0-guard program generates a Blink — a Solana Action that the owner can approve from any Blink-compatible wallet or application.
When Blinks Are Triggered
Agent attempts transfer exceeding daily limit
│
├── Guard validates: amount > remaining_allowance
│
├── Guard checks: blinks_generated_this_hour < 3
│
├── Guard emits: BlinkGenerated event
│
└── Agent returns Blink URL to caller
│
├── Owner opens URL in Blink-compatible wallet
├── Reviews details (amount, recipient, memo)
└── Signs + submits transaction
Blink Types
Transfer Approval
Escrow Release
Policy Update
Generated when an agent transfer exceeds policy limits.import { generateTransferBlink } from '@x0-protocol/sdk';
const blink = generateTransferBlink({
policyPda: agentPolicy,
agent: agentPubkey,
recipient: recipientPubkey,
amount: new BN(5_000_000_000), // 5,000 USDC
mint: usdcMint,
memo: 'Large infrastructure payment',
});
Fields:| Field | Description |
|---|
recipient | Destination wallet address |
amount | Transfer amount in micro-units |
mint | Token mint to transfer |
memo | Human-readable reason for transfer |
Generated when an escrow release requires owner confirmation.import { generateEscrowReleaseBlink } from '@x0-protocol/sdk';
const blink = generateEscrowReleaseBlink({
escrowPda: escrowAccount,
buyer: buyerPubkey,
seller: sellerPubkey,
amount: new BN(10_000_000),
memo: 'Service delivered successfully',
});
Generated for governance-sensitive policy changes.import { generatePolicyUpdateBlink } from '@x0-protocol/sdk';
const blink = generatePolicyUpdateBlink({
policyPda: agentPolicy,
changes: {
dailyLimit: new BN(500_000_000_000),
whitelistMode: 'merkle',
},
});
Rate Limiting
Blinks are rate-limited to prevent spam and denial-of-service:
| Parameter | Value | Description |
|---|
MAX_BLINKS_PER_HOUR | 3 | Maximum Blinks per agent per hour |
BLINK_GENERATION_COST_LAMPORTS | 1,000,000 | 0.001 SOL burned per Blink |
BLINK_EXPIRY_SECONDS | 900 | Blinks expire after 15 minutes |
If the agent exceeds the Blink rate limit, the transfer is rejected with BlinkRateLimitExceeded (0x1130).
Blink Lifecycle
Generation
The agent SDK creates a Blink object containing action details, encodes it as a Solana Actions URL, and optionally generates a QR code.
Delivery
The Blink URL is surfaced to the owner via the agent’s interface — push notification, chat message, email, or QR display.
Review
The owner opens the Blink in any Solana Actions-compatible wallet (Phantom, Solflare, Dialect, etc.) and reviews the requested action.
Approval
The owner signs the transaction in-wallet. The signed transaction is submitted directly to Solana — no intermediary.
Expiration
If the owner does not act within 15 minutes, the Blink expires. The agent may generate a new one (subject to rate limits).
The SDK produces an Actions-compliant JSON metadata response for rendering in Blink clients:
import { buildActionsMetadata } from '@x0-protocol/sdk';
const metadata = buildActionsMetadata(blink);
// Returns:
// {
// icon: "https://x0.org/icon.png",
// title: "Approve Transfer",
// description: "Agent requests 5,000 USDC transfer to ...",
// label: "Approve",
// links: {
// actions: [
// { label: "Approve Transfer", href: "..." },
// ]
// }
// }
QR Code Generation
For mobile-first approval flows:
import { generateQRData } from '@x0-protocol/sdk';
const qrPayload = generateQRData(blink, 'https://api.x0.org/actions');
// Encode qrPayload into a QR code image
Validation
import { validateBlink, isBlinkExpiredFromBlink } from '@x0-protocol/sdk';
if (!validateBlink(data)) {
throw new Error('Invalid blink structure');
}
if (isBlinkExpiredFromBlink(blink)) {
throw new Error('Blink has expired');
}
Events
| Event | Fields | When |
|---|
BlinkGenerated | policy, agent, amount, recipient, expires_at, timestamp | Blink created by guard |
| Code | Name | Description |
|---|
0x1130 | BlinkRateLimitExceeded | Agent exceeded 3 Blinks/hour |
0x1131 | BlinkExpired | Blink is past 15-minute window |
0x1132 | InvalidBlinkSignature | Submitted signature doesn’t match |
0x1133 | BlinkAlreadyProcessed | Blink was already approved/rejected |
Last modified on February 8, 2026