Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,37 @@ See **[python/README.md](./python/README.md)** for the full guide.

---

## Verified end-to-end

Live on-chain settlements through `https://facilitator.acedata.cloud`, run from
[`typescript/scripts`](./typescript/scripts) on **2026-04-25**:

| Network | API endpoint | USDC paid | Settlement tx |
| ---------- | --------------------------- | --------- | ------------- |
| 🟦 Base | `POST /openai/chat/completions` | 0.020568 | [`0xa1697ee4…7c2708`](https://basescan.org/tx/0xa1697ee44d6c8d14c8a26c9a41b507fb718bac85c9153396b3e23b565a7c2708) |
| 🟦 Base | `POST /midjourney/imagine` (turbo) | 0.025708 | [`0x2d161b04…84539b2`](https://basescan.org/tx/0x2d161b04589aad026e6c575509f1867bbeeba6bff6f17064b1a423dd084539b2) |
| 🟨 SKALE | `POST /openai/chat/completions` | 0.020568 | [`0x621b361a…7b12979`](https://elated-tan-skat.explorer.mainnet.skalenodes.com/tx/0x621b361ad78e6bb6f910dba603a4267bca92ca8748894011cced803227b12979) |
| 🟨 SKALE | `POST /midjourney/imagine` (turbo) | 0.025708 | [`0x0e66f646…6b827d3`](https://elated-tan-skat.explorer.mainnet.skalenodes.com/tx/0x0e66f646bdfbf2e29ca8bc3bc19f252aa6109d8cf7aff1ab6836111e56b827d3) |
| 🟪 Solana | `POST /openai/chat/completions` | 0.095215 | [`4fsVAukg…D1Gd3t`](https://solscan.io/tx/4fsVAukgeFpGezcmu84xu4gYm1ANoAzgked8zhV78g2ffFL9AMpNP64Q1QkLoHtxgLuaPXcACBPZiLykwKD1Gd3t) |
| 🟪 Solana | `POST /midjourney/imagine` (turbo) | 0.115215 | [`5G438pwj…WUeBj`](https://solscan.io/tx/5G438pwjGBPjekkZZgHsqKgkV43nxCLoqMoue7J6aHhRVCYhtnR45EE2SzffnsbQMVxceb8BhdZFA3jTECNWUeBj) |

Reproduce with the live e2e scripts (need a funded test wallet for each chain):

```bash
cd typescript

# Base / SKALE — set X402B_BASE_PAYER_PRIVATE_KEY or SKALE_BASE_PRIVATE_KEY
TEST_API_PATH='/openai/chat/completions' \
TEST_BODY='{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":10}' \
npx tsx scripts/test-real-e2e.ts # Base
npx tsx scripts/test-skale-e2e.ts # SKALE

# Solana — set X402B_SOLANA_PAYER_PRIVATE_KEY (base58)
npx tsx scripts/test-solana-e2e.ts
```

---

## Repository layout

```
Expand Down
20 changes: 12 additions & 8 deletions typescript/scripts/test-real-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ function loadEnvFile(envPath: string): void {
}

const scriptDir = dirname(fileURLToPath(import.meta.url));
loadEnvFile(resolve(scriptDir, '../../.claude/.env'));
loadEnvFile(resolve(scriptDir, '../../PlatformBackend/.env'));
loadEnvFile(resolve(scriptDir, '../../../.claude/.env'));
loadEnvFile(resolve(scriptDir, '../../../PlatformBackend/.env'));

const API_BASE = process.env.API_BASE || 'https://api.acedata.cloud';
const TEST_API_PATH = process.env.TEST_API_PATH || '/suno/audios';
const TEST_BODY = process.env.TEST_BODY
? JSON.parse(process.env.TEST_BODY)
: { prompt: 'a short test beat', make_instrumental: true };
const rawKey = process.env.X402B_BASE_PAYER_PRIVATE_KEY?.trim();
if (!rawKey) {
console.error('ERROR: X402B_BASE_PAYER_PRIVATE_KEY is missing. Add it to .claude/.env or PlatformBackend/.env.');
Expand Down Expand Up @@ -69,11 +73,11 @@ async function main() {
console.log(`Payer wallet: ${wallet.address}\n`);

// Step 1: Request without auth → 402
console.log('--- Step 1: POST /suno/audios without auth ---');
const res1 = await fetch(`${API_BASE}/suno/audios`, {
console.log(`--- Step 1: POST ${TEST_API_PATH} without auth ---`);
const res1 = await fetch(`${API_BASE}${TEST_API_PATH}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: 'a short test beat', make_instrumental: true }),
body: JSON.stringify(TEST_BODY),
});
console.log(`Status: ${res1.status}`);
if (res1.status !== 402) {
Expand Down Expand Up @@ -143,14 +147,14 @@ async function main() {
console.log(` X-Payment header length: ${xPayment.length} chars\n`);

// Step 4: Retry with X-Payment header
console.log('--- Step 3: Retry POST /suno/audios with X-Payment ---');
const res2 = await fetch(`${API_BASE}/suno/audios`, {
console.log(`--- Step 3: Retry POST ${TEST_API_PATH} with X-Payment ---`);
const res2 = await fetch(`${API_BASE}${TEST_API_PATH}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Payment': xPayment,
},
body: JSON.stringify({ prompt: 'a short test beat', make_instrumental: true }),
body: JSON.stringify(TEST_BODY),
});

console.log(`Status: ${res2.status}`);
Expand Down
20 changes: 12 additions & 8 deletions typescript/scripts/test-skale-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,26 @@ async function parseBody(res: Response): Promise<unknown> {

async function main() {
const scriptDir = dirname(fileURLToPath(import.meta.url));
loadEnvFile(resolve(scriptDir, '../../.claude/.env'));
loadEnvFile(resolve(scriptDir, '../../../.claude/.env'));
loadEnvFile(resolve(scriptDir, '../../../PlatformBackend/.env'));

const apiBase = process.env.API_BASE || 'https://api.acedata.cloud';
const prompt = process.env.SUNO_PROMPT || 'a short SKALE test beat';
const testApiPath = process.env.TEST_API_PATH || '/suno/audios';
const testBody = process.env.TEST_BODY
? JSON.parse(process.env.TEST_BODY)
: { prompt: process.env.SUNO_PROMPT || 'a short SKALE test beat', make_instrumental: true };
const wallet = new Wallet(getPrivateKey());

console.log('=== SKALE X402 Real E2E Test ===');
console.log(`API: ${apiBase}`);
console.log(`Payer wallet: ${wallet.address}`);
console.log('');

console.log('--- Step 1: Request without auth ---');
const res1 = await fetch(`${apiBase}/suno/audios`, {
console.log(`--- Step 1: Request ${testApiPath} without auth ---`);
const res1 = await fetch(`${apiBase}${testApiPath}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt, make_instrumental: true }),
body: JSON.stringify(testBody),
});

const body1 = await parseBody(res1) as { accepts?: PaymentRequirement[] } | string | null;
Expand Down Expand Up @@ -174,14 +178,14 @@ async function main() {
console.log(` header length: ${xPayment.length}`);
console.log('');

console.log('--- Step 3: Retry with X-Payment ---');
const res2 = await fetch(`${apiBase}/suno/audios`, {
console.log(`--- Step 3: Retry ${testApiPath} with X-Payment ---`);
const res2 = await fetch(`${apiBase}${testApiPath}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Payment': xPayment,
},
body: JSON.stringify({ prompt, make_instrumental: true }),
body: JSON.stringify(testBody),
});

const body2 = await parseBody(res2);
Expand Down
18 changes: 10 additions & 8 deletions typescript/scripts/test-solana-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ function loadEnvFile(envPath: string): void {
}

const scriptDir = dirname(fileURLToPath(import.meta.url));
loadEnvFile(resolve(scriptDir, '../../.claude/.env'));
loadEnvFile(resolve(scriptDir, '../../PlatformBackend/.env'));
loadEnvFile(resolve(scriptDir, '../../../.claude/.env'));
loadEnvFile(resolve(scriptDir, '../../../PlatformBackend/.env'));

const API_BASE = process.env.API_BASE || 'https://api.acedata.cloud';
const SOLANA_RPC = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
Expand All @@ -57,12 +57,14 @@ if (!PAYER_PRIVATE_KEY) {
}

// Use a cheap API for testing (openai chat completion is fast + cheap)
const TEST_API_PATH = '/openai/chat/completions';
const TEST_BODY = {
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Say hi in 3 words' }],
max_tokens: 10,
};
const TEST_API_PATH = process.env.TEST_API_PATH || '/openai/chat/completions';
const TEST_BODY = process.env.TEST_BODY
? JSON.parse(process.env.TEST_BODY)
: {
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Say hi in 3 words' }],
max_tokens: 10,
};

interface PaymentRequirement {
scheme: string;
Expand Down
Loading