sui.ski 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +311 -0
- package/CLAUDE.md +292 -0
- package/CODEBASE_GUIDE.md +217 -0
- package/README.md +77 -0
- package/biome.json +28 -0
- package/package.json +73 -0
- package/scripts/deploy-messaging-mainnet.sh +184 -0
- package/scripts/extract-suins-object.ts +180 -0
- package/scripts/full-deploy.sh +26 -0
- package/scripts/obsidian.ts +243 -0
- package/scripts/set-suins-contenthash.ts +130 -0
- package/scripts/setup-ika-dwallet.ts +338 -0
- package/scripts/transfer-upgrade-cap-from-nft.ts +86 -0
- package/src/durable-objects/wallet-session.ts +333 -0
- package/src/handlers/app.ts +1430 -0
- package/src/handlers/authenticated-events.ts +267 -0
- package/src/handlers/dashboard.ts +1659 -0
- package/src/handlers/landing.ts +6751 -0
- package/src/handlers/mcp.ts +556 -0
- package/src/handlers/messaging-sdk.ts +220 -0
- package/src/handlers/profile.css.ts +9332 -0
- package/src/handlers/profile.ts +12640 -0
- package/src/handlers/register2.ts +2811 -0
- package/src/handlers/ski-sign.ts +1901 -0
- package/src/handlers/ski.ts +314 -0
- package/src/handlers/thunder.ts +940 -0
- package/src/handlers/vault.ts +284 -0
- package/src/handlers/wallet-api.ts +169 -0
- package/src/handlers/x402-register.ts +601 -0
- package/src/index.test.ts +55 -0
- package/src/index.ts +512 -0
- package/src/resolvers/content.ts +231 -0
- package/src/resolvers/rpc.ts +222 -0
- package/src/resolvers/suins.ts +266 -0
- package/src/sdk/messaging.ts +279 -0
- package/src/types.ts +230 -0
- package/src/utils/agent-keypair.ts +40 -0
- package/src/utils/authenticated-events.ts +280 -0
- package/src/utils/cache.ts +82 -0
- package/src/utils/media-pack.ts +27 -0
- package/src/utils/mmr.ts +181 -0
- package/src/utils/ns-price.ts +529 -0
- package/src/utils/og-image.ts +141 -0
- package/src/utils/onchain-activity.ts +211 -0
- package/src/utils/onchain-listing.ts +39 -0
- package/src/utils/premium.ts +29 -0
- package/src/utils/pricing.ts +291 -0
- package/src/utils/pyth-price-info.ts +63 -0
- package/src/utils/response.ts +204 -0
- package/src/utils/rpc.ts +25 -0
- package/src/utils/shared-wallet-js.ts +166 -0
- package/src/utils/social.ts +152 -0
- package/src/utils/status.ts +39 -0
- package/src/utils/subdomain.ts +116 -0
- package/src/utils/surflux-grpc.ts +241 -0
- package/src/utils/swap-transactions.ts +1222 -0
- package/src/utils/thunder-css.ts +1341 -0
- package/src/utils/thunder-js.ts +5046 -0
- package/src/utils/transactions.ts +65 -0
- package/src/utils/vault.ts +18 -0
- package/src/utils/wallet-kit-js.ts +2312 -0
- package/src/utils/wallet-session-js.ts +192 -0
- package/src/utils/wallet-tx-js.ts +2287 -0
- package/src/utils/wallet-ui-js.ts +3057 -0
- package/src/utils/x402-middleware.ts +428 -0
- package/src/utils/x402-sui.ts +171 -0
- package/src/utils/zksend-js.ts +166 -0
- package/tsconfig.json +22 -0
- package/workers/x402-multichain/src/index.ts +237 -0
- package/workers/x402-multichain/src/types.ts +80 -0
- package/workers/x402-multichain/tsconfig.json +20 -0
- package/workers/x402-multichain/wrangler.toml +11 -0
- package/wrangler.toml +84 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import { WalletSession } from './durable-objects/wallet-session'
|
|
3
|
+
import { handleAppRequest } from './handlers/app'
|
|
4
|
+
import { handleAuthenticatedEvents } from './handlers/authenticated-events'
|
|
5
|
+
import { generateDashboardPage } from './handlers/dashboard'
|
|
6
|
+
import {
|
|
7
|
+
apiRoutes,
|
|
8
|
+
cloudflareGracePageHTML,
|
|
9
|
+
generateCancelBidPage,
|
|
10
|
+
landingPageHTML,
|
|
11
|
+
} from './handlers/landing'
|
|
12
|
+
import { createSuiMcpHandler } from './handlers/mcp'
|
|
13
|
+
import { handleMessagingApi } from './handlers/messaging-sdk'
|
|
14
|
+
import { generateEmbedProfilePage, generateProfilePage } from './handlers/profile'
|
|
15
|
+
import { handleBuildRegisterTx, handleRegistrationSubmission } from './handlers/register2'
|
|
16
|
+
import { generateSkiPage } from './handlers/ski'
|
|
17
|
+
import { generateSkiSignPage } from './handlers/ski-sign'
|
|
18
|
+
import { vaultRoutes } from './handlers/vault'
|
|
19
|
+
import {
|
|
20
|
+
handleWalletChallenge,
|
|
21
|
+
handleWalletCheck,
|
|
22
|
+
handleWalletConnect,
|
|
23
|
+
handleWalletDisconnect,
|
|
24
|
+
} from './handlers/wallet-api'
|
|
25
|
+
import { thunderRoutes } from './handlers/thunder'
|
|
26
|
+
import { x402RegisterRoutes } from './handlers/x402-register'
|
|
27
|
+
import { resolveContent, resolveDirectContent } from './resolvers/content'
|
|
28
|
+
import { handleRPCRequest } from './resolvers/rpc'
|
|
29
|
+
import { resolveSuiNS } from './resolvers/suins'
|
|
30
|
+
import type { Env, ParsedSubdomain, SuiNSRecord } from './types'
|
|
31
|
+
import {
|
|
32
|
+
generateDotSkiPngBytes,
|
|
33
|
+
generateSkiLogoSvg,
|
|
34
|
+
generateSuiIconSvg,
|
|
35
|
+
generateThunderIconBytes,
|
|
36
|
+
} from './utils/media-pack'
|
|
37
|
+
import {
|
|
38
|
+
generateBrandOgPng,
|
|
39
|
+
generateBrandOgSvg,
|
|
40
|
+
generateFaviconSvg,
|
|
41
|
+
generateProfileOgSvg,
|
|
42
|
+
} from './utils/og-image'
|
|
43
|
+
import { errorResponse, htmlResponse, jsonResponse, notFoundPage } from './utils/response'
|
|
44
|
+
import { ensureRpcEnv } from './utils/rpc'
|
|
45
|
+
import { isTwitterPreviewBot } from './utils/social'
|
|
46
|
+
import { parseSubdomain } from './utils/subdomain'
|
|
47
|
+
|
|
48
|
+
export { WalletSession }
|
|
49
|
+
|
|
50
|
+
type AppEnv = {
|
|
51
|
+
Bindings: Env
|
|
52
|
+
Variables: {
|
|
53
|
+
parsed: ParsedSubdomain
|
|
54
|
+
hostname: string
|
|
55
|
+
env: Env
|
|
56
|
+
session: {
|
|
57
|
+
address: string | null
|
|
58
|
+
walletName: string | null
|
|
59
|
+
verified: boolean
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const app = new Hono<AppEnv>()
|
|
65
|
+
|
|
66
|
+
app.use('*', async (c, next) => {
|
|
67
|
+
if (c.req.method === 'OPTIONS') {
|
|
68
|
+
const requestedHeaders = c.req.header('Access-Control-Request-Headers') || 'Content-Type'
|
|
69
|
+
return new Response(null, {
|
|
70
|
+
headers: {
|
|
71
|
+
'Access-Control-Allow-Origin': '*',
|
|
72
|
+
'Access-Control-Allow-Methods': 'GET, HEAD, POST, PUT, DELETE, OPTIONS',
|
|
73
|
+
'Access-Control-Allow-Headers': requestedHeaders,
|
|
74
|
+
'Access-Control-Max-Age': '86400',
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
await next()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
app.use('*', async (c, next) => {
|
|
82
|
+
const url = new URL(c.req.url)
|
|
83
|
+
const testHost = url.searchParams.get('host') || c.req.header('X-Host')
|
|
84
|
+
const hostname = testHost || url.hostname
|
|
85
|
+
const parsed = parseSubdomain(hostname)
|
|
86
|
+
|
|
87
|
+
let env = c.env
|
|
88
|
+
if (parsed.networkOverride) {
|
|
89
|
+
env = { ...env, SUI_NETWORK: parsed.networkOverride, SUI_RPC_URL: '' }
|
|
90
|
+
if (parsed.networkOverride === 'testnet') {
|
|
91
|
+
env = {
|
|
92
|
+
...env,
|
|
93
|
+
SEAL_PACKAGE_ID: env.SEAL_TESTNET_PACKAGE_ID || env.SEAL_PACKAGE_ID,
|
|
94
|
+
SEAL_KEY_SERVERS: env.SEAL_TESTNET_KEY_SERVERS || env.SEAL_KEY_SERVERS,
|
|
95
|
+
SEAL_APPROVE_TARGET: env.SEAL_TESTNET_APPROVE_TARGET || env.SEAL_APPROVE_TARGET,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
c.set('env', ensureRpcEnv(env))
|
|
100
|
+
c.set('parsed', parsed)
|
|
101
|
+
c.set('hostname', hostname)
|
|
102
|
+
await next()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const SESSION_ROUTES = new Set(['root', 'suins'])
|
|
106
|
+
|
|
107
|
+
function getCookieValue(cookieHeader: string, name: string): string | null {
|
|
108
|
+
if (!cookieHeader) return null
|
|
109
|
+
const parts = cookieHeader.split(';')
|
|
110
|
+
for (let i = 0; i < parts.length; i++) {
|
|
111
|
+
const part = parts[i].trim()
|
|
112
|
+
if (!part) continue
|
|
113
|
+
const eqIndex = part.indexOf('=')
|
|
114
|
+
if (eqIndex <= 0) continue
|
|
115
|
+
if (part.slice(0, eqIndex).trim() !== name) continue
|
|
116
|
+
return part.slice(eqIndex + 1).trim()
|
|
117
|
+
}
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function decodeCookieValue(value: string | null): string | null {
|
|
122
|
+
if (!value) return null
|
|
123
|
+
try {
|
|
124
|
+
return decodeURIComponent(value)
|
|
125
|
+
} catch {
|
|
126
|
+
return value
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function routeNeedsSession(parsed: ParsedSubdomain, pathname: string): boolean {
|
|
131
|
+
if (!SESSION_ROUTES.has(parsed.type)) return false
|
|
132
|
+
if (pathname.startsWith('/api/') || pathname === '/favicon.svg') return false
|
|
133
|
+
if (
|
|
134
|
+
pathname.startsWith('/og/') ||
|
|
135
|
+
pathname.startsWith('/walrus/') ||
|
|
136
|
+
pathname.startsWith('/ipfs/')
|
|
137
|
+
)
|
|
138
|
+
return false
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
app.use('*', async (c, next) => {
|
|
143
|
+
const cookieHeader = c.req.header('Cookie') || ''
|
|
144
|
+
const sessionId = decodeCookieValue(getCookieValue(cookieHeader, 'session_id'))
|
|
145
|
+
const walletAddress = decodeCookieValue(getCookieValue(cookieHeader, 'wallet_address'))
|
|
146
|
+
const walletName = decodeCookieValue(getCookieValue(cookieHeader, 'wallet_name'))
|
|
147
|
+
|
|
148
|
+
const session = {
|
|
149
|
+
address: walletAddress || null,
|
|
150
|
+
walletName: walletName || null,
|
|
151
|
+
verified: false,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (sessionId) {
|
|
155
|
+
const parsed = c.get('parsed')
|
|
156
|
+
const url = new URL(c.req.url)
|
|
157
|
+
if (routeNeedsSession(parsed, url.pathname)) {
|
|
158
|
+
const stub = c.env.WALLET_SESSIONS.getByName('global')
|
|
159
|
+
const info = await stub.getSessionInfo(sessionId)
|
|
160
|
+
if (info) {
|
|
161
|
+
session.address = info.address
|
|
162
|
+
session.verified = info.verified
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
c.set('session', session)
|
|
168
|
+
await next()
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
app.post('/api/wallet/challenge', async (c) => {
|
|
172
|
+
return handleWalletChallenge(c.req.raw, c.env)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
app.post('/api/wallet/connect', async (c) => {
|
|
176
|
+
return handleWalletConnect(c.req.raw, c.env)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
app.get('/api/wallet/check', async (c) => {
|
|
180
|
+
return handleWalletCheck(c.req.raw, c.env)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
app.post('/api/wallet/disconnect', async (c) => {
|
|
184
|
+
return handleWalletDisconnect(c.req.raw, c.env)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
app.post('/api/register/build-tx', async (c) => {
|
|
188
|
+
return handleBuildRegisterTx(c.req.raw, c.get('env'))
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
app.post('/api/register/submit', async (c) => {
|
|
192
|
+
return handleRegistrationSubmission(c.req.raw, c.get('env'))
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
app.use('*', async (c, next) => {
|
|
196
|
+
const parsed = c.get('parsed')
|
|
197
|
+
const env = c.get('env')
|
|
198
|
+
|
|
199
|
+
switch (parsed.type) {
|
|
200
|
+
case 'rpc':
|
|
201
|
+
return handleRPCRequest(c.req.raw, env)
|
|
202
|
+
case 'app':
|
|
203
|
+
return handleAppRequest(c.req.raw, env, c.get('session'))
|
|
204
|
+
case 'dashboard':
|
|
205
|
+
return htmlResponse(generateDashboardPage(env))
|
|
206
|
+
case 'content': {
|
|
207
|
+
const result = await resolveDirectContent(parsed.subdomain, env)
|
|
208
|
+
if (!result.found) return errorResponse(result.error || 'Content not found', 'NOT_FOUND', 404)
|
|
209
|
+
return result.data as Response
|
|
210
|
+
}
|
|
211
|
+
case 'mvr': {
|
|
212
|
+
const mvrInfo = parsed.mvrInfo
|
|
213
|
+
if (!mvrInfo) return errorResponse('Missing MVR info', 'INVALID_MVR', 400)
|
|
214
|
+
return jsonResponse({
|
|
215
|
+
mvrPackage: `@${mvrInfo.suinsName}/${mvrInfo.packageName}`,
|
|
216
|
+
version: mvrInfo.version || 'latest',
|
|
217
|
+
message: 'MVR package resolution coming soon',
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
default:
|
|
221
|
+
await next()
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
app.all('/api/events/*', async (c) => handleAuthenticatedEvents(c.req.raw, c.get('env')))
|
|
226
|
+
app.all('/api/app/*', async (c) => handleAppRequest(c.req.raw, c.get('env'), c.get('session')))
|
|
227
|
+
app.use('/api/agents/x402-register/*', async (c, next) => {
|
|
228
|
+
if (c.get('parsed').type !== 'root') return c.notFound()
|
|
229
|
+
await next()
|
|
230
|
+
})
|
|
231
|
+
app.route('/api/agents/x402-register', x402RegisterRoutes)
|
|
232
|
+
app.use('/api/thunder', async (c, next) => {
|
|
233
|
+
if (c.get('parsed').type !== 'root') return c.notFound()
|
|
234
|
+
await next()
|
|
235
|
+
})
|
|
236
|
+
app.use('/api/thunder/*', async (c, next) => {
|
|
237
|
+
if (c.get('parsed').type !== 'root') return c.notFound()
|
|
238
|
+
await next()
|
|
239
|
+
})
|
|
240
|
+
app.route('/api/thunder', thunderRoutes)
|
|
241
|
+
app.all('/api/agents/*', async (c) => handleAppRequest(c.req.raw, c.get('env'), c.get('session')))
|
|
242
|
+
app.all('/api/ika/*', async (c) => handleAppRequest(c.req.raw, c.get('env'), c.get('session')))
|
|
243
|
+
app.all('/api/llm/*', async (c) => handleAppRequest(c.req.raw, c.get('env'), c.get('session')))
|
|
244
|
+
app.all('/api/messaging/*', async (c) =>
|
|
245
|
+
handleMessagingApi(c.req.raw, c.get('env'), new URL(c.req.url)),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
app.route('/api/vault', vaultRoutes)
|
|
249
|
+
app.route('/api', apiRoutes)
|
|
250
|
+
|
|
251
|
+
const SVG_HEADERS = { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=604800' }
|
|
252
|
+
|
|
253
|
+
app.get('/favicon.svg', () => new Response(generateFaviconSvg(), { headers: SVG_HEADERS }))
|
|
254
|
+
|
|
255
|
+
app.get('/og-image.svg', () => new Response(generateBrandOgSvg(), { headers: SVG_HEADERS }))
|
|
256
|
+
|
|
257
|
+
app.get(
|
|
258
|
+
'/media-pack/SuiIcon.svg',
|
|
259
|
+
() => new Response(generateSuiIconSvg(), { headers: SVG_HEADERS }),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
app.get(
|
|
263
|
+
'/media-pack/skilogo.svg',
|
|
264
|
+
() => new Response(generateSkiLogoSvg(), { headers: SVG_HEADERS }),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
const PNG_HEADERS = { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=604800' }
|
|
268
|
+
|
|
269
|
+
app.get(
|
|
270
|
+
'/media-pack/dotSKI.png',
|
|
271
|
+
() =>
|
|
272
|
+
new Response(generateDotSkiPngBytes(), {
|
|
273
|
+
headers: {
|
|
274
|
+
'Content-Type': 'image/webp',
|
|
275
|
+
'Cache-Control': 'public, max-age=604800',
|
|
276
|
+
},
|
|
277
|
+
}),
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
app.get(
|
|
281
|
+
'/media-pack/ThunderIcon.png',
|
|
282
|
+
() =>
|
|
283
|
+
new Response(generateThunderIconBytes(), {
|
|
284
|
+
headers: {
|
|
285
|
+
'Content-Type': 'image/webp',
|
|
286
|
+
'Cache-Control': 'public, max-age=604800',
|
|
287
|
+
},
|
|
288
|
+
}),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
app.get('/og-image.png', () => new Response(generateBrandOgPng(), { headers: PNG_HEADERS }))
|
|
292
|
+
|
|
293
|
+
app.get('/og/:name{.+\\.svg}', (c) => {
|
|
294
|
+
const rawName = c.req.param('name').replace(/\.svg$/, '')
|
|
295
|
+
const name = decodeURIComponent(rawName)
|
|
296
|
+
return new Response(generateProfileOgSvg(name, ''), { headers: SVG_HEADERS })
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
app.get('/walrus/:id{.+}', async (c) => {
|
|
300
|
+
if (c.get('parsed').type !== 'root') return c.notFound()
|
|
301
|
+
const subdomain = `walrus-${c.req.param('id')}`
|
|
302
|
+
const result = await resolveDirectContent(subdomain, c.get('env'))
|
|
303
|
+
if (!result.found) return errorResponse(result.error || 'Content not found', 'NOT_FOUND', 404)
|
|
304
|
+
return result.data as Response
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
app.get('/ipfs/:id{.+}', async (c) => {
|
|
308
|
+
if (c.get('parsed').type !== 'root') return c.notFound()
|
|
309
|
+
const subdomain = `ipfs-${c.req.param('id')}`
|
|
310
|
+
const result = await resolveDirectContent(subdomain, c.get('env'))
|
|
311
|
+
if (!result.found) return errorResponse(result.error || 'Content not found', 'NOT_FOUND', 404)
|
|
312
|
+
return result.data as Response
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
app.get('/in', async (c) => {
|
|
316
|
+
return htmlResponse(generateSkiPage(c.get('env'), c.get('session')))
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
app.get('/sign', async (c) => {
|
|
320
|
+
return htmlResponse(generateSkiSignPage(c.get('env')), 200, {
|
|
321
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
322
|
+
Pragma: 'no-cache',
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
app.get('/cancel-bid', async (c) => {
|
|
327
|
+
if (c.get('parsed').type !== 'root') return c.notFound()
|
|
328
|
+
const bidId = new URL(c.req.url).searchParams.get('bid') || ''
|
|
329
|
+
const env = c.get('env')
|
|
330
|
+
return htmlResponse(generateCancelBidPage(env, bidId), 200, {
|
|
331
|
+
'Cache-Control': 'no-store',
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
app.get('/cloudflare', async (c) => {
|
|
336
|
+
if (c.get('parsed').type !== 'root') return c.notFound()
|
|
337
|
+
return htmlResponse(cloudflareGracePageHTML(c.get('env').SUI_NETWORK))
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
app.get('/grace', async (c) => {
|
|
341
|
+
if (c.get('parsed').type !== 'root') return c.notFound()
|
|
342
|
+
return htmlResponse(cloudflareGracePageHTML(c.get('env').SUI_NETWORK))
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
app.all('/app', async (c) => {
|
|
346
|
+
if (c.get('parsed').type !== 'root') return c.notFound()
|
|
347
|
+
return handleAppRequest(c.req.raw, c.get('env'), c.get('session'))
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
app.all('/app/*', async (c) => {
|
|
351
|
+
if (c.get('parsed').type !== 'root') return c.notFound()
|
|
352
|
+
return handleAppRequest(c.req.raw, c.get('env'), c.get('session'))
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
app.all('/mcp', async (c) => {
|
|
356
|
+
if (c.get('parsed').type !== 'root') return c.notFound()
|
|
357
|
+
const handler = createSuiMcpHandler(c.get('env'))
|
|
358
|
+
return handler(c.req.raw, c.env, c.executionCtx)
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
app.all('*', async (c) => {
|
|
362
|
+
const parsed = c.get('parsed')
|
|
363
|
+
const env = c.get('env')
|
|
364
|
+
const url = new URL(c.req.url)
|
|
365
|
+
|
|
366
|
+
if (parsed.type === 'root') {
|
|
367
|
+
const session = c.get('session')
|
|
368
|
+
const hasSession = !!session.address
|
|
369
|
+
if (!hasSession) {
|
|
370
|
+
const cache = caches.default
|
|
371
|
+
const landingCacheUrl = `https://cache.internal/landing:${url.hostname}${url.pathname || '/'}`
|
|
372
|
+
const cached = await cache.match(landingCacheUrl)
|
|
373
|
+
if (cached) return cached
|
|
374
|
+
}
|
|
375
|
+
const canonicalUrl = `${url.protocol}//${url.hostname}${url.pathname || '/'}`
|
|
376
|
+
const response = htmlResponse(
|
|
377
|
+
landingPageHTML(env.SUI_NETWORK, {
|
|
378
|
+
canonicalUrl,
|
|
379
|
+
rpcUrl: env.SUI_RPC_URL,
|
|
380
|
+
network: env.SUI_NETWORK,
|
|
381
|
+
session: c.get('session'),
|
|
382
|
+
}),
|
|
383
|
+
200,
|
|
384
|
+
hasSession ? {} : { 'Cache-Control': 'public, s-maxage=120, stale-while-revalidate=300' },
|
|
385
|
+
)
|
|
386
|
+
if (!hasSession) {
|
|
387
|
+
const cache = caches.default
|
|
388
|
+
const landingCacheUrl = `https://cache.internal/landing:${url.hostname}${url.pathname || '/'}`
|
|
389
|
+
c.executionCtx.waitUntil(cache.put(landingCacheUrl, response.clone()))
|
|
390
|
+
}
|
|
391
|
+
return response
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (parsed.type === 'suins') {
|
|
395
|
+
if (url.pathname === '/favicon.svg') {
|
|
396
|
+
return new Response(generateFaviconSvg(), { headers: SVG_HEADERS })
|
|
397
|
+
}
|
|
398
|
+
if (url.pathname === '/og-image.svg') {
|
|
399
|
+
return new Response(generateBrandOgSvg(), { headers: SVG_HEADERS })
|
|
400
|
+
}
|
|
401
|
+
if (url.pathname.startsWith('/og/') && url.pathname.endsWith('.svg')) {
|
|
402
|
+
const nameSlug = url.pathname.slice(4, -4)
|
|
403
|
+
const ogResult = await resolveSuiNS(parsed.subdomain, env)
|
|
404
|
+
const ogAddr =
|
|
405
|
+
ogResult.found && ogResult.data && 'address' in ogResult.data
|
|
406
|
+
? (ogResult.data as SuiNSRecord).address
|
|
407
|
+
: ''
|
|
408
|
+
return new Response(generateProfileOgSvg(decodeURIComponent(nameSlug), ogAddr), {
|
|
409
|
+
headers: SVG_HEADERS,
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const hostname = c.get('hostname')
|
|
414
|
+
const userAgent = c.req.header('user-agent')
|
|
415
|
+
const skipCache = url.searchParams.has('nocache') || url.searchParams.has('refresh')
|
|
416
|
+
const hasSession = !!c.get('session').address
|
|
417
|
+
const wantsJson = url.pathname === '/json' || url.searchParams.has('json')
|
|
418
|
+
const wantsProfile = url.pathname === '/home' || url.searchParams.has('profile')
|
|
419
|
+
const wantsEmbed = url.searchParams.has('embed')
|
|
420
|
+
|
|
421
|
+
const canServeCached = !skipCache && !hasSession && !wantsJson && !wantsProfile
|
|
422
|
+
if (canServeCached) {
|
|
423
|
+
const cache = caches.default
|
|
424
|
+
const cacheUrl = new URL(`https://cache.internal/profile:${hostname}${url.pathname || '/'}`)
|
|
425
|
+
const cached = await cache.match(cacheUrl.toString())
|
|
426
|
+
if (cached) return cached
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const result = await resolveSuiNS(parsed.subdomain, env, skipCache)
|
|
430
|
+
|
|
431
|
+
if (!result.found)
|
|
432
|
+
return notFoundPage(parsed.subdomain, env, result.available, c.get('session'))
|
|
433
|
+
|
|
434
|
+
const record = result.data as SuiNSRecord
|
|
435
|
+
const normalizedPath = url.pathname || '/'
|
|
436
|
+
const canonicalUrl = `${url.protocol}//${hostname}${normalizedPath}`
|
|
437
|
+
const profileOptions = {
|
|
438
|
+
canonicalUrl,
|
|
439
|
+
hostname,
|
|
440
|
+
inGracePeriod: result.inGracePeriod || false,
|
|
441
|
+
session: c.get('session'),
|
|
442
|
+
}
|
|
443
|
+
const shouldServeProfileForTwitter =
|
|
444
|
+
isTwitterPreviewBot(userAgent ?? null) && normalizedPath === '/'
|
|
445
|
+
let cachedProfileHtml: string | null = null
|
|
446
|
+
const renderProfile = () => {
|
|
447
|
+
if (cachedProfileHtml === null) {
|
|
448
|
+
cachedProfileHtml = generateProfilePage(parsed.subdomain, record, env, profileOptions)
|
|
449
|
+
}
|
|
450
|
+
return cachedProfileHtml
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (wantsEmbed) {
|
|
454
|
+
const embedHtml = generateEmbedProfilePage(parsed.subdomain, record, env, hostname)
|
|
455
|
+
return htmlResponse(embedHtml, 200, {
|
|
456
|
+
'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=600',
|
|
457
|
+
})
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (wantsJson) return jsonResponse(record)
|
|
461
|
+
if (wantsProfile)
|
|
462
|
+
return htmlResponse(renderProfile(), 200, {
|
|
463
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
464
|
+
Pragma: 'no-cache',
|
|
465
|
+
})
|
|
466
|
+
if (shouldServeProfileForTwitter)
|
|
467
|
+
return htmlResponse(renderProfile(), 200, {
|
|
468
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
469
|
+
Pragma: 'no-cache',
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
if (record.content) {
|
|
473
|
+
const contentResponse = await resolveContent(record.content, env)
|
|
474
|
+
if (!contentResponse.ok && (url.pathname === '/' || url.pathname === '')) {
|
|
475
|
+
const profileResponse = htmlResponse(renderProfile(), 200, {
|
|
476
|
+
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
|
|
477
|
+
})
|
|
478
|
+
if (canServeCached) {
|
|
479
|
+
const cache = caches.default
|
|
480
|
+
const cacheUrl = new URL(
|
|
481
|
+
`https://cache.internal/profile:${hostname}${url.pathname || '/'}`,
|
|
482
|
+
)
|
|
483
|
+
c.executionCtx.waitUntil(cache.put(cacheUrl.toString(), profileResponse.clone()))
|
|
484
|
+
}
|
|
485
|
+
return profileResponse
|
|
486
|
+
}
|
|
487
|
+
return contentResponse
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const profileResponse = htmlResponse(renderProfile(), 200, {
|
|
491
|
+
'Cache-Control': canServeCached
|
|
492
|
+
? 'public, s-maxage=60, stale-while-revalidate=300'
|
|
493
|
+
: 'no-store, no-cache, must-revalidate',
|
|
494
|
+
})
|
|
495
|
+
if (canServeCached) {
|
|
496
|
+
const cache = caches.default
|
|
497
|
+
const cacheUrl = new URL(`https://cache.internal/profile:${hostname}${url.pathname || '/'}`)
|
|
498
|
+
c.executionCtx.waitUntil(cache.put(cacheUrl.toString(), profileResponse.clone()))
|
|
499
|
+
}
|
|
500
|
+
return profileResponse
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return errorResponse('Unknown route type', 'UNKNOWN_ROUTE', 400)
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
app.onError((err, _c) => {
|
|
507
|
+
console.error('Gateway error:', err)
|
|
508
|
+
const message = err instanceof Error ? err.message : 'Unknown error'
|
|
509
|
+
return errorResponse(`Gateway error: ${message}`, 'GATEWAY_ERROR', 500)
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
export default app
|