@@ -70,6 +70,7 @@ export interface ApiConfig {
7070 getSubscription ?: ( id : number ) => {
7171 id : number ; owner : string ; webhook_url : string ;
7272 balance : string ; deliveries : number ; active : boolean ; created_at : number ;
73+ demo ?: boolean ;
7374 } | null ;
7475 /** Optional delivery-rate calc for /sub/:id.ics balance-runout projection. */
7576 getDeliveryRate ?: ( id : number ) => { count : number ; window_seconds : number ; per_day : number } ;
@@ -356,6 +357,7 @@ interface BadgeSpec { label: string; value: string; color: string }
356357 */
357358function renderEmbed ( sub : NonNullable < ReturnType < NonNullable < ApiConfig [ 'getSubscription' ] > > > , contractHash : string , chain : string ) : string {
358359 const cspr = ( BigInt ( sub . balance ) / 1_000_000_000n ) . toString ( ) ;
360+ const demo = sub . demo === true ;
359361 const explorer = chain === 'casper-test' ? 'https://testnet.cspr.live' : 'https://cspr.live' ;
360362 const esc = ( s : string ) => s . replace ( / & / g, '&' ) . replace ( / < / g, '<' ) . replace ( / > / g, '>' ) . replace ( / " / g, '"' ) ;
361363 return `<!DOCTYPE html>
@@ -376,7 +378,7 @@ function renderEmbed(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubsc
376378 .num{font:600 28px/1 'Casper Sans',Inter,sans-serif;letter-spacing:-.02em}
377379 .small{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:#888}
378380 .pill{padding:2px 8px;font:600 9px 'JetBrains Mono';letter-spacing:.08em;border-radius:2px}
379- .ok{background:#3edc64;color:#000}.bad{background:#ff2d2e;color:#fff}
381+ .ok{background:#3edc64;color:#000}.bad{background:#ff2d2e;color:#fff}.demo{background:#ffb347;color:#000}
380382</style>
381383</head><body>
382384<a class="card" id="card" href="${ explorer } /contract/${ contractHash } " target="_blank" rel="noopener" title="Open Sluice subscription #${ sub . id } on cspr.live">
@@ -386,6 +388,7 @@ function renderEmbed(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubsc
386388 <span class="mono small">SUB</span>
387389 <span class="mono" style="font-weight:600">#${ sub . id } </span>
388390 <span style="flex:1"></span>
391+ ${ demo ? '<span class="pill demo" title="Simulated demo lane — no real on-chain escrow">DEMO</span>' : '' }
389392 <span class="pill ${ sub . active ? 'ok' : 'bad' } " id="status">${ sub . active ? 'ACTIVE' : 'INACTIVE' } </span>
390393 </div>
391394 <div class="row" style="gap:24px">
@@ -402,6 +405,7 @@ function renderEmbed(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubsc
402405 <span>${ chain . toUpperCase ( ) } </span>
403406 <span style="color:#444">·</span>
404407 <span>updated <span id="ago">just now</span></span>
408+ ${ demo ? '<span style="color:#444">·</span><span style="color:#ffb347">demo · simulated</span>' : '' }
405409 <span style="flex:1"></span>
406410 <span class="accent">sluice.unitynodes.com ↗</span>
407411 </div>
@@ -503,6 +507,7 @@ function renderOgSvg(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubsc
503507 const status = sub . active ? 'ACTIVE' : 'INACTIVE' ;
504508 const statusBg = sub . active ? '#3edc64' : '#ff2d2e' ;
505509 const chainBadge = chain . toUpperCase ( ) ;
510+ const demo = sub . demo === true ;
506511 const esc = ( s : string ) => s . replace ( / & / g, '&' ) . replace ( / < / g, '<' ) . replace ( / > / g, '>' ) ;
507512 return `<?xml version="1.0" encoding="UTF-8"?>
508513<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" width="1200" height="630" role="img" aria-label="Sluice subscription ${ sub . id } ">
@@ -530,6 +535,10 @@ function renderOgSvg(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubsc
530535 <rect width="100" height="32" fill="${ statusBg } "/>
531536 <text x="50" y="22" font-size="14" font-weight="600" fill="#000" text-anchor="middle" letter-spacing="2">${ status } </text>
532537 </g>
538+ ${ demo ? `<g transform="translate(172, 380)">
539+ <rect width="160" height="32" fill="#ffb347"/>
540+ <text x="80" y="22" font-size="14" font-weight="600" fill="#000" text-anchor="middle" letter-spacing="2">DEMO · SIMULATED</text>
541+ </g>` : '' }
533542 <g transform="translate(60, 470)">
534543 <text font-size="14" font-weight="500" fill="#666" letter-spacing="2">BALANCE</text>
535544 <text y="32" font-size="32" font-weight="500" fill="#fff">${ esc ( cspr ) } CSPR</text>
@@ -577,6 +586,7 @@ function buildIcs(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubscrip
577586 // Float division: integer BigInt division truncated sub-1-CSPR balances to 0,
578587 // which skipped the runway reminder for exactly the subs that need it.
579588 const balanceCspr = Number ( sub . balance ) / motesPerCspr ;
589+ const demo = sub . demo === true ;
580590 const perDeliveryMotes = Number ( process . env . SLUICE_PER_DELIVERY_MOTES ?? 500_000_000 ) ; // 0.5 CSPR
581591 const perDeliveryCspr = perDeliveryMotes / motesPerCspr ;
582592 const explorer = chain === 'casper-test' ? 'https://testnet.cspr.live' : 'https://cspr.live' ;
@@ -587,8 +597,10 @@ function buildIcs(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubscrip
587597 lines . push ( 'PRODID:-//Sluice//Subscription Calendar//EN' ) ;
588598 lines . push ( 'CALSCALE:GREGORIAN' ) ;
589599 lines . push ( 'METHOD:PUBLISH' ) ;
590- lines . push ( `X-WR-CALNAME:Sluice sub_${ sub . id } ` ) ;
591- lines . push ( `X-WR-CALDESC:On-chain event subscription on Casper (${ chain } )` ) ;
600+ lines . push ( `X-WR-CALNAME:Sluice sub_${ sub . id } ${ demo ? ' (demo)' : '' } ` ) ;
601+ lines . push ( `X-WR-CALDESC:${ demo
602+ ? `Demo lane on Casper (${ chain } ) — simulated deliveries, no real on-chain escrow`
603+ : `On-chain event subscription on Casper (${ chain } )` } `) ;
592604
593605 // Balance-runs-out projection, only if we have a measurable rate.
594606 if ( rate . per_day > 0 && perDeliveryCspr > 0 ) {
@@ -625,8 +637,9 @@ function buildIcs(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubscrip
625637 lines . push ( `SUMMARY:${ icsEscape ( `Sluice sub_${ sub . id } , weekly check-in` ) } ` ) ;
626638 lines . push ( `DESCRIPTION:${ icsEscape (
627639 `Quick weekly look at subscription #${ sub . id } :\n` +
640+ ( demo ? `• demo lane: simulated deliveries, no real on-chain escrow\n` : '' ) +
628641 `• balance: ${ balanceCspr } CSPR\n` +
629- `• deliveries to date: ${ sub . deliveries } \n` +
642+ `• deliveries to date: ${ sub . deliveries } ${ demo ? ' (simulated)' : '' } \n` +
630643 `• webhook: ${ sub . webhook_url } \n` +
631644 `Open the dashboard: https://sluice.unitynodes.com/app`
632645 ) } `) ;
@@ -635,7 +648,7 @@ function buildIcs(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubscrip
635648
636649 // Delivery-count milestones already crossed, fixed dates in the past so
637650 // calendar apps surface them as a one-tap history of the subscription.
638- for ( const milestone of [ 100 , 500 , 1000 , 5000 , 10000 ] ) {
651+ for ( const milestone of demo ? [ ] : [ 100 , 500 , 1000 , 5000 , 10000 ] ) {
639652 if ( sub . deliveries < milestone ) continue ;
640653 const at = new Date ( sub . created_at * 1000 ) ; // approximate, we don't track exact crossing
641654 lines . push ( 'BEGIN:VEVENT' ) ;
0 commit comments