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
1 change: 1 addition & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
run: just test-and-benchmark

- name: Post CU report comment
if: github.event.pull_request.head.repo.full_name == github.repository
env:
GH_TOKEN: ${{ github.token }}
run: |
Expand Down
6 changes: 3 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -148,16 +148,16 @@ ensure-surfpool:
> /tmp/surfpool.log 2>&1 &
echo $! > .surfpool/pid.txt

for i in {1..7}; do
for i in {1..30}; do
if curl -sf -X POST http://localhost:8899 \
-H "Content-Type: application/json" \
-d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"getAccountInfo\",\"params\":[\"$PROG_ID\",{\"encoding\":\"base64\"}]}" \
| grep -q '"executable":true'; then
echo "✓ Program deployed successfully ($PROG_ID)"
exit 0
fi
echo "Waiting for program deployment... ($i/7)"
sleep 1
echo "Waiting for program deployment... ($i/30)"
sleep 2
done

echo "Error: Program deployment failed"
Expand Down
72 changes: 72 additions & 0 deletions webapp/api/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const CONFIG_PATH = join(__dirname, '../config.json');

let surfpoolProcess: ChildProcess | null = null;
let startingValidator = false;
let deployingProgram = false;

const MIN_SOL_AIRDROP = 0.1;
const MAX_SOL_AIRDROP = 10;
Expand Down Expand Up @@ -333,6 +334,70 @@ async function handleStartValidator(): Promise<Response> {
});
}

async function deployProgramViaSurfnet(): Promise<boolean> {
if (deployingProgram) return false;
deployingProgram = true;
try {
const soBytes = await readFile(SO_PATH);
const hexData = soBytes.toString('hex');
log('info', 'Deploying program via surfnet_writeProgram fallback', { size: soBytes.length });

const res = await fetch('http://127.0.0.1:8899', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'surfnet_writeProgram',
params: [PROGRAM_ADDRESS, hexData, 0],
}),
signal: AbortSignal.timeout(30_000),
});
const json = (await res.json()) as { result?: unknown; error?: { message?: string } };
if (json.error) {
log('error', 'surfnet_writeProgram failed', { error: json.error.message });
return false;
}
log('info', 'Program deployed via surfnet_writeProgram');

// Register IDL if available
const IDL_PATH = join(__dirname, '../../idl/subscriptions.json');
try {
const idlJson = JSON.parse(await readFile(IDL_PATH, 'utf-8'));
// surfnet_registerIdl expects an 'address' field at the top level
if (!idlJson.address) {
idlJson.address = PROGRAM_ADDRESS;
}
const idlRes = await fetch('http://127.0.0.1:8899', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'surfnet_registerIdl',
params: [idlJson],
}),
signal: AbortSignal.timeout(10_000),
});
const idlResult = (await idlRes.json()) as { result?: unknown; error?: { message?: string } };
if (idlResult.error) {
log('error', 'surfnet_registerIdl failed', { error: idlResult.error.message });
} else {
log('info', 'IDL registered via surfnet_registerIdl');
}
} catch (err) {
log('info', 'IDL registration skipped (file not found or RPC error)', { error: String(err) });
}

return true;
} catch (err) {
log('error', 'Fallback program deploy failed', { error: String(err) });
return false;
} finally {
deployingProgram = false;
}
}

async function handleValidatorStatus(): Promise<Response> {
let validatorRunning = false;
let programDeployed = false;
Expand Down Expand Up @@ -366,6 +431,13 @@ async function handleValidatorStatus(): Promise<Response> {
});
const acctJson = (await acctRes.json()) as { result?: { value?: { executable?: boolean } } };
programDeployed = acctJson.result?.value?.executable === true;

// Fallback: if runbook didn't deploy, kick off RPC cheatcode deploy
// without blocking status response. Next poll will see executable=true.
if (!programDeployed && !deployingProgram) {
void deployProgramViaSurfnet();
}

if (programDeployed) {
const config = await readConfig();
if (config.networks['localnet']?.programAddress !== programAddress) {
Expand Down
17 changes: 9 additions & 8 deletions webapp/src/components/plan/create-plan-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,17 +284,18 @@ export function CreatePlanDialog({ open, onOpenChange }: CreatePlanDialogProps)

<div className="grid gap-2">
<Label>Icon</Label>
<Select
value={selectedIcon || null}
onValueChange={value => setSelectedIcon(value ?? '')}
placeholder="Select an icon"
<select
value={selectedIcon}
onChange={e => setSelectedIcon(e.target.value)}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
{PLAN_ICONS.map(({ name, label, icon: Icon }) => (
<SelectItem key={name} value={name} icon={<Icon />}>
<option value="">Select an icon</option>
{PLAN_ICONS.map(({ name, label }) => (
<option key={name} value={name}>
{label}
</SelectItem>
</option>
))}
</Select>
</select>
</div>

<div className="grid gap-2">
Expand Down
13 changes: 11 additions & 2 deletions webapp/src/components/plan/enhanced-collect-payments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,18 @@ function EnhancedPlanCard({ planData, blockTs }: { planData: PlanSubscriberData;

return (
<div className="border border-sand-200 bg-sand-200 rounded-xl overflow-hidden">
<button
<div
role="button"
tabIndex={0}
aria-expanded={expanded}
className="w-full p-4 flex items-center justify-between cursor-pointer hover:bg-sand-100 transition-colors"
onClick={() => setExpanded(!expanded)}
onKeyDown={e => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setExpanded(!expanded);
}
}}
>
<div className="flex items-center gap-3">
<PlanIcon className="h-5 w-5 text-foreground" />
Expand Down Expand Up @@ -334,7 +343,7 @@ function EnhancedPlanCard({ planData, blockTs }: { planData: PlanSubscriberData;
Collect ${pendingUsd.toFixed(2)}
</SolanaButton>
</div>
</button>
</div>

{expanded && (
<div className="border-t border-sand-200">
Expand Down
3 changes: 3 additions & 0 deletions webapp/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import path from 'path';
import { defineConfig } from 'vite';

export default defineConfig({
define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV ?? 'development'),
},
plugins: [react()],
resolve: {
alias: {
Expand Down
Loading