frontier-os-app-builder 1.1.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/agents/fos-executor.md +22 -65
  4. package/agents/fos-plan-checker.md +13 -12
  5. package/agents/fos-planner.md +20 -67
  6. package/agents/fos-researcher.md +14 -10
  7. package/agents/fos-verifier.md +11 -5
  8. package/bin/fos-tools.cjs +48 -11
  9. package/bin/install.js +8 -5
  10. package/commands/fos/add-feature.md +1 -2
  11. package/commands/fos/discuss.md +0 -1
  12. package/commands/fos/new-app.md +1 -3
  13. package/commands/fos/new-milestone.md +1 -1
  14. package/commands/fos/plan.md +0 -2
  15. package/package.json +7 -1
  16. package/references/app-patterns.md +46 -28
  17. package/references/deployment.md +40 -74
  18. package/references/module-index.md +32 -0
  19. package/references/sdk/chain.md +92 -0
  20. package/references/sdk/communities.md +159 -0
  21. package/references/sdk/events.md +212 -0
  22. package/references/sdk/init.md +126 -0
  23. package/references/sdk/navigation.md +49 -0
  24. package/references/sdk/offices.md +76 -0
  25. package/references/sdk/partnerships.md +111 -0
  26. package/references/sdk/storage.md +44 -0
  27. package/references/sdk/thirdparty.md +240 -0
  28. package/references/sdk/token-amount.md +99 -0
  29. package/references/sdk/types.md +27 -0
  30. package/references/sdk/ui-utils.md +39 -0
  31. package/references/sdk/user.md +208 -0
  32. package/references/sdk/wallet.md +334 -0
  33. package/references/verification-rules.md +18 -18
  34. package/templates/app/frontier-services.tsx +75 -18
  35. package/templates/app/layout.tsx +19 -9
  36. package/templates/app/package.json +2 -1
  37. package/templates/app/public/favicon.svg +3 -0
  38. package/templates/app/sdk-context.tsx +7 -9
  39. package/templates/app/sdk-services.tsx +92 -117
  40. package/templates/app/vercel.json +8 -47
  41. package/templates/state/plan.md +32 -14
  42. package/templates/state/roadmap.md +2 -2
  43. package/templates/state/summary.md +26 -29
  44. package/workflows/add-feature.md +6 -1
  45. package/workflows/discuss.md +9 -3
  46. package/workflows/execute-plan.md +3 -3
  47. package/workflows/execute.md +17 -6
  48. package/workflows/new-app.md +54 -18
  49. package/workflows/new-milestone.md +9 -2
  50. package/workflows/plan.md +14 -5
  51. package/workflows/ship.md +26 -10
  52. package/workflows/status.md +0 -1
  53. package/references/module-inference.md +0 -348
  54. package/references/sdk-surface.md +0 -1600
  55. package/templates/app/main-simple-standalone.tsx +0 -19
  56. package/templates/app/main-simple.tsx +0 -19
  57. package/templates/state/manifest.json +0 -12
@@ -0,0 +1,334 @@
1
+ # Wallet Module
2
+
3
+ **Trigger keywords:** payment, pay, charge, pos, checkout, purchase, buy, sell, transfer, send money, balance, funds, money, wallet, fnd, swap, exchange, convert, token, deposit, on-ramp, fund, withdraw, off-ramp, bank, fiat, subscription, billing, price, cost, fee, tip, donate, donation
4
+
5
+ Access via `sdk.getWallet()`. All methods use the current chain from the chain manager. Write operations require biometric authentication.
6
+
7
+ ---
8
+
9
+ ## Methods
10
+
11
+ ```typescript
12
+ getBalance(): Promise<WalletBalance>
13
+ ```
14
+ Returns raw balance breakdown (bigint values). To display, format each bigint field with `formatAmount()` from `@frontiertower/frontier-sdk`. Permission: `wallet:getBalance`
15
+
16
+ ```typescript
17
+ getAddress(): Promise<string>
18
+ ```
19
+ Returns the smart account contract address for the current chain. Permission: `wallet:getAddress`
20
+
21
+ ```typescript
22
+ getSmartAccount(): Promise<SmartAccount>
23
+ ```
24
+ Returns detailed smart account info including deployment status. Permission: `wallet:getSmartAccount`
25
+
26
+ ```typescript
27
+ transferERC20(
28
+ tokenAddress: string,
29
+ to: string,
30
+ amount: bigint,
31
+ overrides?: GasOverrides
32
+ ): Promise<UserOperationReceipt>
33
+ ```
34
+ Transfer ERC20 tokens. Amount in token's smallest unit. Permission: `wallet:transferERC20`
35
+
36
+ ```typescript
37
+ approveERC20(
38
+ tokenAddress: string,
39
+ spender: string,
40
+ amount: bigint,
41
+ overrides?: GasOverrides
42
+ ): Promise<UserOperationReceipt>
43
+ ```
44
+ Approve a spender for ERC20 tokens. Permission: `wallet:approveERC20`
45
+
46
+ ```typescript
47
+ transferNative(
48
+ to: string,
49
+ amount: bigint,
50
+ overrides?: GasOverrides
51
+ ): Promise<UserOperationReceipt>
52
+ ```
53
+ Transfer native currency (ETH). Amount in wei. Permission: `wallet:transferNative`
54
+
55
+ ```typescript
56
+ executeCall(
57
+ call: ExecuteCall,
58
+ overrides?: GasOverrides
59
+ ): Promise<UserOperationReceipt>
60
+ ```
61
+ Execute an arbitrary contract call. Permission: `wallet:executeCall`
62
+
63
+ ```typescript
64
+ executeBatchCall(
65
+ calls: ExecuteCall[],
66
+ overrides?: GasOverrides
67
+ ): Promise<UserOperationReceipt>
68
+ ```
69
+ Execute multiple calls atomically in a single transaction. Permission: `wallet:executeBatchCall`
70
+
71
+ ```typescript
72
+ transferFrontierDollar(
73
+ to: string,
74
+ amount: bigint,
75
+ overrides?: GasOverrides
76
+ ): Promise<UserOperationReceipt>
77
+ ```
78
+ Transfer FND (Frontier Network Dollar). Amount in base units — build with `parseAmount('10.5')` from `@frontiertower/frontier-sdk`. Permission: `wallet:transferFrontierDollar`
79
+
80
+ ```typescript
81
+ transferInternalFrontierDollar(
82
+ to: string,
83
+ amount: bigint,
84
+ overrides?: GasOverrides
85
+ ): Promise<UserOperationReceipt>
86
+ ```
87
+ Transfer iFND (Internal Frontier Network Dollar). Amount in base units — build with `parseAmount('10.5')` from `@frontiertower/frontier-sdk`. Permission: `wallet:transferInternalFrontierDollar`
88
+
89
+ ```typescript
90
+ transferOverallFrontierDollar(
91
+ to: string,
92
+ amount: bigint,
93
+ overrides?: GasOverrides
94
+ ): Promise<UserOperationReceipt>
95
+ ```
96
+ Transfer using iFND first, falling back to FND for the remainder. Permission: `wallet:transferOverallFrontierDollar`
97
+
98
+ ```typescript
99
+ getSupportedTokens(): Promise<string[]>
100
+ ```
101
+ Returns token symbols supported for swaps on the current chain (e.g. `['FND', 'USDC', 'WETH']`). Permission: `wallet:getSupportedTokens`
102
+
103
+ ```typescript
104
+ swap(
105
+ sourceToken: string,
106
+ targetToken: string,
107
+ sourceNetwork: string,
108
+ targetNetwork: string,
109
+ amount: bigint
110
+ ): Promise<SwapResult>
111
+ ```
112
+ Execute a token swap (same-chain or cross-chain). Amount in base units — build with `parseAmount('10.5')` from `@frontiertower/frontier-sdk`. Permission: `wallet:swap`
113
+
114
+ ```typescript
115
+ quoteSwap(
116
+ sourceToken: string,
117
+ targetToken: string,
118
+ sourceNetwork: string,
119
+ targetNetwork: string,
120
+ amount: bigint
121
+ ): Promise<SwapQuote>
122
+ ```
123
+ Get a swap quote without executing. Permission: `wallet:quoteSwap`
124
+
125
+ ```typescript
126
+ getUsdDepositInstructions(): Promise<OnRampResponse<UsdDepositInstructions>>
127
+ ```
128
+ Get US bank details for fiat-to-crypto on-ramp. Requires approved KYC. Permission: `wallet:getUsdDepositInstructions`
129
+
130
+ ```typescript
131
+ getEurDepositInstructions(): Promise<OnRampResponse<EurDepositInstructions>>
132
+ ```
133
+ Get SEPA bank details for EUR fiat-to-crypto on-ramp. Requires approved KYC. Permission: `wallet:getEurDepositInstructions`
134
+
135
+ ```typescript
136
+ getLinkedBanks(): Promise<LinkedBanksResponse>
137
+ ```
138
+ Get all linked bank accounts for off-ramp withdrawals. Requires approved KYC. Permission: `wallet:getLinkedBanks`
139
+
140
+ ```typescript
141
+ linkUsBankAccount(
142
+ accountOwnerName: string,
143
+ bankName: string,
144
+ routingNumber: string,
145
+ accountNumber: string,
146
+ checkingOrSavings: 'checking' | 'savings',
147
+ address: BillingAddress
148
+ ): Promise<LinkBankResponse>
149
+ ```
150
+ Link a US bank account for USD withdrawals via ACH. Requires approved KYC. Permission: `wallet:linkUsBankAccount`
151
+
152
+ ```typescript
153
+ linkEuroAccount(
154
+ accountOwnerName: string,
155
+ accountOwnerType: AccountOwnerType,
156
+ firstName: string,
157
+ lastName: string,
158
+ ibanAccountNumber: string,
159
+ bic?: string
160
+ ): Promise<LinkBankResponse>
161
+ ```
162
+ Link a EUR/IBAN bank account for SEPA withdrawals. Requires approved KYC. Permission: `wallet:linkEuroAccount`
163
+
164
+ ```typescript
165
+ deleteLinkedBank(bankId: string): Promise<void>
166
+ ```
167
+ Delete a linked bank account. Permission: `wallet:deleteLinkedBank`
168
+
169
+ ```typescript
170
+ getDeprecatedSmartAccounts(): Promise<DeprecatedSmartAccount[]>
171
+ ```
172
+ Get deprecated smart accounts that still have active gas sponsorship. Permission: `wallet:getDeprecatedSmartAccounts`
173
+
174
+ ---
175
+
176
+ ## Types
177
+
178
+ ```typescript
179
+ interface SmartAccount {
180
+ id: number;
181
+ ownerAddress: string;
182
+ contractAddress: string | null;
183
+ network: string;
184
+ status: string;
185
+ deploymentTransactionHash: string;
186
+ createdAt: string;
187
+ }
188
+
189
+ interface WalletBalance {
190
+ total: bigint;
191
+ fnd: bigint;
192
+ internalFnd: bigint;
193
+ }
194
+
195
+ interface UserOperationReceipt {
196
+ userOpHash: string;
197
+ transactionHash: string;
198
+ blockNumber: bigint;
199
+ success: boolean;
200
+ }
201
+
202
+ interface GasOverrides {
203
+ maxFeePerGas?: bigint;
204
+ maxPriorityFeePerGas?: bigint;
205
+ gasLimit?: bigint;
206
+ }
207
+
208
+ interface ExecuteCall {
209
+ target: string;
210
+ value?: bigint;
211
+ data: string;
212
+ }
213
+
214
+ interface SwapParams {
215
+ sourceToken: string;
216
+ targetToken: string;
217
+ sourceNetwork: string;
218
+ targetNetwork: string;
219
+ amount: bigint;
220
+ }
221
+
222
+ enum SwapResultStatus {
223
+ COMPLETED = 'COMPLETED',
224
+ SUBMITTED = 'SUBMITTED',
225
+ }
226
+
227
+ interface SwapResult {
228
+ sourceChain: object;
229
+ targetChain: object;
230
+ sourceToken: object;
231
+ targetToken: object;
232
+ status: SwapResultStatus;
233
+ }
234
+
235
+ interface SwapQuote {
236
+ sourceChain: object;
237
+ targetChain: object;
238
+ sourceToken: object;
239
+ targetToken: object;
240
+ expectedAmountOut: bigint;
241
+ minAmountOut: bigint;
242
+ }
243
+
244
+ interface UsdDepositInstructions {
245
+ currency: 'usd';
246
+ bankName: string;
247
+ bankAddress: string;
248
+ bankRoutingNumber: string;
249
+ bankAccountNumber: string;
250
+ bankBeneficiaryName: string;
251
+ paymentRail: string;
252
+ }
253
+
254
+ interface EurDepositInstructions {
255
+ currency: 'eur';
256
+ iban: string;
257
+ bic: string;
258
+ accountHolderName: string;
259
+ }
260
+
261
+ interface OnRampResponse<T = UsdDepositInstructions | EurDepositInstructions> {
262
+ currency: 'usd' | 'eur';
263
+ depositInstructions: T;
264
+ destinationAddress: string;
265
+ destinationNetwork: string;
266
+ }
267
+
268
+ interface LinkedBank {
269
+ id: string;
270
+ bankName: string;
271
+ last4: string;
272
+ withdrawalAddress: string;
273
+ network: string;
274
+ }
275
+
276
+ interface LinkedBanksResponse {
277
+ banks: LinkedBank[];
278
+ }
279
+
280
+ interface LinkBankResponse {
281
+ externalAccountId: string;
282
+ bankName: string;
283
+ withdrawalAddress: string;
284
+ network: string;
285
+ }
286
+
287
+ interface BillingAddress {
288
+ streetLine1: string;
289
+ streetLine2?: string;
290
+ city: string;
291
+ state: string;
292
+ postalCode: string;
293
+ country: string;
294
+ }
295
+
296
+ type AccountOwnerType = 'individual' | 'business';
297
+
298
+ interface DeprecatedSmartAccount {
299
+ id: number;
300
+ ownerAddress: string;
301
+ contractAddress: string;
302
+ network: string;
303
+ deprecatedAt: string;
304
+ version: number;
305
+ }
306
+ ```
307
+
308
+ ---
309
+
310
+ ## Permissions (21)
311
+
312
+ | Permission | Description |
313
+ |---|---|
314
+ | `wallet:getBalance` | Access wallet balance (raw bigint) |
315
+ | `wallet:getAddress` | Access wallet address |
316
+ | `wallet:getSmartAccount` | Access smart account details |
317
+ | `wallet:transferERC20` | Transfer ERC20 tokens |
318
+ | `wallet:approveERC20` | Approve ERC20 token spending |
319
+ | `wallet:transferNative` | Transfer native currency (ETH) |
320
+ | `wallet:transferFrontierDollar` | Transfer FND (Frontier Network Dollar) |
321
+ | `wallet:transferInternalFrontierDollar` | Transfer iFND (Internal Frontier Network Dollar) |
322
+ | `wallet:transferOverallFrontierDollar` | Transfer using iFND first, fallback to FND |
323
+ | `wallet:executeCall` | Execute arbitrary contract call |
324
+ | `wallet:executeBatchCall` | Execute multiple contract calls atomically |
325
+ | `wallet:getSupportedTokens` | Get supported token symbols for swaps |
326
+ | `wallet:swap` | Execute token swap (same-chain or cross-chain) |
327
+ | `wallet:quoteSwap` | Get swap quote without executing |
328
+ | `wallet:getUsdDepositInstructions` | Get USD bank deposit instructions (on-ramp) |
329
+ | `wallet:getEurDepositInstructions` | Get EUR/SEPA deposit instructions (on-ramp) |
330
+ | `wallet:getLinkedBanks` | Get linked bank accounts (off-ramp) |
331
+ | `wallet:linkUsBankAccount` | Link US bank account for USD withdrawals |
332
+ | `wallet:linkEuroAccount` | Link EUR/IBAN bank account for EUR withdrawals |
333
+ | `wallet:deleteLinkedBank` | Delete a linked bank account |
334
+ | `wallet:getDeprecatedSmartAccounts` | Get deprecated smart accounts with active gas sponsorship |
@@ -48,7 +48,7 @@ The `src/` directory must contain at minimum:
48
48
  src/
49
49
  main.tsx
50
50
  lib/
51
- sdk-context.tsx
51
+ frontier-services.tsx
52
52
  views/
53
53
  Layout.tsx
54
54
  styles/
@@ -70,6 +70,8 @@ src/
70
70
  components/
71
71
  ```
72
72
 
73
+ > **Note:** `src/lib/sdk-context.tsx` and `src/lib/sdk-services.tsx` exist only **after** the SDK Integration phase (Tier 2). They are not present during pre-integration phases, so S-02 (a Tier-1 check that runs after every phase) must not require them.
74
+
73
75
  **Pass condition:** All required paths exist. Optional paths should be present when the corresponding feature is used (e.g., `router.tsx` when `react-router-dom` is a dependency).
74
76
 
75
77
  ### S-03: No extraneous top-level files
@@ -130,24 +132,26 @@ if (standaloneHtml) {
130
132
  }
131
133
  ```
132
134
 
133
- ### I-03: SdkProvider wrapping children in Layout.tsx
135
+ ### I-03: SdkProvider + FrontierServicesProvider wrapping children in Layout.tsx
134
136
 
135
- When inside the Frontier app, the Layout must wrap its children (either `<Outlet />` or a single component) with `<SdkProvider>`.
137
+ When inside the Frontier app, the Layout must wrap its children with `<SdkProvider>` AND bridge the SDK into `<FrontierServicesProvider>`. Feature code consumes `useServices()`, so without `FrontierServicesProvider` the app **crashes at runtime** ("useServices must be used within FrontierServicesProvider").
136
138
 
137
- **Pass condition:** The return statement for the "in Frontier" case contains:
139
+ **Pass condition:** Layout's "in Frontier" return path contains both providers:
138
140
  ```tsx
139
141
  <SdkProvider>
140
- <Outlet /> {/* or a single component */}
142
+ <SdkServicesBridge> {/* useSdk() createSdkServices(sdk) FrontierServicesProvider */}
143
+ <Outlet />
144
+ </SdkServicesBridge>
141
145
  </SdkProvider>
142
146
  ```
143
147
 
144
148
  ### I-04: useSdk() hook available and used
145
149
 
146
- `src/lib/sdk-context.tsx` must export `useSdk` and `SdkProvider`. Any view component that accesses the SDK must call `useSdk()`.
150
+ `src/lib/sdk-context.tsx` must export `useSdk` and `SdkProvider`. Only `sdk-services.tsx` and `src/views/Layout.tsx` may consume them; feature code under `src/hooks/`, `src/views/`, and `src/components/` accesses the SDK indirectly through `useServices()` (see M-03), never `useSdk()` directly.
147
151
 
148
152
  **Pass condition:**
149
153
  - `sdk-context.tsx` exports `useSdk` and `SdkProvider`.
150
- - Every file that accesses SDK methods imports `useSdk` from `../lib/sdk-context` (or appropriate relative path) and calls it within the component body.
154
+ - `useSdk()` / `SdkProvider` are consumed only by `sdk-services.tsx` and `Layout.tsx`; feature code uses `useServices()` from `../lib/frontier-services` (see M-03), not `useSdk`.
151
155
  - No direct `new FrontierSDK()` calls outside of `sdk-context.tsx`.
152
156
 
153
157
  ---
@@ -156,27 +160,23 @@ When inside the Frontier app, the Layout must wrap its children (either `<Outlet
156
160
 
157
161
  These verify configuration files have the correct content.
158
162
 
159
- ### C-01: vercel.json has all 3 CORS origin blocks
163
+ ### C-01: vercel.json has CORS + CSP frame-ancestors for the 3 origins
160
164
 
161
165
  > **Tier 2 — SDK Integration phase only.**
162
166
 
163
- The `vercel.json` file must contain exactly 3 header blocks, one for each allowed origin:
164
-
165
- 1. `https://os.frontiertower.io`
166
- 2. `https://sandbox.os.frontiertower.io`
167
- 3. `http://localhost:5173`
168
-
169
- Each block must include:
170
- - `Access-Control-Allow-Origin` matching the origin
167
+ The `vercel.json` file uses a single unconditional header block containing:
168
+ - `Access-Control-Allow-Origin: https://os.frontiertower.io` (the production origin)
171
169
  - `Access-Control-Allow-Methods: GET, OPTIONS`
172
170
  - `Access-Control-Allow-Headers: Content-Type`
171
+ - `Content-Security-Policy` with `frame-ancestors` listing all 3 live origins (`https://os.frontiertower.io`, `https://sandbox.os.frontiertower.io`, `http://localhost:5173`) — this governs iframe embedding
172
+ - security headers: `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`
173
173
 
174
174
  It must also include the SPA rewrite:
175
175
  ```json
176
176
  { "source": "/(.*)", "destination": "/index.html" }
177
177
  ```
178
178
 
179
- **Pass condition:** All 3 origin blocks are present with correct headers. The rewrite rule exists.
179
+ **Pass condition:** All 3 origins appear in the `frame-ancestors` directive, the CORS and security headers are present, and the rewrite rule exists.
180
180
 
181
181
  ### C-02: tsconfig.json has strict mode and vitest types
182
182
 
@@ -443,7 +443,7 @@ Source files under `src/hooks/`, `src/views/`, and `src/components/` must NOT im
443
443
  | I-02 | SDK | createStandaloneHTML() fallback in Layout.tsx | Tier 2 | Error |
444
444
  | I-03 | SDK | SdkProvider wrapping children in Layout.tsx | Tier 2 | Error |
445
445
  | I-04 | SDK | useSdk() hook available and used | Tier 2 | Error |
446
- | C-01 | Configuration | vercel.json has all 3 CORS origin blocks | Tier 2 | Error |
446
+ | C-01 | Configuration | vercel.json: CORS + CSP frame-ancestors (3 origins) | Tier 2 | Error |
447
447
  | C-02 | Configuration | tsconfig.json has strict mode and vitest types | Tier 1 | Error |
448
448
  | C-03 | Configuration | postcss.config.js imports @tailwindcss/postcss | Tier 1 | Error |
449
449
  | C-04 | Configuration | package.json has correct scripts | Tier 1 | Error |
@@ -1,5 +1,15 @@
1
1
  import { createContext, useContext, type ReactNode } from 'react';
2
2
 
3
+ // ── Token amounts (bigint base units) ────────────────────────────────────────
4
+ //
5
+ // Since Frontier SDK v0.23, all FND amounts are bigint base units (FND_DECIMALS = 6).
6
+ // WalletBalance fields and every transfer*/swap amount are bigint, NOT display strings.
7
+ // - Display a balance: formatAmount(b.fnd) → symbol-free decimal string (100_500000n → '100.5')
8
+ // - Parse user input: parseAmount('10.5') → bigint (throws InvalidAmountError on bad input)
9
+ // - Write: wallet.transferFrontierDollar(to, parseAmount('10.5'))
10
+ // Import formatAmount / parseAmount / FND_DECIMALS / InvalidAmountError from the package
11
+ // ROOT '@frontiertower/frontier-sdk' (NOT the /ui-utils subpath). The SDK never adds '$'.
12
+
3
13
  // ── Shared Types ────────────────────────────────────────────────────────────
4
14
 
5
15
  export interface PaginatedResponse<T> {
@@ -15,12 +25,6 @@ export interface WalletBalance {
15
25
  internalFnd: bigint;
16
26
  }
17
27
 
18
- export interface WalletBalanceFormatted {
19
- total: string;
20
- fnd: string;
21
- internalFnd: string;
22
- }
23
-
24
28
  export interface SmartAccount {
25
29
  id: number;
26
30
  ownerAddress: string;
@@ -63,8 +67,8 @@ export interface SwapQuote {
63
67
  targetChain: object;
64
68
  sourceToken: object;
65
69
  targetToken: object;
66
- expectedAmountOut: string;
67
- minAmountOut: string;
70
+ expectedAmountOut: bigint;
71
+ minAmountOut: bigint;
68
72
  }
69
73
 
70
74
  export interface OnRampResponse<T> {
@@ -407,6 +411,57 @@ export interface FrontierEvent {
407
411
  color: string;
408
412
  reviewStatus: string;
409
413
  status: string;
414
+ isHost?: boolean;
415
+ deposit?: EventDeposit | null;
416
+ }
417
+
418
+ // ── Events Deposit Types ──────────────────────────────────────────────────────
419
+
420
+ export type DepositStatus =
421
+ | 'not_required'
422
+ | 'required'
423
+ | 'pending'
424
+ | 'secured'
425
+ | 'released'
426
+ | 'withheld'
427
+ | 'failed';
428
+
429
+ export type CryptoDepositStatus =
430
+ | 'secured'
431
+ | 'awaiting_payment'
432
+ | 'grant'
433
+ | 'released'
434
+ | 'withheld'
435
+ | 'failed';
436
+
437
+ export interface EventDeposit {
438
+ status: DepositStatus;
439
+ amount: number;
440
+ currency: string;
441
+ }
442
+
443
+ export interface DepositPreflightToken {
444
+ key: string;
445
+ address: string;
446
+ decimals: number;
447
+ baseUnits: string;
448
+ }
449
+
450
+ export interface DepositPreflight {
451
+ spender: string;
452
+ network: string;
453
+ amount: string;
454
+ currency: string;
455
+ tokens: DepositPreflightToken[];
456
+ }
457
+
458
+ export interface DepositResult {
459
+ provider: 'crypto';
460
+ status: CryptoDepositStatus;
461
+ amount: string;
462
+ currency: string;
463
+ reference: string;
464
+ statusReason: string;
410
465
  }
411
466
 
412
467
  export interface EventLocation {
@@ -481,7 +536,6 @@ export interface DeepLinkData {
481
536
 
482
537
  export interface WalletService {
483
538
  getBalance(): Promise<WalletBalance>;
484
- getBalanceFormatted(): Promise<WalletBalanceFormatted>;
485
539
  getAddress(): Promise<string>;
486
540
  getSmartAccount(): Promise<SmartAccount>;
487
541
  transferERC20(tokenAddress: string, to: string, amount: bigint, overrides?: GasOverrides): Promise<UserOperationReceipt>;
@@ -489,12 +543,12 @@ export interface WalletService {
489
543
  transferNative(to: string, amount: bigint, overrides?: GasOverrides): Promise<UserOperationReceipt>;
490
544
  executeCall(call: ExecuteCall, overrides?: GasOverrides): Promise<UserOperationReceipt>;
491
545
  executeBatchCall(calls: ExecuteCall[], overrides?: GasOverrides): Promise<UserOperationReceipt>;
492
- transferFrontierDollar(to: string, amount: string, overrides?: GasOverrides): Promise<UserOperationReceipt>;
493
- transferInternalFrontierDollar(to: string, amount: string, overrides?: GasOverrides): Promise<UserOperationReceipt>;
494
- transferOverallFrontierDollar(to: string, amount: string, overrides?: GasOverrides): Promise<UserOperationReceipt>;
546
+ transferFrontierDollar(to: string, amount: bigint, overrides?: GasOverrides): Promise<UserOperationReceipt>;
547
+ transferInternalFrontierDollar(to: string, amount: bigint, overrides?: GasOverrides): Promise<UserOperationReceipt>;
548
+ transferOverallFrontierDollar(to: string, amount: bigint, overrides?: GasOverrides): Promise<UserOperationReceipt>;
495
549
  getSupportedTokens(): Promise<string[]>;
496
- swap(sourceToken: string, targetToken: string, sourceNetwork: string, targetNetwork: string, amount: string): Promise<SwapResult>;
497
- quoteSwap(sourceToken: string, targetToken: string, sourceNetwork: string, targetNetwork: string, amount: string): Promise<SwapQuote>;
550
+ swap(sourceToken: string, targetToken: string, sourceNetwork: string, targetNetwork: string, amount: bigint): Promise<SwapResult>;
551
+ quoteSwap(sourceToken: string, targetToken: string, sourceNetwork: string, targetNetwork: string, amount: bigint): Promise<SwapQuote>;
498
552
  getUsdDepositInstructions(): Promise<OnRampResponse<UsdDepositInstructions>>;
499
553
  getEurDepositInstructions(): Promise<OnRampResponse<EurDepositInstructions>>;
500
554
  getLinkedBanks(): Promise<LinkedBanksResponse>;
@@ -505,7 +559,7 @@ export interface WalletService {
505
559
  }
506
560
 
507
561
  export interface StorageService {
508
- get<T = any>(key: string): Promise<T>;
562
+ get<T = any>(key: string): Promise<T | null>;
509
563
  set(key: string, value: any): Promise<void>;
510
564
  remove(key: string): Promise<void>;
511
565
  clear(): Promise<void>;
@@ -579,6 +633,8 @@ export interface EventsService {
579
633
  listLocations(payload?: { locationType?: LocationType }): Promise<EventLocation[]>;
580
634
  listRoomBookings(payload?: { locationId?: string; date?: string; startDate?: string; endDate?: string; page?: number }): Promise<PaginatedResponse<RoomBooking>>;
581
635
  createRoomBooking(payload: { startsAt: string; endsAt: string; location: string }): Promise<RoomBooking>;
636
+ getCryptoDepositPreflight(payload: { eventId: number }): Promise<DepositPreflight>;
637
+ placeCryptoDeposit(payload: { eventId: number }): Promise<DepositResult>;
582
638
  }
583
639
 
584
640
  export interface OfficesService {
@@ -639,7 +695,6 @@ async function mockWriteOp(module: string, method: string): Promise<UserOperatio
639
695
  export function createMockServices(): FrontierServices {
640
696
  const wallet: WalletService = {
641
697
  async getBalance() { mockLog('wallet', 'getBalance'); return { total: BigInt(10_500000), fnd: BigInt(7_500000), internalFnd: BigInt(3_000000) }; },
642
- async getBalanceFormatted() { mockLog('wallet', 'getBalanceFormatted'); return { total: '$100.00', fnd: '$75.00', internalFnd: '$25.00' }; },
643
698
  async getAddress() { mockLog('wallet', 'getAddress'); return '0x1234567890abcdef1234567890abcdef12345678'; },
644
699
  async getSmartAccount() { mockLog('wallet', 'getSmartAccount'); return { id: 1, ownerAddress: '0xowner', contractAddress: '0xcontract', network: 'base-sepolia', status: 'deployed', deploymentTransactionHash: '0xdeploy', createdAt: new Date().toISOString() }; },
645
700
  async transferERC20() { return mockWriteOp('wallet', 'transferERC20'); },
@@ -652,7 +707,7 @@ export function createMockServices(): FrontierServices {
652
707
  async transferOverallFrontierDollar() { return mockWriteOp('wallet', 'transferOverallFrontierDollar'); },
653
708
  async getSupportedTokens() { mockLog('wallet', 'getSupportedTokens'); return ['FND', 'USDC', 'WETH']; },
654
709
  async swap() { mockLog('wallet', 'swap'); await delay(1000); return { sourceChain: {}, targetChain: {}, sourceToken: {}, targetToken: {}, status: 'COMPLETED' as const }; },
655
- async quoteSwap() { mockLog('wallet', 'quoteSwap'); return { sourceChain: {}, targetChain: {}, sourceToken: {}, targetToken: {}, expectedAmountOut: '10.00', minAmountOut: '9.95' }; },
710
+ async quoteSwap() { mockLog('wallet', 'quoteSwap'); return { sourceChain: {}, targetChain: {}, sourceToken: {}, targetToken: {}, expectedAmountOut: BigInt(10_000000), minAmountOut: BigInt(9_950000) }; },
656
711
  async getUsdDepositInstructions() { mockLog('wallet', 'getUsdDepositInstructions'); return { currency: 'usd', depositInstructions: { currency: 'usd', bankName: 'Mock Bank', bankAddress: '123 Mock St', bankRoutingNumber: '000000000', bankAccountNumber: '1234567890', bankBeneficiaryName: 'Frontier Mock', paymentRail: 'ach' }, destinationAddress: '0xdest', destinationNetwork: 'base' }; },
657
712
  async getEurDepositInstructions() { mockLog('wallet', 'getEurDepositInstructions'); return { currency: 'eur', depositInstructions: { currency: 'eur', iban: 'DE00000000000000000000', bic: 'MOCKDEFF', accountHolderName: 'Frontier Mock' }, destinationAddress: '0xdest', destinationNetwork: 'base' }; },
658
713
  async getLinkedBanks() { mockLog('wallet', 'getLinkedBanks'); return { banks: [] }; },
@@ -663,7 +718,7 @@ export function createMockServices(): FrontierServices {
663
718
  };
664
719
 
665
720
  const storage: StorageService = {
666
- async get<T = any>(key: string): Promise<T> {
721
+ async get<T = any>(key: string): Promise<T | null> {
667
722
  mockLog('storage', 'get');
668
723
  const raw = localStorage.getItem(MOCK_PREFIX + key);
669
724
  return raw ? JSON.parse(raw) : null;
@@ -765,6 +820,8 @@ export function createMockServices(): FrontierServices {
765
820
  async listLocations() { mockLog('events', 'listLocations'); return []; },
766
821
  async listRoomBookings() { mockLog('events', 'listRoomBookings'); return emptyPage(); },
767
822
  async createRoomBooking() { mockLog('events', 'createRoomBooking'); await delay(1000); return { id: 1, startsAt: new Date().toISOString(), endsAt: new Date(Date.now() + 3600000).toISOString(), location: 'mock-room' }; },
823
+ async getCryptoDepositPreflight() { mockLog('events', 'getCryptoDepositPreflight'); return { spender: '0xspender', network: 'base-sepolia', amount: '400.00', currency: 'usd', tokens: [{ key: 'ifnd_token', address: '0xifnd', decimals: 6, baseUnits: '400000000' }, { key: 'fnd_token', address: '0xfnd', decimals: 6, baseUnits: '400000000' }] }; },
824
+ async placeCryptoDeposit() { mockLog('events', 'placeCryptoDeposit'); await delay(1000); return { provider: 'crypto', status: 'secured', amount: '400.00', currency: 'usd', reference: '0xmocktxhash', statusReason: '' }; },
768
825
  };
769
826
 
770
827
  const offices: OfficesService = {
@@ -1,21 +1,27 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useEffect, useMemo, useState, type ReactNode } from 'react';
2
2
  import { Outlet } from 'react-router-dom';
3
3
  import { isInFrontierApp, createStandaloneHTML } from '@frontiertower/frontier-sdk/ui-utils';
4
- import { SdkProvider } from '../lib/sdk-context';
4
+ import { SdkProvider, useSdk } from '../lib/sdk-context';
5
+ import { FrontierServicesProvider } from '../lib/frontier-services';
6
+ import { createSdkServices } from '../lib/sdk-services';
7
+
8
+ // Bridges the live SDK (from SdkProvider) into the FrontierServices seam that
9
+ // feature code consumes via useServices(). Keeps feature code SDK-agnostic: the
10
+ // exact same components run against mocks (standalone) and the real SDK (iframe).
11
+ const SdkServicesBridge = ({ children }: { children: ReactNode }) => {
12
+ const sdk = useSdk();
13
+ const services = useMemo(() => createSdkServices(sdk), [sdk]);
14
+ return <FrontierServicesProvider services={services}>{children}</FrontierServicesProvider>;
15
+ };
5
16
 
6
17
  export const Layout = () => {
7
18
  const [loading, setLoading] = useState(true);
8
19
  const [standaloneHtml, setStandaloneHtml] = useState('');
9
20
 
10
21
  useEffect(() => {
11
- const inFrontier = isInFrontierApp();
12
-
13
- if (!inFrontier) {
22
+ if (!isInFrontierApp()) {
14
23
  setStandaloneHtml(createStandaloneHTML('{{APP_NAME}}'));
15
- setLoading(false);
16
- return;
17
24
  }
18
-
19
25
  setLoading(false);
20
26
  }, []);
21
27
 
@@ -37,9 +43,13 @@ export const Layout = () => {
37
43
  );
38
44
  }
39
45
 
46
+ // In-frame: provide both the raw SDK (useSdk) and the SDK-backed services
47
+ // seam (useServices) so every feature component works unchanged from standalone.
40
48
  return (
41
49
  <SdkProvider>
42
- <Outlet />
50
+ <SdkServicesBridge>
51
+ <Outlet />
52
+ </SdkServicesBridge>
43
53
  </SdkProvider>
44
54
  );
45
55
  };
@@ -15,7 +15,8 @@
15
15
  "react": "^19.2.3",
16
16
  "react-dom": "^19.2.3",
17
17
  "react-icons": "^5.5.0",
18
- "react-router-dom": "^7.12.0"
18
+ "react-router-dom": "^7.12.0",
19
+ "viem": "^2.44.0"
19
20
  },
20
21
  "devDependencies": {
21
22
  "@tailwindcss/postcss": "^4.1.18",
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" role="img" aria-label="Frontier">
2
+ <circle cx="16" cy="16" r="14" fill="#764AE2" />
3
+ </svg>