import { createKeyPairSignerFromBytes } from '@solana/kit';
import { x402Client, x402HTTPClient } from '@x402/core/client';
import { registerExactSvmScheme } from '@x402/svm/exact/client';
const config = {
apiUrl: process.env.API_URL ?? 'https://api.mobula.io',
};
const SUBSCRIPTION_ENDPOINT = `${config.apiUrl}/x402/agent/subscription`;
/**
* Decode a base58 string (e.g. Solana private key) into raw bytes.
* Uses the standard Bitcoin/Solana base58 alphabet (no 0, O, I, l).
*/
function base58ToBytes(b58: string): Uint8Array {
const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const map = new Uint8Array(256).fill(255);
for (let i = 0; i < ALPHABET.length; i++) map[ALPHABET.charCodeAt(i)] = i;
const bytes: number[] = [0];
for (const char of b58) {
const value = map[char.charCodeAt(0)];
if (value === undefined || value === 255) throw new Error(`Invalid base58: ${char}`);
let carry: number = value;
for (let j = bytes.length - 1; j >= 0; j--) {
carry += (bytes[j] as number) * 58;
bytes[j] = carry & 0xff;
carry >>= 8;
}
while (carry > 0) {
bytes.unshift(carry & 0xff);
carry >>= 8;
}
}
for (const char of b58) {
if (char !== '1') break;
bytes.unshift(0);
}
return new Uint8Array(bytes);
}
async function main() {
const privateKeyB58 = process.env.SOLANA_PRIVATE_KEY;
if (!privateKeyB58) {
console.error('ERROR: SOLANA_PRIVATE_KEY is not set.');
console.error(' Provide a base58-encoded private key for a wallet holding USDC and SOL.');
process.exit(1);
}
const signer = await createKeyPairSignerFromBytes(base58ToBytes(privateKeyB58));
const walletAddress = typeof signer.address === 'string' ? signer.address : String(signer.address);
const client = new x402Client();
registerExactSvmScheme(client, { signer });
const httpClient = new x402HTTPClient(client);
// ── Step 1: Probe for 402 ─────────────────────────────────────────────────
console.log('Step 1 — Probing subscription endpoint...');
console.log(' Wallet:', walletAddress);
console.log(` GET ${SUBSCRIPTION_ENDPOINT}`);
const probeRes = await fetch(SUBSCRIPTION_ENDPOINT);
console.log(' Status:', probeRes.status);
if (probeRes.status !== 402) {
console.error(' Unexpected response (expected 402):', await probeRes.text());
process.exit(1);
}
console.log(' 402 received — payment details follow.');
const paymentRequiredHeader = probeRes.headers.get('payment-required');
if (!paymentRequiredHeader) {
console.error(' Missing payment-required header.');
process.exit(1);
}
const decoded = JSON.parse(Buffer.from(paymentRequiredHeader, 'base64').toString('utf-8'));
const svmOption = decoded.accepts?.find(
(a: { network: string }) => a.network?.startsWith('solana:'),
);
if (!svmOption) {
console.error(' No Solana payment option found in 402 response.');
console.error(' Check that API_URL is set to https://api.mobula.io');
process.exit(1);
}
// ── Step 2: Sign payment ──────────────────────────────────────────────────
console.log('\nStep 2 — Signing $0.001 USDC payment...');
const paymentRequired = httpClient.getPaymentRequiredResponse(
(name) => probeRes.headers.get(name),
decoded,
);
let paymentHeaders: Record<string, string>;
try {
const payload = await httpClient.createPaymentPayload(paymentRequired);
paymentHeaders = httpClient.encodePaymentSignatureHeader(payload);
} catch (err) {
console.error(' Failed to sign payment:', err);
process.exit(1);
}
console.log(' Payment signed.');
// ── Step 3: Submit and receive plan stats ─────────────────────────────────
console.log('\nStep 3 — Fetching plan stats...');
const paidRes = await fetch(SUBSCRIPTION_ENDPOINT, { headers: paymentHeaders });
const body = await paidRes.text();
console.log(' Status:', paidRes.status);
if (paidRes.status === 404) {
console.error('\n No agent found for this wallet.');
console.error(' Subscribe first: bun run scripts/src/x402/agent-subscribe-solana.ts');
process.exit(1);
}
if (paidRes.status !== 200) {
console.error('\n Request failed.');
console.error(' Response:', body || '(empty)');
process.exit(1);
}
// ── Step 4: Print results ─────────────────────────────────────────────────
const data = JSON.parse(body) as {
user_id: string;
api_keys: string[];
plan: string;
last_payment: string | null;
payment_frequency: string;
left_days: number;
credits_left: number;
plan_active: boolean;
};
console.log('\n✓ Subscription status retrieved.');
console.log('');
console.log(' user_id :', data.user_id);
console.log(' plan :', data.plan);
console.log(' plan_active :', data.plan_active);
console.log(' payment_frequency:', data.payment_frequency);
console.log(' last_payment :', data.last_payment ?? '—');
console.log(' left_days :', data.left_days);
console.log(' credits_left :', data.credits_left);
console.log(' api_keys :', data.api_keys.length, 'key(s)');
data.api_keys.forEach((k, i) => {
console.log(` [${i + 1}]`, k);
});
process.exit(0);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});