@forgedevv/shared 0.0.0-stage → 0.2.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/dist/api.d.ts +464 -0
- package/dist/api.js +307 -0
- package/dist/constants.d.ts +53 -0
- package/dist/constants.js +42 -0
- package/dist/db.d.ts +189 -0
- package/dist/db.js +75 -0
- package/dist/env.d.ts +21 -0
- package/dist/env.js +92 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/jobs.d.ts +44 -0
- package/dist/jobs.js +76 -0
- package/dist/site-config.d.ts +66 -0
- package/dist/site-config.js +48 -0
- package/dist/spec.d.ts +66 -0
- package/dist/spec.js +121 -0
- package/dist/web-client.d.ts +210 -0
- package/dist/web-client.js +52 -0
- package/package.json +38 -3
- package/README.md +0 -3
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const R2_ENV: readonly ["R2_ACCESS_KEY_ID", "R2_SECRET_ACCESS_KEY", "R2_ACCOUNT_ID", "R2_BUCKET"];
|
|
2
|
+
/** Variables that may be absent. FORGE_MINT is absent before the $FORGE launch. */
|
|
3
|
+
export declare const OPTIONAL_ENV: readonly ["FORGE_MINT"];
|
|
4
|
+
/**
|
|
5
|
+
* apps/web. RPC_URL and SUPABASE_SERVICE_ROLE_KEY are server-only.
|
|
6
|
+
* FORGE_GATING_AMOUNT is required only when FORGE_MINT is set.
|
|
7
|
+
*/
|
|
8
|
+
export declare const WEB_ENV: readonly ["SOLANA_CLUSTER", "RPC_URL", "SUPABASE_URL", "SUPABASE_ANON_KEY", "SUPABASE_SERVICE_ROLE_KEY", "ANTHROPIC_API_KEY", "FORGE_MINT", "FORGE_GATING_AMOUNT", "FORGE_CASHBOX_WALLET", "JUPITER_API_KEY", "SESSION_SECRET", "SUPABASE_JWT_SECRET", "NEXT_PUBLIC_APP_URL", "NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_ANON_KEY", "NEXT_PUBLIC_SOLANA_CLUSTER", "R2_ACCESS_KEY_ID", "R2_SECRET_ACCESS_KEY", "R2_ACCOUNT_ID", "R2_BUCKET"];
|
|
9
|
+
/** apps/launchpad-template. RPC_URL is server-only; FORGE_* addresses are read through @forge/core. */
|
|
10
|
+
export declare const TEMPLATE_ENV: readonly ["SOLANA_CLUSTER", "RPC_URL", "FORGE_MULTISIG_VAULT", "FORGE_METEORA_REFERRAL_ACCOUNT", "FORGE_PLATFORM_FEE_WALLET", "FORGE_JUPITER_REFERRAL_ACCOUNT", "JUPITER_API_KEY", "R2_ACCESS_KEY_ID", "R2_SECRET_ACCESS_KEY", "R2_ACCOUNT_ID", "R2_BUCKET"];
|
|
11
|
+
/** Variables the builder does not use itself but copies into each client Vercel project. */
|
|
12
|
+
export declare const BUILDER_VERCEL_INJECTED_ENV: readonly ["FORGE_MULTISIG_VAULT", "FORGE_METEORA_REFERRAL_ACCOUNT", "FORGE_PLATFORM_FEE_WALLET", "FORGE_JUPITER_REFERRAL_ACCOUNT", "JUPITER_API_KEY", "R2_ACCESS_KEY_ID", "R2_SECRET_ACCESS_KEY", "R2_ACCOUNT_ID", "R2_BUCKET"];
|
|
13
|
+
/** apps/builder. ANTHROPIC_API_KEY is used by the gateway only. */
|
|
14
|
+
export declare const BUILDER_ENV: readonly ["SOLANA_CLUSTER", "SUPABASE_URL", "SUPABASE_SERVICE_ROLE_KEY", "ANTHROPIC_API_KEY", "FORGE_MULTISIG_VAULT", "FORGE_METEORA_REFERRAL_ACCOUNT", "FORGE_PLATFORM_FEE_WALLET", "FORGE_JUPITER_REFERRAL_ACCOUNT", "JUPITER_API_KEY", "R2_ACCESS_KEY_ID", "R2_SECRET_ACCESS_KEY", "R2_ACCOUNT_ID", "R2_BUCKET", "GITHUB_APP_ID", "GITHUB_APP_PRIVATE_KEY", "GITHUB_ORG", "VERCEL_TOKEN", "VERCEL_TEAM_ID", "SIGNER_URL", "SIGNER_HMAC_SECRET", "TELEGRAM_BOT_TOKEN", "TELEGRAM_CHAT_ID"];
|
|
15
|
+
/** apps/signer. FORGE_MINT is used for the buyback (simulation mode when absent). */
|
|
16
|
+
export declare const SIGNER_ENV: readonly ["SOLANA_CLUSTER", "RPC_URL", "SUPABASE_URL", "SUPABASE_SERVICE_ROLE_KEY", "FORGE_MINT", "FORGE_CASHBOX_WALLET", "FORGE_MULTISIG_VAULT", "FORGE_JUPITER_REFERRAL_ACCOUNT", "JUPITER_API_KEY", "SIGNER_URL", "SIGNER_HMAC_SECRET", "SIGNER_KEYSTORE_PASSPHRASE", "SIGNER_KEYSTORE_DIR", "SIGNER_ALLOWED_IPS", "SIGNER_PORT", "TELEGRAM_BOT_TOKEN", "TELEGRAM_CHAT_ID"];
|
|
17
|
+
export type WebEnvName = (typeof WEB_ENV)[number];
|
|
18
|
+
export type TemplateEnvName = (typeof TEMPLATE_ENV)[number];
|
|
19
|
+
export type BuilderEnvName = (typeof BUILDER_ENV)[number];
|
|
20
|
+
export type SignerEnvName = (typeof SIGNER_ENV)[number];
|
|
21
|
+
export type EnvName = WebEnvName | TemplateEnvName | BuilderEnvName | SignerEnvName;
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Environment variable names per app (docs/INTERFACES.md §8). Names only, never values.
|
|
3
|
+
* `R2_*` is expanded to the variables of the fun-launch scaffold (docs/METEORA.md).
|
|
4
|
+
*/
|
|
5
|
+
export const R2_ENV = [
|
|
6
|
+
'R2_ACCESS_KEY_ID',
|
|
7
|
+
'R2_SECRET_ACCESS_KEY',
|
|
8
|
+
'R2_ACCOUNT_ID',
|
|
9
|
+
'R2_BUCKET',
|
|
10
|
+
];
|
|
11
|
+
/** Variables that may be absent. FORGE_MINT is absent before the $FORGE launch. */
|
|
12
|
+
export const OPTIONAL_ENV = ['FORGE_MINT'];
|
|
13
|
+
/**
|
|
14
|
+
* apps/web. RPC_URL and SUPABASE_SERVICE_ROLE_KEY are server-only.
|
|
15
|
+
* FORGE_GATING_AMOUNT is required only when FORGE_MINT is set.
|
|
16
|
+
*/
|
|
17
|
+
export const WEB_ENV = [
|
|
18
|
+
'SOLANA_CLUSTER',
|
|
19
|
+
'RPC_URL',
|
|
20
|
+
'SUPABASE_URL',
|
|
21
|
+
'SUPABASE_ANON_KEY',
|
|
22
|
+
'SUPABASE_SERVICE_ROLE_KEY',
|
|
23
|
+
'ANTHROPIC_API_KEY',
|
|
24
|
+
'FORGE_MINT',
|
|
25
|
+
'FORGE_GATING_AMOUNT',
|
|
26
|
+
'FORGE_CASHBOX_WALLET',
|
|
27
|
+
'JUPITER_API_KEY',
|
|
28
|
+
'SESSION_SECRET',
|
|
29
|
+
'SUPABASE_JWT_SECRET',
|
|
30
|
+
'NEXT_PUBLIC_APP_URL',
|
|
31
|
+
'NEXT_PUBLIC_SUPABASE_URL',
|
|
32
|
+
'NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
|
33
|
+
'NEXT_PUBLIC_SOLANA_CLUSTER',
|
|
34
|
+
...R2_ENV,
|
|
35
|
+
];
|
|
36
|
+
/** apps/launchpad-template. RPC_URL is server-only; FORGE_* addresses are read through @forge/core. */
|
|
37
|
+
export const TEMPLATE_ENV = [
|
|
38
|
+
'SOLANA_CLUSTER',
|
|
39
|
+
'RPC_URL',
|
|
40
|
+
'FORGE_MULTISIG_VAULT',
|
|
41
|
+
'FORGE_METEORA_REFERRAL_ACCOUNT',
|
|
42
|
+
'FORGE_PLATFORM_FEE_WALLET',
|
|
43
|
+
'FORGE_JUPITER_REFERRAL_ACCOUNT',
|
|
44
|
+
'JUPITER_API_KEY',
|
|
45
|
+
...R2_ENV,
|
|
46
|
+
];
|
|
47
|
+
/** Variables the builder does not use itself but copies into each client Vercel project. */
|
|
48
|
+
export const BUILDER_VERCEL_INJECTED_ENV = [
|
|
49
|
+
'FORGE_MULTISIG_VAULT',
|
|
50
|
+
'FORGE_METEORA_REFERRAL_ACCOUNT',
|
|
51
|
+
'FORGE_PLATFORM_FEE_WALLET',
|
|
52
|
+
'FORGE_JUPITER_REFERRAL_ACCOUNT',
|
|
53
|
+
'JUPITER_API_KEY',
|
|
54
|
+
...R2_ENV,
|
|
55
|
+
];
|
|
56
|
+
/** apps/builder. ANTHROPIC_API_KEY is used by the gateway only. */
|
|
57
|
+
export const BUILDER_ENV = [
|
|
58
|
+
'SOLANA_CLUSTER',
|
|
59
|
+
'SUPABASE_URL',
|
|
60
|
+
'SUPABASE_SERVICE_ROLE_KEY',
|
|
61
|
+
'ANTHROPIC_API_KEY',
|
|
62
|
+
...BUILDER_VERCEL_INJECTED_ENV,
|
|
63
|
+
'GITHUB_APP_ID',
|
|
64
|
+
'GITHUB_APP_PRIVATE_KEY',
|
|
65
|
+
'GITHUB_ORG',
|
|
66
|
+
'VERCEL_TOKEN',
|
|
67
|
+
'VERCEL_TEAM_ID',
|
|
68
|
+
'SIGNER_URL',
|
|
69
|
+
'SIGNER_HMAC_SECRET',
|
|
70
|
+
'TELEGRAM_BOT_TOKEN',
|
|
71
|
+
'TELEGRAM_CHAT_ID',
|
|
72
|
+
];
|
|
73
|
+
/** apps/signer. FORGE_MINT is used for the buyback (simulation mode when absent). */
|
|
74
|
+
export const SIGNER_ENV = [
|
|
75
|
+
'SOLANA_CLUSTER',
|
|
76
|
+
'RPC_URL',
|
|
77
|
+
'SUPABASE_URL',
|
|
78
|
+
'SUPABASE_SERVICE_ROLE_KEY',
|
|
79
|
+
'FORGE_MINT',
|
|
80
|
+
'FORGE_CASHBOX_WALLET',
|
|
81
|
+
'FORGE_MULTISIG_VAULT',
|
|
82
|
+
'FORGE_JUPITER_REFERRAL_ACCOUNT',
|
|
83
|
+
'JUPITER_API_KEY',
|
|
84
|
+
'SIGNER_URL',
|
|
85
|
+
'SIGNER_HMAC_SECRET',
|
|
86
|
+
'SIGNER_KEYSTORE_PASSPHRASE',
|
|
87
|
+
'SIGNER_KEYSTORE_DIR',
|
|
88
|
+
'SIGNER_ALLOWED_IPS',
|
|
89
|
+
'SIGNER_PORT',
|
|
90
|
+
'TELEGRAM_BOT_TOKEN',
|
|
91
|
+
'TELEGRAM_CHAT_ID',
|
|
92
|
+
];
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/jobs.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Job statuses (docs/INTERFACES.md §4). */
|
|
3
|
+
export declare const JobStatus: z.ZodEnum<{
|
|
4
|
+
spec_ready: "spec_ready";
|
|
5
|
+
paid: "paid";
|
|
6
|
+
building: "building";
|
|
7
|
+
preview_ready: "preview_ready";
|
|
8
|
+
approved: "approved";
|
|
9
|
+
onchain_setup: "onchain_setup";
|
|
10
|
+
awaiting_owner_signature: "awaiting_owner_signature";
|
|
11
|
+
owner_signed: "owner_signed";
|
|
12
|
+
deploying: "deploying";
|
|
13
|
+
live: "live";
|
|
14
|
+
failed: "failed";
|
|
15
|
+
refunded: "refunded";
|
|
16
|
+
}>;
|
|
17
|
+
export type JobStatus = z.infer<typeof JobStatus>;
|
|
18
|
+
export declare const JobType: z.ZodEnum<{
|
|
19
|
+
create_launchpad: "create_launchpad";
|
|
20
|
+
modify_launchpad: "modify_launchpad";
|
|
21
|
+
}>;
|
|
22
|
+
export type JobType = z.infer<typeof JobType>;
|
|
23
|
+
export declare const FailedStage: z.ZodEnum<{
|
|
24
|
+
build: "build";
|
|
25
|
+
onchain: "onchain";
|
|
26
|
+
deploy: "deploy";
|
|
27
|
+
}>;
|
|
28
|
+
export type FailedStage = z.infer<typeof FailedStage>;
|
|
29
|
+
/** Statuses during which the builder holds the job lock (locked_by / locked_at). */
|
|
30
|
+
export declare const ACTIVE_JOB_STATUSES: readonly ["building", "onchain_setup", "deploying"];
|
|
31
|
+
export type ActiveJobStatus = (typeof ACTIVE_JOB_STATUSES)[number];
|
|
32
|
+
/** Happy path of a `create_launchpad` job, in order. */
|
|
33
|
+
export declare const CREATE_FLOW: readonly ["spec_ready", "paid", "building", "preview_ready", "approved", "onchain_setup", "awaiting_owner_signature", "owner_signed", "deploying", "live"];
|
|
34
|
+
/** Happy path of a `modify_launchpad` job, in order. */
|
|
35
|
+
export declare const MODIFY_FLOW: readonly ["spec_ready", "paid", "building", "preview_ready", "approved", "deploying", "live"];
|
|
36
|
+
export declare function isActiveJobStatus(status: JobStatus): status is ActiveJobStatus;
|
|
37
|
+
/** Ordered happy path for a job type. */
|
|
38
|
+
export declare function flowForJobType(type: JobType): readonly JobStatus[];
|
|
39
|
+
/**
|
|
40
|
+
* Stage recorded in failed_stage when a job fails (or is found stale) in the given status:
|
|
41
|
+
* building -> build, onchain_setup -> onchain, deploying -> deploy. Null for non-active statuses.
|
|
42
|
+
*/
|
|
43
|
+
export declare function failedStageForStatus(status: ActiveJobStatus): FailedStage;
|
|
44
|
+
export declare function failedStageForStatus(status: JobStatus): FailedStage | null;
|
package/dist/jobs.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Job statuses (docs/INTERFACES.md §4). */
|
|
3
|
+
export const JobStatus = z.enum([
|
|
4
|
+
/** Created by apps/web: spec confirmed (creation) or modification request recorded. */
|
|
5
|
+
'spec_ready',
|
|
6
|
+
/** Payment confirmed on-chain. */
|
|
7
|
+
'paid',
|
|
8
|
+
/** Builder: container running. */
|
|
9
|
+
'building',
|
|
10
|
+
/** Preview URL available. */
|
|
11
|
+
'preview_ready',
|
|
12
|
+
/** The client approved the preview. */
|
|
13
|
+
'approved',
|
|
14
|
+
/** Builder + signer: configs creation and launch transaction preparation. */
|
|
15
|
+
'onchain_setup',
|
|
16
|
+
/** Launch transaction ready in jobs.owner_tx; the client must sign the first buy. */
|
|
17
|
+
'awaiting_owner_signature',
|
|
18
|
+
/** apps/web: client transaction confirmed on-chain. */
|
|
19
|
+
'owner_signed',
|
|
20
|
+
/** Builder: merge to main, Vercel production deploy. */
|
|
21
|
+
'deploying',
|
|
22
|
+
'live',
|
|
23
|
+
/** A stage failed (see failed_stage and attempts). */
|
|
24
|
+
'failed',
|
|
25
|
+
'refunded',
|
|
26
|
+
]);
|
|
27
|
+
export const JobType = z.enum(['create_launchpad', 'modify_launchpad']);
|
|
28
|
+
export const FailedStage = z.enum(['build', 'onchain', 'deploy']);
|
|
29
|
+
/** Statuses during which the builder holds the job lock (locked_by / locked_at). */
|
|
30
|
+
export const ACTIVE_JOB_STATUSES = [
|
|
31
|
+
'building',
|
|
32
|
+
'onchain_setup',
|
|
33
|
+
'deploying',
|
|
34
|
+
];
|
|
35
|
+
/** Happy path of a `create_launchpad` job, in order. */
|
|
36
|
+
export const CREATE_FLOW = [
|
|
37
|
+
'spec_ready',
|
|
38
|
+
'paid',
|
|
39
|
+
'building',
|
|
40
|
+
'preview_ready',
|
|
41
|
+
'approved',
|
|
42
|
+
'onchain_setup',
|
|
43
|
+
'awaiting_owner_signature',
|
|
44
|
+
'owner_signed',
|
|
45
|
+
'deploying',
|
|
46
|
+
'live',
|
|
47
|
+
];
|
|
48
|
+
/** Happy path of a `modify_launchpad` job, in order. */
|
|
49
|
+
export const MODIFY_FLOW = [
|
|
50
|
+
'spec_ready',
|
|
51
|
+
'paid',
|
|
52
|
+
'building',
|
|
53
|
+
'preview_ready',
|
|
54
|
+
'approved',
|
|
55
|
+
'deploying',
|
|
56
|
+
'live',
|
|
57
|
+
];
|
|
58
|
+
export function isActiveJobStatus(status) {
|
|
59
|
+
return ACTIVE_JOB_STATUSES.includes(status);
|
|
60
|
+
}
|
|
61
|
+
/** Ordered happy path for a job type. */
|
|
62
|
+
export function flowForJobType(type) {
|
|
63
|
+
return type === 'create_launchpad' ? CREATE_FLOW : MODIFY_FLOW;
|
|
64
|
+
}
|
|
65
|
+
export function failedStageForStatus(status) {
|
|
66
|
+
switch (status) {
|
|
67
|
+
case 'building':
|
|
68
|
+
return 'build';
|
|
69
|
+
case 'onchain_setup':
|
|
70
|
+
return 'onchain';
|
|
71
|
+
case 'deploying':
|
|
72
|
+
return 'deploy';
|
|
73
|
+
default:
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const SolanaCluster: z.ZodEnum<{
|
|
3
|
+
devnet: "devnet";
|
|
4
|
+
"mainnet-beta": "mainnet-beta";
|
|
5
|
+
}>;
|
|
6
|
+
export type SolanaCluster = z.infer<typeof SolanaCluster>;
|
|
7
|
+
/** `live`, `sleeping` (dormant) or `disabled` (cut by FORGE). Written by the builder only. */
|
|
8
|
+
export declare const SiteMode: z.ZodEnum<{
|
|
9
|
+
live: "live";
|
|
10
|
+
sleeping: "sleeping";
|
|
11
|
+
disabled: "disabled";
|
|
12
|
+
}>;
|
|
13
|
+
export type SiteMode = z.infer<typeof SiteMode>;
|
|
14
|
+
/**
|
|
15
|
+
* `forge.config.json`, generated by the builder in each client repo and read by the client site
|
|
16
|
+
* (docs/INTERFACES.md §3). The builder agent may change `theme` and `content`, never `onchain`
|
|
17
|
+
* nor `mode`. FORGE addresses (multisig, referrals, platform fee wallet) are NOT in this file.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ForgeSiteConfig: z.ZodObject<{
|
|
20
|
+
version: z.ZodLiteral<1>;
|
|
21
|
+
name: z.ZodString;
|
|
22
|
+
slug: z.ZodString;
|
|
23
|
+
mode: z.ZodEnum<{
|
|
24
|
+
live: "live";
|
|
25
|
+
sleeping: "sleeping";
|
|
26
|
+
disabled: "disabled";
|
|
27
|
+
}>;
|
|
28
|
+
onchain: z.ZodOptional<z.ZodObject<{
|
|
29
|
+
cluster: z.ZodEnum<{
|
|
30
|
+
devnet: "devnet";
|
|
31
|
+
"mainnet-beta": "mainnet-beta";
|
|
32
|
+
}>;
|
|
33
|
+
launchpadConfig: z.ZodString;
|
|
34
|
+
launchpadCoinConfig: z.ZodString;
|
|
35
|
+
launchpadCoinMint: z.ZodString;
|
|
36
|
+
ownerWallet: z.ZodString;
|
|
37
|
+
poolCreationFeeSol: z.ZodOptional<z.ZodNumber>;
|
|
38
|
+
}, z.core.$strip>>;
|
|
39
|
+
theme: z.ZodObject<{
|
|
40
|
+
primaryColor: z.ZodString;
|
|
41
|
+
accentColor: z.ZodString;
|
|
42
|
+
darkMode: z.ZodBoolean;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
content: z.ZodObject<{
|
|
45
|
+
tagline: z.ZodString;
|
|
46
|
+
about: z.ZodString;
|
|
47
|
+
}, z.core.$strip>;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
export type ForgeSiteConfig = z.infer<typeof ForgeSiteConfig>;
|
|
50
|
+
/** Read-only view of forge.config.json exposed to the template UI (docs/frontend/CONTRACT.md). */
|
|
51
|
+
export interface SiteConfig {
|
|
52
|
+
name: string;
|
|
53
|
+
slug: string;
|
|
54
|
+
theme: {
|
|
55
|
+
primaryColor: string;
|
|
56
|
+
accentColor: string;
|
|
57
|
+
darkMode: boolean;
|
|
58
|
+
};
|
|
59
|
+
content: {
|
|
60
|
+
tagline: string;
|
|
61
|
+
about: string;
|
|
62
|
+
};
|
|
63
|
+
mode: SiteMode;
|
|
64
|
+
}
|
|
65
|
+
/** Pure projection of a parsed forge.config.json to the UI-facing SiteConfig. */
|
|
66
|
+
export declare function toSiteConfig(config: ForgeSiteConfig): SiteConfig;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Slug, SolanaAddress, isValidPoolCreationFeeSol } from './spec.js';
|
|
3
|
+
export const SolanaCluster = z.enum(['devnet', 'mainnet-beta']);
|
|
4
|
+
/** `live`, `sleeping` (dormant) or `disabled` (cut by FORGE). Written by the builder only. */
|
|
5
|
+
export const SiteMode = z.enum(['live', 'sleeping', 'disabled']);
|
|
6
|
+
const HexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/);
|
|
7
|
+
/**
|
|
8
|
+
* `forge.config.json`, generated by the builder in each client repo and read by the client site
|
|
9
|
+
* (docs/INTERFACES.md §3). The builder agent may change `theme` and `content`, never `onchain`
|
|
10
|
+
* nor `mode`. FORGE addresses (multisig, referrals, platform fee wallet) are NOT in this file.
|
|
11
|
+
*/
|
|
12
|
+
export const ForgeSiteConfig = z.object({
|
|
13
|
+
version: z.literal(1),
|
|
14
|
+
name: z.string().min(2).max(32),
|
|
15
|
+
slug: Slug,
|
|
16
|
+
mode: SiteMode,
|
|
17
|
+
/** Absent until the on-chain setup is done. */
|
|
18
|
+
onchain: z
|
|
19
|
+
.object({
|
|
20
|
+
cluster: SolanaCluster,
|
|
21
|
+
launchpadConfig: SolanaAddress,
|
|
22
|
+
launchpadCoinConfig: SolanaAddress,
|
|
23
|
+
launchpadCoinMint: SolanaAddress,
|
|
24
|
+
ownerWallet: SolanaAddress,
|
|
25
|
+
/** Coin creation fee charged by this launchpad, shown to coin creators before signing. */
|
|
26
|
+
poolCreationFeeSol: z.number().refine(isValidPoolCreationFeeSol).optional(),
|
|
27
|
+
})
|
|
28
|
+
.optional(),
|
|
29
|
+
theme: z.object({
|
|
30
|
+
primaryColor: HexColor,
|
|
31
|
+
accentColor: HexColor,
|
|
32
|
+
darkMode: z.boolean(),
|
|
33
|
+
}),
|
|
34
|
+
content: z.object({
|
|
35
|
+
tagline: z.string().max(120),
|
|
36
|
+
about: z.string(),
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
/** Pure projection of a parsed forge.config.json to the UI-facing SiteConfig. */
|
|
40
|
+
export function toSiteConfig(config) {
|
|
41
|
+
return {
|
|
42
|
+
name: config.name,
|
|
43
|
+
slug: config.slug,
|
|
44
|
+
theme: { ...config.theme },
|
|
45
|
+
content: { ...config.content },
|
|
46
|
+
mode: config.mode,
|
|
47
|
+
};
|
|
48
|
+
}
|
package/dist/spec.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Base58 Solana public key (32 to 44 characters). */
|
|
3
|
+
export declare const SolanaAddress: z.ZodString;
|
|
4
|
+
export type SolanaAddress = z.infer<typeof SolanaAddress>;
|
|
5
|
+
/** Launchpad slug: becomes `<slug>.<clients-domain>`. */
|
|
6
|
+
export declare const Slug: z.ZodString;
|
|
7
|
+
/** Pure check of the pool creation fee bound: 0 (when allowed) or within [min, max] SOL. */
|
|
8
|
+
export declare function isValidPoolCreationFeeSol(value: number): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* True when a SOL amount converts exactly to lamports: finite, with at most 9 decimals (the same
|
|
11
|
+
* rule as `solToLamports` in @forge/core, which refuses anything else at build time).
|
|
12
|
+
*/
|
|
13
|
+
export declare function hasAtMostSolDecimals(value: number): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Launchpad specification produced by the design chat, consumed by the builder and the signer
|
|
16
|
+
* (docs/INTERFACES.md §2). Bounds come from CLIENT_BOUNDS.
|
|
17
|
+
*/
|
|
18
|
+
export declare const LaunchpadSpec: z.ZodObject<{
|
|
19
|
+
version: z.ZodLiteral<1>;
|
|
20
|
+
ownerWallet: z.ZodString;
|
|
21
|
+
name: z.ZodString;
|
|
22
|
+
slug: z.ZodString;
|
|
23
|
+
quote: z.ZodLiteral<"SOL">;
|
|
24
|
+
tradingFeeBps: z.ZodNumber;
|
|
25
|
+
coinCreatorSharePct: z.ZodDefault<z.ZodNumber>;
|
|
26
|
+
poolCreationFeeSol: z.ZodNumber;
|
|
27
|
+
antiSniper: z.ZodDefault<z.ZodBoolean>;
|
|
28
|
+
theme: z.ZodObject<{
|
|
29
|
+
primaryColor: z.ZodString;
|
|
30
|
+
accentColor: z.ZodString;
|
|
31
|
+
darkMode: z.ZodBoolean;
|
|
32
|
+
tagline: z.ZodString;
|
|
33
|
+
logoUrl: z.ZodOptional<z.ZodURL>;
|
|
34
|
+
designNotes: z.ZodString;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
launchpadCoin: z.ZodObject<{
|
|
37
|
+
name: z.ZodString;
|
|
38
|
+
symbol: z.ZodString;
|
|
39
|
+
description: z.ZodString;
|
|
40
|
+
imageUrl: z.ZodURL;
|
|
41
|
+
firstBuySol: z.ZodNumber;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
/** Parsed spec (defaults applied). */
|
|
45
|
+
export type LaunchpadSpec = z.infer<typeof LaunchpadSpec>;
|
|
46
|
+
/** Spec before parsing (fields with defaults are optional). */
|
|
47
|
+
export type LaunchpadSpecInput = z.input<typeof LaunchpadSpec>;
|
|
48
|
+
type DeepPartial<T> = T extends readonly unknown[] ? T : T extends object ? {
|
|
49
|
+
[K in keyof T]?: DeepPartial<T[K]>;
|
|
50
|
+
} : T;
|
|
51
|
+
/** Partial spec while the chat fills it (docs/frontend/CONTRACT.md). Complete = LaunchpadSpec. */
|
|
52
|
+
export type LaunchpadSpecDraft = DeepPartial<Omit<LaunchpadSpecInput, 'version' | 'quote'>> & {
|
|
53
|
+
version?: 1;
|
|
54
|
+
quote?: 'SOL';
|
|
55
|
+
};
|
|
56
|
+
/** Draft validation result. `errors` keys are dotted field paths, e.g. "launchpadCoin.symbol". */
|
|
57
|
+
export interface SpecValidation {
|
|
58
|
+
complete: boolean;
|
|
59
|
+
errors: Record<string, string>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validates a draft against LaunchpadSpec. `version` and `quote` are fixed by the contract and
|
|
63
|
+
* filled in when missing. Keeps the first error message per field path.
|
|
64
|
+
*/
|
|
65
|
+
export declare function validateSpecDraft(draft: LaunchpadSpecDraft): SpecValidation;
|
|
66
|
+
export {};
|
package/dist/spec.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { CLIENT_BOUNDS } from './constants.js';
|
|
3
|
+
/** Base58 Solana public key (32 to 44 characters). */
|
|
4
|
+
export const SolanaAddress = z.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/, {
|
|
5
|
+
error: 'Invalid Solana address',
|
|
6
|
+
});
|
|
7
|
+
const HexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/, {
|
|
8
|
+
error: 'Color must be a 6-digit hex value like #7C3AED',
|
|
9
|
+
});
|
|
10
|
+
/** Only http(s) URLs are accepted for images and logos rendered on client sites. */
|
|
11
|
+
const HttpUrl = z.url({ protocol: /^https?$/, error: 'Must be an http(s) URL' });
|
|
12
|
+
/** Launchpad slug: becomes `<slug>.<clients-domain>`. */
|
|
13
|
+
export const Slug = z.string().regex(/^(?=.{3,32}$)[a-z0-9](?:[a-z0-9]|-(?!-))*[a-z0-9]$/, {
|
|
14
|
+
error: 'Slug must be 3 to 32 characters: lowercase letters, digits and single dashes, not at the start or end',
|
|
15
|
+
});
|
|
16
|
+
/** Metaplex limits on-chain names to 32 UTF-8 bytes, not 32 characters. */
|
|
17
|
+
function utf8Bytes(value) {
|
|
18
|
+
let bytes = 0;
|
|
19
|
+
for (const char of value) {
|
|
20
|
+
const code = char.codePointAt(0) ?? 0;
|
|
21
|
+
bytes += code < 0x80 ? 1 : code < 0x800 ? 2 : code < 0x10000 ? 3 : 4;
|
|
22
|
+
}
|
|
23
|
+
return bytes;
|
|
24
|
+
}
|
|
25
|
+
const { tradingFeeBps, coinCreatorSharePct, poolCreationFeeSol } = CLIENT_BOUNDS;
|
|
26
|
+
/** Pure check of the pool creation fee bound: 0 (when allowed) or within [min, max] SOL. */
|
|
27
|
+
export function isValidPoolCreationFeeSol(value) {
|
|
28
|
+
if (value === 0)
|
|
29
|
+
return poolCreationFeeSol.allowZero;
|
|
30
|
+
return value >= poolCreationFeeSol.min && value <= poolCreationFeeSol.max;
|
|
31
|
+
}
|
|
32
|
+
/** Decimals of SOL: 1 SOL = 10^9 lamports. */
|
|
33
|
+
const SOL_DECIMALS = 9;
|
|
34
|
+
/**
|
|
35
|
+
* True when a SOL amount converts exactly to lamports: finite, with at most 9 decimals (the same
|
|
36
|
+
* rule as `solToLamports` in @forge/core, which refuses anything else at build time).
|
|
37
|
+
*/
|
|
38
|
+
export function hasAtMostSolDecimals(value) {
|
|
39
|
+
if (!Number.isFinite(value))
|
|
40
|
+
return false;
|
|
41
|
+
return Number(value.toFixed(SOL_DECIMALS)) === value;
|
|
42
|
+
}
|
|
43
|
+
const SOL_DECIMALS_ERROR = `must have at most ${SOL_DECIMALS} decimals (1 lamport)`;
|
|
44
|
+
/**
|
|
45
|
+
* Launchpad specification produced by the design chat, consumed by the builder and the signer
|
|
46
|
+
* (docs/INTERFACES.md §2). Bounds come from CLIENT_BOUNDS.
|
|
47
|
+
*/
|
|
48
|
+
export const LaunchpadSpec = z.object({
|
|
49
|
+
version: z.literal(1),
|
|
50
|
+
/** Client wallet = session wallet = payer = feeClaimer of both configs. */
|
|
51
|
+
ownerWallet: SolanaAddress,
|
|
52
|
+
name: z.string().min(2).max(32),
|
|
53
|
+
slug: Slug,
|
|
54
|
+
quote: z.literal('SOL'),
|
|
55
|
+
tradingFeeBps: z.number().int().min(tradingFeeBps.min).max(tradingFeeBps.max),
|
|
56
|
+
coinCreatorSharePct: z
|
|
57
|
+
.number()
|
|
58
|
+
.int()
|
|
59
|
+
.min(coinCreatorSharePct.min)
|
|
60
|
+
.max(coinCreatorSharePct.max)
|
|
61
|
+
.default(coinCreatorSharePct.default),
|
|
62
|
+
poolCreationFeeSol: z
|
|
63
|
+
.number()
|
|
64
|
+
.refine(isValidPoolCreationFeeSol, {
|
|
65
|
+
error: `poolCreationFeeSol must be 0 or between ${poolCreationFeeSol.min} and ${poolCreationFeeSol.max} SOL`,
|
|
66
|
+
})
|
|
67
|
+
.refine(hasAtMostSolDecimals, { error: `poolCreationFeeSol ${SOL_DECIMALS_ERROR}` }),
|
|
68
|
+
antiSniper: z.boolean().default(true),
|
|
69
|
+
theme: z.object({
|
|
70
|
+
primaryColor: HexColor,
|
|
71
|
+
accentColor: HexColor,
|
|
72
|
+
darkMode: z.boolean(),
|
|
73
|
+
tagline: z.string().max(120),
|
|
74
|
+
logoUrl: HttpUrl.optional(),
|
|
75
|
+
/** Free-form instructions for the builder agent. */
|
|
76
|
+
designNotes: z.string().max(2000),
|
|
77
|
+
}),
|
|
78
|
+
launchpadCoin: z.object({
|
|
79
|
+
/**
|
|
80
|
+
* Written on-chain as is: no surrounding spaces (the on-chain launch refuses them), at most
|
|
81
|
+
* 32 UTF-8 bytes. Name, symbol and the metadata URI also share one byte budget in the launch
|
|
82
|
+
* transaction (`LAUNCH_METADATA_BUDGET_BYTES` in @forge/core).
|
|
83
|
+
*/
|
|
84
|
+
name: z
|
|
85
|
+
.string()
|
|
86
|
+
.min(1)
|
|
87
|
+
.refine((value) => value.trim() === value, {
|
|
88
|
+
error: 'Coin name must not start or end with spaces',
|
|
89
|
+
})
|
|
90
|
+
.refine((value) => utf8Bytes(value) <= 32, { error: 'Coin name must be at most 32 bytes' }),
|
|
91
|
+
/** Written on-chain as is: the pattern leaves no room for spaces. */
|
|
92
|
+
symbol: z.string().regex(/^[A-Z0-9]{2,10}$/, {
|
|
93
|
+
error: 'Symbol must be 2 to 10 uppercase letters or digits',
|
|
94
|
+
}),
|
|
95
|
+
description: z.string().max(500),
|
|
96
|
+
imageUrl: HttpUrl,
|
|
97
|
+
/** Paid by the client. */
|
|
98
|
+
// The 10 SOL migration threshold means a first buy above ~10 SOL cannot fit in the curve.
|
|
99
|
+
firstBuySol: z
|
|
100
|
+
.number()
|
|
101
|
+
.min(0)
|
|
102
|
+
.max(10)
|
|
103
|
+
.refine(hasAtMostSolDecimals, { error: `firstBuySol ${SOL_DECIMALS_ERROR}` }),
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
107
|
+
* Validates a draft against LaunchpadSpec. `version` and `quote` are fixed by the contract and
|
|
108
|
+
* filled in when missing. Keeps the first error message per field path.
|
|
109
|
+
*/
|
|
110
|
+
export function validateSpecDraft(draft) {
|
|
111
|
+
const result = LaunchpadSpec.safeParse({ ...draft, version: 1, quote: 'SOL' });
|
|
112
|
+
if (result.success)
|
|
113
|
+
return { complete: true, errors: {} };
|
|
114
|
+
const errors = {};
|
|
115
|
+
for (const issue of result.error.issues) {
|
|
116
|
+
const key = issue.path.map(String).join('.');
|
|
117
|
+
if (!(key in errors))
|
|
118
|
+
errors[key] = issue.message;
|
|
119
|
+
}
|
|
120
|
+
return { complete: false, errors };
|
|
121
|
+
}
|