frontier-os-app-builder 1.0.0 → 1.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/LICENSE +21 -0
- package/README.md +90 -14
- package/agents/fos-executor.md +105 -39
- package/agents/fos-plan-checker.md +62 -25
- package/agents/fos-planner.md +80 -72
- package/agents/fos-researcher.md +26 -15
- package/agents/fos-verifier.md +96 -27
- package/bin/fos-tools.cjs +146 -42
- package/bin/install.js +8 -5
- package/commands/fos/add-feature.md +1 -2
- package/commands/fos/discuss.md +0 -1
- package/commands/fos/new-app.md +2 -4
- package/commands/fos/new-milestone.md +1 -1
- package/commands/fos/plan.md +0 -2
- package/package.json +7 -1
- package/references/app-patterns.md +128 -21
- package/references/deployment.md +40 -124
- package/references/module-index.md +32 -0
- package/references/sdk/chain.md +92 -0
- package/references/sdk/communities.md +159 -0
- package/references/sdk/events.md +212 -0
- package/references/sdk/init.md +126 -0
- package/references/sdk/navigation.md +49 -0
- package/references/sdk/offices.md +76 -0
- package/references/sdk/partnerships.md +111 -0
- package/references/sdk/storage.md +44 -0
- package/references/sdk/thirdparty.md +240 -0
- package/references/sdk/token-amount.md +99 -0
- package/references/sdk/types.md +27 -0
- package/references/sdk/ui-utils.md +39 -0
- package/references/sdk/user.md +208 -0
- package/references/sdk/wallet.md +334 -0
- package/references/verification-rules.md +111 -50
- package/templates/app/frontier-services.tsx +871 -0
- package/templates/app/layout-standalone.tsx +8 -0
- package/templates/app/layout.tsx +19 -9
- package/templates/app/package-standalone.json +35 -0
- package/templates/app/package.json +2 -1
- package/templates/app/public/favicon.svg +3 -0
- package/templates/app/sdk-context.tsx +7 -9
- package/templates/app/sdk-services.tsx +98 -0
- package/templates/app/vercel-standalone.json +5 -0
- package/templates/app/vercel.json +8 -95
- package/templates/state/plan.md +32 -14
- package/templates/state/requirements.md +1 -1
- package/templates/state/roadmap.md +57 -24
- package/templates/state/summary.md +27 -30
- package/workflows/add-feature.md +6 -1
- package/workflows/discuss.md +126 -11
- package/workflows/execute-plan.md +21 -14
- package/workflows/execute.md +204 -24
- package/workflows/new-app.md +64 -23
- package/workflows/new-milestone.md +10 -3
- package/workflows/plan.md +16 -5
- package/workflows/ship.md +91 -34
- package/workflows/status.md +1 -2
- package/references/module-inference.md +0 -349
- package/references/sdk-surface.md +0 -1622
- package/templates/app/main-simple.tsx +0 -19
- package/templates/state/manifest.json +0 -11
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Communities Module
|
|
2
|
+
|
|
3
|
+
**Trigger keywords:** community, group, team, club, internship, intern, cohort, reassign, transfer member, collective, society
|
|
4
|
+
|
|
5
|
+
Access via `sdk.getCommunities()`. Manage communities, internship passes, and member reassignment requests.
|
|
6
|
+
|
|
7
|
+
Community listing is public. Internship passes require authentication, an active subscription, and community manager status. Reassign requests require authentication; creating requires managing the member's current community, accepting requires managing the target community. Superusers can access everything.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Methods
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
listCommunities(payload?: ListCommunitiesParams): Promise<PaginatedResponse<Community>>
|
|
15
|
+
```
|
|
16
|
+
List all visible communities, paginated. Permission: `communities:listCommunities`
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
getCommunity(payload: { idOrSlug: string | number }): Promise<Community>
|
|
20
|
+
```
|
|
21
|
+
Get a community by numeric ID or slug string. Permission: `communities:getCommunity`
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
createInternshipPass(payload: CreateInternshipPassRequest): Promise<InternshipPass>
|
|
25
|
+
```
|
|
26
|
+
Create an internship pass. Auto-creates an inactive account if the user does not exist. Permission: `communities:createInternshipPass`
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
listInternshipPasses(payload?: ListInternshipPassesParams): Promise<PaginatedResponse<InternshipPass>>
|
|
30
|
+
```
|
|
31
|
+
List internship passes for managed communities. Active only by default; set `includeRevoked: true` to include revoked. Permission: `communities:listInternshipPasses`
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
getInternshipPass(payload: { id: number }): Promise<InternshipPass>
|
|
35
|
+
```
|
|
36
|
+
Get an internship pass by ID. Permission: `communities:getInternshipPass`
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
revokeInternshipPass(payload: { id: number }): Promise<void>
|
|
40
|
+
```
|
|
41
|
+
Revoke an internship pass. Cannot revoke an already-revoked pass. Permission: `communities:revokeInternshipPass`
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
createReassignRequest(payload: CreateReassignRequestPayload): Promise<ReassignRequest>
|
|
45
|
+
```
|
|
46
|
+
Request to move a member to a different community. Caller must manage the member's current community. Permission: `communities:createReassignRequest`
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
listReassignRequests(payload?: ListReassignRequestsParams): Promise<PaginatedResponse<ReassignRequest>>
|
|
50
|
+
```
|
|
51
|
+
List pending reassign requests visible to the caller. Permission: `communities:listReassignRequests`
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
getReassignRequest(payload: { id: number }): Promise<ReassignRequest>
|
|
55
|
+
```
|
|
56
|
+
Get a reassign request by ID. Permission: `communities:getReassignRequest`
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
acceptReassignRequest(payload: { id: number }): Promise<ReassignRequest>
|
|
60
|
+
```
|
|
61
|
+
Accept a reassign request. Moves the member to the target community. Only target community managers (or superusers) can accept. Permission: `communities:acceptReassignRequest`
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
rejectReassignRequest(payload: { id: number }): Promise<void>
|
|
65
|
+
```
|
|
66
|
+
Reject a reassign request. Only pending requests can be rejected. Permission: `communities:rejectReassignRequest`
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Types
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
interface Community {
|
|
74
|
+
id: number;
|
|
75
|
+
name: string;
|
|
76
|
+
description: string;
|
|
77
|
+
slug: string;
|
|
78
|
+
iconName: string;
|
|
79
|
+
splashVideo: string | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface ListCommunitiesParams {
|
|
83
|
+
limit?: number;
|
|
84
|
+
offset?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type InternshipPassStatus = 'active' | 'revoked';
|
|
88
|
+
|
|
89
|
+
interface InternshipPass {
|
|
90
|
+
id: number;
|
|
91
|
+
email: string;
|
|
92
|
+
firstName: string;
|
|
93
|
+
lastName: string;
|
|
94
|
+
community: number;
|
|
95
|
+
communityName: string;
|
|
96
|
+
status: InternshipPassStatus;
|
|
97
|
+
createdAt: string;
|
|
98
|
+
revokedAt: string | null;
|
|
99
|
+
updatedAt: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface CreateInternshipPassRequest {
|
|
103
|
+
email: string;
|
|
104
|
+
firstName: string;
|
|
105
|
+
lastName: string;
|
|
106
|
+
community: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface ListInternshipPassesParams {
|
|
110
|
+
limit?: number;
|
|
111
|
+
offset?: number;
|
|
112
|
+
includeRevoked?: boolean;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
type ReassignRequestStatus = 'pending' | 'accepted' | 'rejected';
|
|
116
|
+
|
|
117
|
+
interface ReassignRequest {
|
|
118
|
+
id: number;
|
|
119
|
+
requester: number;
|
|
120
|
+
requesterEmail: string;
|
|
121
|
+
member: number;
|
|
122
|
+
memberEmail: string;
|
|
123
|
+
targetCommunity: number;
|
|
124
|
+
targetCommunityName: string;
|
|
125
|
+
status: ReassignRequestStatus;
|
|
126
|
+
createdAt: string;
|
|
127
|
+
resolvedAt: string | null;
|
|
128
|
+
resolvedBy: number | null;
|
|
129
|
+
resolvedByEmail: string | null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface CreateReassignRequestPayload {
|
|
133
|
+
memberEmail: string;
|
|
134
|
+
targetCommunity: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface ListReassignRequestsParams {
|
|
138
|
+
limit?: number;
|
|
139
|
+
offset?: number;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Permissions (11)
|
|
146
|
+
|
|
147
|
+
| Permission | Description |
|
|
148
|
+
|---|---|
|
|
149
|
+
| `communities:listCommunities` | List all visible communities (paginated) |
|
|
150
|
+
| `communities:getCommunity` | Get a community by ID or slug |
|
|
151
|
+
| `communities:createInternshipPass` | Create an internship pass for a managed community |
|
|
152
|
+
| `communities:listInternshipPasses` | List internship passes for managed communities |
|
|
153
|
+
| `communities:getInternshipPass` | Retrieve an internship pass by ID |
|
|
154
|
+
| `communities:revokeInternshipPass` | Revoke an internship pass |
|
|
155
|
+
| `communities:createReassignRequest` | Create a member reassignment request |
|
|
156
|
+
| `communities:listReassignRequests` | List pending reassignment requests |
|
|
157
|
+
| `communities:getReassignRequest` | Retrieve a reassignment request by ID |
|
|
158
|
+
| `communities:acceptReassignRequest` | Accept a reassignment request (moves member) |
|
|
159
|
+
| `communities:rejectReassignRequest` | Reject a reassignment request |
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Events Module
|
|
2
|
+
|
|
3
|
+
**Trigger keywords:** event, meetup, gathering, calendar, schedule, room, booking, reserve, reservation, space, venue, location, conference, meeting, coworking
|
|
4
|
+
|
|
5
|
+
Access via `sdk.getEvents()`. Manage events, locations (event spaces and rooms), and room bookings.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Methods
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
listEvents(payload?: ListEventsParams): Promise<PaginatedResponse<Event>>
|
|
13
|
+
```
|
|
14
|
+
List active events with optional filters (search, type, location, date range). Permission: `events:listEvents`
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
createEvent(payload: CreateEventRequest): Promise<Event>
|
|
18
|
+
```
|
|
19
|
+
Create a new event. Permission: `events:createEvent`
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
addEventHost(payload: { eventId: number; email: string }): Promise<Event>
|
|
23
|
+
```
|
|
24
|
+
Add a co-host to an event. Only the primary host can add co-hosts, and only to upcoming events. Permission: `events:addEventHost`
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
listLocations(payload?: ListLocationsParams): Promise<Location[]>
|
|
28
|
+
```
|
|
29
|
+
List available locations. Returns an array (not paginated). Optional `locationType` filter. Permission: `events:listLocations`
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
listRoomBookings(payload?: ListRoomBookingsParams): Promise<PaginatedResponse<RoomBooking>>
|
|
33
|
+
```
|
|
34
|
+
List approved room bookings with optional filters. Permission: `events:listRoomBookings`
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
createRoomBooking(payload: CreateRoomBookingRequest): Promise<RoomBooking>
|
|
38
|
+
```
|
|
39
|
+
Create a room booking. Location must be of type `'room'`. Permission: `events:createRoomBooking`
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
getCryptoDepositPreflight(payload: { eventId: number }): Promise<DepositPreflight>
|
|
43
|
+
```
|
|
44
|
+
Preflight an event's FND security deposit. Host only, read-only. Returns the spender plus candidate ERC-20 tokens (iFND first, then FND) with on-chain decimals and base-unit amounts so you can approve the allowance BEFORE placing the deposit. Throws if not authenticated, not the host, or no deposit is required. Permission: `events:getCryptoDepositPreflight`
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
placeCryptoDeposit(payload: { eventId: number }): Promise<DepositResult>
|
|
48
|
+
```
|
|
49
|
+
Place the FND security deposit. The backend `transferFrom`s the stablecoin from the member's smart account into treasury; the member must first approve the allowance to the preflight `spender` via `getWallet().approveERC20(token.address, spender, BigInt(token.baseUnits))`. Returns `status: 'secured'` (`reference` is the tx hash) or `'awaiting_payment'` (read `statusReason`, fix, retry). Host only. Permission: `events:placeCryptoDeposit`
|
|
50
|
+
|
|
51
|
+
**Canonical 3-step deposit flow** (host only):
|
|
52
|
+
```typescript
|
|
53
|
+
// 1. Preflight — discover the spender + candidate tokens (iFND first, then FND)
|
|
54
|
+
const { spender, tokens } = await sdk.getEvents().getCryptoDepositPreflight({ eventId: 42 });
|
|
55
|
+
const token = tokens[0]; // prefer iFND; fall back to tokens[1] (FND) if the member lacks iFND
|
|
56
|
+
// 2. Approve the allowance to the spender (on-chain; the member confirms in the wallet)
|
|
57
|
+
await sdk.getWallet().approveERC20(token.address, spender, BigInt(token.baseUnits));
|
|
58
|
+
// 3. Place the deposit — the backend transferFroms it into treasury
|
|
59
|
+
const result = await sdk.getEvents().placeCryptoDeposit({ eventId: 42 });
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Types
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
type EventType = 'public' | 'members_plus_one' | 'members_only' | 'community_only';
|
|
68
|
+
type EventService = 'luma' | 'private' | 'test';
|
|
69
|
+
type ReviewStatus = 'not_required' | 'approved' | 'rejected' | 'pending';
|
|
70
|
+
type EventStatus = 'active' | 'suspended' | 'archived';
|
|
71
|
+
type LocationType = 'event_space' | 'room';
|
|
72
|
+
|
|
73
|
+
// Compact deposit status on the Event payload (host UI gating). 7 values.
|
|
74
|
+
type DepositStatus = 'not_required' | 'required' | 'pending' | 'secured' | 'released' | 'withheld' | 'failed';
|
|
75
|
+
|
|
76
|
+
// Raw status returned by placeCryptoDeposit. 6 values — has 'awaiting_payment' + 'grant', lacks 'not_required'/'required'/'pending'.
|
|
77
|
+
type CryptoDepositStatus = 'secured' | 'awaiting_payment' | 'grant' | 'released' | 'withheld' | 'failed';
|
|
78
|
+
|
|
79
|
+
interface Event {
|
|
80
|
+
id: number;
|
|
81
|
+
name: string;
|
|
82
|
+
description: string;
|
|
83
|
+
eventType: EventType;
|
|
84
|
+
eventService: EventService;
|
|
85
|
+
host: string;
|
|
86
|
+
community: number | null;
|
|
87
|
+
startsAt: string;
|
|
88
|
+
endsAt: string;
|
|
89
|
+
coverImage: string | null;
|
|
90
|
+
eventId: string;
|
|
91
|
+
location: string;
|
|
92
|
+
locationName: string;
|
|
93
|
+
displayLocation: string;
|
|
94
|
+
url: string;
|
|
95
|
+
additionalHosts: string[];
|
|
96
|
+
color: string;
|
|
97
|
+
reviewStatus: ReviewStatus;
|
|
98
|
+
status: EventStatus;
|
|
99
|
+
isHost?: boolean; // True iff the requesting user is this event's host (the user the deposit endpoints authorize)
|
|
100
|
+
deposit?: EventDeposit | null; // Read-only security-deposit summary, null when no deposit row
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface ListEventsParams {
|
|
104
|
+
search?: string;
|
|
105
|
+
eventType?: EventType;
|
|
106
|
+
locationType?: LocationType;
|
|
107
|
+
locationId?: string;
|
|
108
|
+
date?: string; // YYYY-MM-DD
|
|
109
|
+
startDate?: string; // YYYY-MM-DD
|
|
110
|
+
endDate?: string; // YYYY-MM-DD
|
|
111
|
+
page?: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface CreateEventRequest {
|
|
115
|
+
name: string;
|
|
116
|
+
eventType: EventType;
|
|
117
|
+
startsAt: string; // ISO 8601
|
|
118
|
+
endsAt: string; // ISO 8601
|
|
119
|
+
location: string; // readable_id slug
|
|
120
|
+
description?: string;
|
|
121
|
+
coverImage?: string; // Base64 data URI
|
|
122
|
+
additionalHosts?: string[];
|
|
123
|
+
color?: string; // Hex color code
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface Location {
|
|
127
|
+
id: number;
|
|
128
|
+
owner: number | null;
|
|
129
|
+
readableId: string;
|
|
130
|
+
name: string;
|
|
131
|
+
maxCapacity: number;
|
|
132
|
+
description: string;
|
|
133
|
+
directions: string;
|
|
134
|
+
locationType: LocationType;
|
|
135
|
+
warmupBuffer: string; // e.g. "00:10:00"
|
|
136
|
+
cooldownBuffer: string; // e.g. "00:15:00"
|
|
137
|
+
openBooking: boolean;
|
|
138
|
+
floorLocation: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface ListLocationsParams {
|
|
142
|
+
locationType?: LocationType;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface RoomBooking {
|
|
146
|
+
id: number;
|
|
147
|
+
startsAt: string;
|
|
148
|
+
endsAt: string;
|
|
149
|
+
location: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
interface ListRoomBookingsParams {
|
|
153
|
+
locationId?: string;
|
|
154
|
+
date?: string; // YYYY-MM-DD
|
|
155
|
+
startDate?: string; // YYYY-MM-DD
|
|
156
|
+
endDate?: string; // YYYY-MM-DD
|
|
157
|
+
page?: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface CreateRoomBookingRequest {
|
|
161
|
+
startsAt: string; // ISO 8601
|
|
162
|
+
endsAt: string; // ISO 8601
|
|
163
|
+
location: string; // readable_id (must be room type)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface EventDeposit {
|
|
167
|
+
status: DepositStatus;
|
|
168
|
+
amount: number; // snapshotted deposit amount in `currency` (e.g. 400 for the FND rail)
|
|
169
|
+
currency: string; // e.g. "usd"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
interface DepositPreflightToken {
|
|
173
|
+
key: string; // e.g. "ifnd_token" or "fnd_token"
|
|
174
|
+
address: string; // ERC-20 contract to approve the allowance on
|
|
175
|
+
decimals: number; // on-chain token decimals (read from the contract, never assumed)
|
|
176
|
+
baseUnits: string; // deposit amount in this token's base units, as a decimal string (wrap in BigInt() for approveERC20)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
interface DepositPreflight {
|
|
180
|
+
spender: string; // address to approve the allowance to (treasury) — never hardcode it
|
|
181
|
+
network: string; // e.g. "base" (prod) or "base_sepolia" (sandbox)
|
|
182
|
+
amount: string; // deposit amount in `currency`, as a decimal string (e.g. "400.00")
|
|
183
|
+
currency: string; // e.g. "usd"
|
|
184
|
+
tokens: DepositPreflightToken[]; // candidate tokens in preference order: iFND first, then FND
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
interface DepositResult {
|
|
188
|
+
provider: 'crypto'; // always 'crypto' on this rail
|
|
189
|
+
status: CryptoDepositStatus;
|
|
190
|
+
amount: string; // deposit amount in `currency`, as a decimal string
|
|
191
|
+
currency: string; // e.g. "usd"
|
|
192
|
+
reference: string; // on-chain tx hash when secured; empty otherwise
|
|
193
|
+
statusReason: string; // human-readable reason when not secured (e.g. insufficient iFND/FND allowance or balance)
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
> **Deposit notes:** `DepositStatus` (7 values, carried on the `Event` payload) DIFFERS from `CryptoDepositStatus` (6 values, returned by `placeCryptoDeposit` — it adds `awaiting_payment` + `grant` and drops `not_required`/`required`/`pending`). `DepositPreflightToken.baseUnits` is a decimal STRING — wrap it via `BigInt()` for `approveERC20`. Never hardcode `spender` or token addresses, and read on-chain `decimals` rather than assuming 6.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Permissions (8)
|
|
202
|
+
|
|
203
|
+
| Permission | Description |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `events:listEvents` | List events with optional filters (paginated) |
|
|
206
|
+
| `events:createEvent` | Create a new event |
|
|
207
|
+
| `events:addEventHost` | Add a co-host to an event |
|
|
208
|
+
| `events:listLocations` | List available locations (event spaces and rooms) |
|
|
209
|
+
| `events:listRoomBookings` | List room bookings (paginated) |
|
|
210
|
+
| `events:createRoomBooking` | Create a room booking |
|
|
211
|
+
| `events:getCryptoDepositPreflight` | Preflight an event's FND security deposit (spender, candidate tokens, amounts) before approving the allowance |
|
|
212
|
+
| `events:placeCryptoDeposit` | Place an event's FND security deposit (backend transferFrom from the member's smart account) |
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# SDK Initialization & Core Protocol
|
|
2
|
+
|
|
3
|
+
Frontier SDK v0.24.0 — `@frontiertower/frontier-sdk`
|
|
4
|
+
|
|
5
|
+
Import paths:
|
|
6
|
+
- `@frontiertower/frontier-sdk` -- main SDK class and access modules
|
|
7
|
+
- `@frontiertower/frontier-sdk/ui-utils` -- detection and standalone helpers
|
|
8
|
+
|
|
9
|
+
The package **root** also exports the bigint token-amount helpers `parseAmount`, `formatAmount`, `FND_DECIMALS`, and `InvalidAmountError` (from `@frontiertower/frontier-sdk`, **not** `/ui-utils`) — the canonical bridge for displaying and parsing the now-`bigint` FND amounts. See [`token-amount.md`](./token-amount.md).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Class: `FrontierSDK`
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { FrontierSDK } from '@frontiertower/frontier-sdk';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Constructor
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
const sdk = new FrontierSDK();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
On construction the SDK:
|
|
26
|
+
1. Instantiates all ten access modules (wallet, storage, chain, user, partnerships, thirdParty, communities, events, offices, navigation).
|
|
27
|
+
2. Registers a `window.addEventListener('message', ...)` listener that routes `SDKResponse` messages from `window.parent`.
|
|
28
|
+
3. Sends an `{ type: 'app:ready', payload: null }` postMessage to `window.parent` to notify the host that the app iframe is ready.
|
|
29
|
+
|
|
30
|
+
### `destroy(): void`
|
|
31
|
+
|
|
32
|
+
Call when the app is being torn down. Removes the message event listener, calls `this.navigation.destroy()` to clean up deep-link listeners, and clears all pending request promises.
|
|
33
|
+
|
|
34
|
+
### Internal: `request(type: string, payload?: any): Promise<any>`
|
|
35
|
+
|
|
36
|
+
Used by all access classes. Sends an `SDKRequest` via `window.parent.postMessage` and returns a promise that resolves/rejects when the host responds. Requests time out after **30 000 ms**.
|
|
37
|
+
|
|
38
|
+
## PostMessage Protocol Types
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
interface SDKRequest {
|
|
42
|
+
type: string; // e.g. 'wallet:getBalance'
|
|
43
|
+
requestId: string; // `${Date.now()}-${incrementingId}`
|
|
44
|
+
payload?: any;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface SDKResponse {
|
|
48
|
+
type: 'response' | 'error';
|
|
49
|
+
requestId: string;
|
|
50
|
+
result?: any;
|
|
51
|
+
error?: string;
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Module Getters
|
|
56
|
+
|
|
57
|
+
| Getter | Returns | Module |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `sdk.getWallet()` | `WalletAccess` | Wallet |
|
|
60
|
+
| `sdk.getStorage()` | `StorageAccess` | Storage |
|
|
61
|
+
| `sdk.getChain()` | `ChainAccess` | Chain |
|
|
62
|
+
| `sdk.getUser()` | `UserAccess` | User |
|
|
63
|
+
| `sdk.getPartnerships()` | `PartnershipsAccess` | Partnerships |
|
|
64
|
+
| `sdk.getThirdParty()` | `ThirdPartyAccess` | Third-Party |
|
|
65
|
+
| `sdk.getCommunities()` | `CommunitiesAccess` | Communities |
|
|
66
|
+
| `sdk.getEvents()` | `EventsAccess` | Events |
|
|
67
|
+
| `sdk.getOffices()` | `OfficesAccess` | Offices |
|
|
68
|
+
| `sdk.getNavigation()` | `NavigationAccess` | Navigation |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Security
|
|
73
|
+
|
|
74
|
+
### Allowed Origins
|
|
75
|
+
|
|
76
|
+
The SDK defines three allowed Frontier Wallet origins. Apps should only accept messages from these:
|
|
77
|
+
|
|
78
|
+
| Environment | Origin |
|
|
79
|
+
|---|---|
|
|
80
|
+
| Development | `http://localhost:5173` |
|
|
81
|
+
| Sandbox | `https://sandbox.os.frontiertower.io` |
|
|
82
|
+
| Production | `https://os.frontiertower.io` |
|
|
83
|
+
|
|
84
|
+
### Access Controls Verification
|
|
85
|
+
|
|
86
|
+
The `user:getVerifiedAccessControls` method provides a tamper-proof way to verify user access. The flow:
|
|
87
|
+
|
|
88
|
+
1. The PWA host relays a `SignedAccessControls` envelope from the Frontier API server.
|
|
89
|
+
2. The SDK decodes the Base64 payload, computes its SHA-256 hash, and verifies the ECDSA secp256k1 signature against a hardcoded public key for the current environment stage.
|
|
90
|
+
3. If the signature is valid, the decoded `AccessControlsPayload` is returned.
|
|
91
|
+
4. If invalid, the method throws -- the app should deny access.
|
|
92
|
+
|
|
93
|
+
Supported stages and their public keys (uncompressed secp256k1, hex):
|
|
94
|
+
|
|
95
|
+
| Stage(s) | Key |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `test` | `04aab6c393...` (test-only key) |
|
|
98
|
+
| `development`, `local`, `sandbox`, `staging` | `04dc3ab0e1...` (shared dev/sandbox key) |
|
|
99
|
+
| `production` | `045d1a0f9c...` (production key) |
|
|
100
|
+
|
|
101
|
+
**Rule: Always use `getVerifiedAccessControls()` for access-gating decisions.** Do not trust unsigned user data from other SDK methods for gating features, content, or permissions.
|
|
102
|
+
|
|
103
|
+
### PostMessage Security
|
|
104
|
+
|
|
105
|
+
- The SDK sends requests to `window.parent` with `'*'` as the target origin.
|
|
106
|
+
- The SDK only processes responses where `event.source === window.parent`.
|
|
107
|
+
- Requests auto-expire after 30 seconds.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Wildcard Permissions
|
|
112
|
+
|
|
113
|
+
Each module supports a wildcard permission that grants access to all methods in that module:
|
|
114
|
+
|
|
115
|
+
| Wildcard | Grants |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `wallet:*` | All wallet permissions |
|
|
118
|
+
| `storage:*` | All storage permissions |
|
|
119
|
+
| `chain:*` | All chain permissions |
|
|
120
|
+
| `user:*` | All user permissions |
|
|
121
|
+
| `partnerships:*` | All partnerships permissions |
|
|
122
|
+
| `thirdParty:*` | All third-party permissions |
|
|
123
|
+
| `communities:*` | All communities permissions |
|
|
124
|
+
| `events:*` | All events permissions |
|
|
125
|
+
| `offices:*` | All offices permissions |
|
|
126
|
+
| `navigation:*` | All navigation permissions |
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Navigation Module
|
|
2
|
+
|
|
3
|
+
**Trigger keywords:** navigate, deep link, deeplink, open app, app link, cross-app, redirect, launch app, inter-app
|
|
4
|
+
|
|
5
|
+
Access via `sdk.getNavigation()`. App-to-app deep linking. Allows apps to navigate to other Frontier OS apps and receive incoming deep link data.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Methods
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
openApp(appId: string, options?: NavigationOpenAppOptions): Promise<void>
|
|
13
|
+
```
|
|
14
|
+
Navigate the host to another app in the Frontier OS ecosystem. `appId` is the target app ID from the Frontier app registry. `options.path` provides an optional deep link path for the target app. `options.params` provides optional key-value params for the target app. Permission: `navigation:openApp`
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
close(): Promise<void>
|
|
18
|
+
```
|
|
19
|
+
Close the current app and return to the previous screen. Permission: `navigation:close`
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
onDeepLink(callback: (data: DeepLinkData) => void): () => void
|
|
23
|
+
```
|
|
24
|
+
Register a callback for incoming deep link data. Called when this app was opened via another app's `openApp()` call. Returns an unsubscribe function. No permission required (passive listener).
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Types
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
interface NavigationOpenAppOptions {
|
|
32
|
+
path?: string;
|
|
33
|
+
params?: Record<string, string>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface DeepLinkData {
|
|
37
|
+
path?: string;
|
|
38
|
+
params?: Record<string, string>;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Permissions (2)
|
|
45
|
+
|
|
46
|
+
| Permission | Description |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `navigation:openApp` | Navigate to another app |
|
|
49
|
+
| `navigation:close` | Close current app |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Offices Module
|
|
2
|
+
|
|
3
|
+
**Trigger keywords:** office, access pass, building, door, entry, visitor, check-in, checkin, physical access, facility
|
|
4
|
+
|
|
5
|
+
Access via `sdk.getOffices()`. Manage office access passes for membership contracts.
|
|
6
|
+
|
|
7
|
+
All endpoints require authentication, an active subscription, and manager status on the membership contract's organization (or superuser).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Methods
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
createAccessPass(payload: CreateAccessPassRequest): Promise<AccessPass>
|
|
15
|
+
```
|
|
16
|
+
Create an access pass for a membership contract. Auto-creates an inactive account if the user does not exist. Each user can only have one active pass per contract. Permission: `offices:createAccessPass`
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
listAccessPasses(payload?: ListAccessPassesParams): Promise<PaginatedResponse<AccessPass>>
|
|
20
|
+
```
|
|
21
|
+
List access passes for contracts the user manages. Active only by default; set `includeRevoked: true` for all. Ordered newest first. Permission: `offices:listAccessPasses`
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
getAccessPass(payload: { id: number }): Promise<AccessPass>
|
|
25
|
+
```
|
|
26
|
+
Get an access pass by ID. Permission: `offices:getAccessPass`
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
revokeAccessPass(payload: { id: number }): Promise<void>
|
|
30
|
+
```
|
|
31
|
+
Revoke an access pass. Cannot revoke an already-revoked pass. Permission: `offices:revokeAccessPass`
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Types
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
type AccessPassStatus = 'active' | 'revoked';
|
|
39
|
+
|
|
40
|
+
interface AccessPass {
|
|
41
|
+
id: number;
|
|
42
|
+
email: string;
|
|
43
|
+
firstName: string;
|
|
44
|
+
lastName: string;
|
|
45
|
+
status: AccessPassStatus;
|
|
46
|
+
membershipContract: number;
|
|
47
|
+
contractReference: string;
|
|
48
|
+
createdAt: string;
|
|
49
|
+
revokedAt: string | null;
|
|
50
|
+
updatedAt: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface CreateAccessPassRequest {
|
|
54
|
+
email: string;
|
|
55
|
+
firstName: string;
|
|
56
|
+
lastName: string;
|
|
57
|
+
membershipContract: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface ListAccessPassesParams {
|
|
61
|
+
limit?: number;
|
|
62
|
+
offset?: number;
|
|
63
|
+
includeRevoked?: boolean;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Permissions (4)
|
|
70
|
+
|
|
71
|
+
| Permission | Description |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `offices:createAccessPass` | Create an access pass for a membership contract |
|
|
74
|
+
| `offices:listAccessPasses` | List access passes for managed contracts (paginated) |
|
|
75
|
+
| `offices:getAccessPass` | Retrieve an access pass by ID |
|
|
76
|
+
| `offices:revokeAccessPass` | Revoke an access pass |
|