@fun-xyz/fiat-contract 0.22.2 → 0.24.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/README.md +83 -44
- package/dist/chunk-5UEPWL7B.mjs +11 -0
- package/dist/{chunk-5GRPYE2U.mjs → chunk-CRNKRIKR.mjs} +1 -1
- package/dist/{chunk-DTHUNDA7.mjs → chunk-IPOPANXS.mjs} +2 -2
- package/dist/chunk-T5JAYOWY.mjs +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +241 -64
- package/dist/index.mjs +238 -65
- package/dist/providers/moonpay.d.ts +107 -109
- package/dist/providers/moonpay.js +1 -1
- package/dist/providers/moonpay.mjs +1 -1
- package/dist/providers/moonpay.schemas.d.ts +22 -5
- package/dist/routing.d.ts +168 -0
- package/dist/routing.js +31 -0
- package/dist/routing.mjs +7 -0
- package/dist/schemas.d.ts +9 -1
- package/dist/table.js +1 -1
- package/dist/table.mjs +1 -1
- package/dist/terms.d.ts +2 -2
- package/dist/terms.mjs +1 -1
- package/dist/types.d.ts +16 -1
- package/package.json +11 -1
- package/routing.d.ts +1 -0
- package/routing.js +2 -0
- package/dist/chunk-3HCZ6JBJ.mjs +0 -9
package/README.md
CHANGED
|
@@ -13,15 +13,16 @@ Four things, zero runtime logic beyond validation:
|
|
|
13
13
|
| `src/table.ts` | the transition table **as data**: per state, its legal transition set, the states any call from it may return, and `terminal: boolean` |
|
|
14
14
|
| `src/assert.ts` + `src/fixtures/` | `assertFiatStepResponse` · `assertLegalEmission` · `assertLegalReturn` · `walkTable` · fixture loader + 18 recorded step responses (inlined as data — no filesystem, so React Native can bundle it) |
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Six entry points — production vs test-time
|
|
17
17
|
|
|
18
18
|
| Import | Weight | Contains | Used by |
|
|
19
19
|
| --- | --- | --- | --- |
|
|
20
20
|
| `@fun-xyz/fiat-contract/types` | **0.1 KB** (types erase) | every type; no runtime values | production, both repos |
|
|
21
21
|
| `@fun-xyz/fiat-contract/table` | **13.8 KB**, zero deps | `TRANSITION_TABLE`, `stateKey`, `tableEntry`, `isTerminal`, `walkTable`, `TABLE_VERSION`, `TERMINAL_ORDER_STATUSES`, `DOCUMENTED_ENDPOINTS`, `UNOFFERED_ENDPOINTS`, `UNREACHABLE_STATES`, `unofferedEndpoints`, `unreachableStates` | **production frontend** + backend |
|
|
22
|
-
| `@fun-xyz/fiat-contract/moonpay` | zero deps | the MoonPay
|
|
22
|
+
| `@fun-xyz/fiat-contract/moonpay` | zero deps | the MoonPay flow on `POST /fiat/next`: action requests, prompts, responses, fault types, and `MOONPAY_ENDPOINTS` | **production frontend** (MoonPay adapter) + backend |
|
|
23
23
|
| `@fun-xyz/fiat-contract/terms` | zero deps | the platform terms route: `TermsDocument`, `FiatTermsRequest`, `FiatTermsResponse`, fault types, and `FIAT_TERMS_ENDPOINT` | **production frontend** + backend |
|
|
24
|
-
| `@fun-xyz/fiat-contract` |
|
|
24
|
+
| `@fun-xyz/fiat-contract/routing` | zero deps | the routing table and routing quote: `FiatRoutingTableQuery`, `FiatRoutingTableResponse`, `FiatRoutingQuoteRequest`, both routes' error bodies, and `ROUTING_ENDPOINTS` | **production frontend** + backend |
|
|
25
|
+
| `@fun-xyz/fiat-contract` | 47.6 KB, needs zod | the above + 77 zod schemas + assertions + 18 fixtures | tests, and backend dev/test guards |
|
|
25
26
|
|
|
26
27
|
`./table` is not a micro-optimisation. Terminality is table data a **shipped** client must read
|
|
27
28
|
(litmus rule 3 — clients never infer it), and Metro has no cross-module tree-shaking on by default,
|
|
@@ -101,7 +102,7 @@ A provider can run its own flow instead of the shared step machine. `POST /fiat/
|
|
|
101
102
|
and prices. When the request's `handoffs` lists the winning provider, the response carries
|
|
102
103
|
`handoff: { provider }` and no transitions. The client shows that provider's `CHECKOUT` documents
|
|
103
104
|
from `PROVIDER_CHECKOUT_DOCUMENTS`, records them with `POST /fiat/terms` on Continue, and then calls
|
|
104
|
-
`POST /fiat/
|
|
105
|
+
`POST /fiat/next`. The schema refuses a handoff on any state but `QUOTE`, a
|
|
105
106
|
handoff to a provider other than the response's own, and a handoff that still offers transitions.
|
|
106
107
|
|
|
107
108
|
**Terms (`./terms`).** Every provider records acceptances through the platform `POST /fiat/terms`
|
|
@@ -120,48 +121,86 @@ text. `PROVIDER_CHECKOUT_DOCUMENTS` maps each provider to its current `CHECKOUT`
|
|
|
120
121
|
stays valid: the backend refuses only an unknown version, a wrong digest, or an `acceptedAt` older
|
|
121
122
|
than 5 minutes. A repeat of a version the login already holds writes nothing and returns the ids.
|
|
122
123
|
|
|
123
|
-
**
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
`
|
|
133
|
-
|
|
134
|
-
|
|
124
|
+
**Routing table (`./routing`).** `ROUTING_ENDPOINTS.table` (`GET /fiat/routing/table`) answers,
|
|
125
|
+
for each requested method, every amount interval and its providers best first, each with `next`
|
|
126
|
+
(`READY`, `AUTH`, `DECLARATIVE_KYC`, `DOCUMENTARY_KYC`, `PENDING_REVIEW`) and `visibility`. The
|
|
127
|
+
client shows min, max, provider and the Continue label for an amount with no call. A method with no
|
|
128
|
+
interval may carry `unavailableReason`, which is not the step response's `BlockedReason`:
|
|
129
|
+
`USER_LIMIT`, `KYC_REJECTED`, `PROVIDER_OUTAGE`, or `NOT_SERVED` when every provider was ruled out
|
|
130
|
+
for reasons that depend on neither the buyer nor provider health (it does not offer the corridor,
|
|
131
|
+
currency, method or asset, or config has not enabled it). An empty method carries no reason when the
|
|
132
|
+
cause is unknown. Refetch the table at `expiresAt`, on `refreshLimits: true`, and after auth, KYC or
|
|
133
|
+
a `REQUOTE`. The schemas hold the table's invariants: each method once, sorted, non-overlapping
|
|
134
|
+
intervals, at least one provider each and none twice, and a reason only on an empty method.
|
|
135
|
+
|
|
136
|
+
`ROUTING_ENDPOINTS.quote` (`POST /fiat/routing/quote`) takes the `POST /fiat/quote` body plus the
|
|
137
|
+
interval's first `provider`, never `routingOverrides`, and answers the same `FiatStepResponse`. A
|
|
138
|
+
refusal is a `FiatRoutingQuoteErrorBody`, and it decides whether to quote the interval's next
|
|
139
|
+
provider:
|
|
140
|
+
|
|
141
|
+
| Status | `errorCode` | Then |
|
|
135
142
|
| --- | --- | --- |
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
`
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
143
|
+
| 404 | `FiatNoRouteError`, `reason` `NO_CANDIDATE`, `PROVIDER_DECLINED`, `PROVIDER_OUTAGE` or `KYC_REJECTED` | quote the next provider |
|
|
144
|
+
| 400 | `FiatNoRouteError`, `reason: USER_LIMIT`, `code: LIMITS_EXCEEDED`, `recovery: REQUOTE` | quote the next provider |
|
|
145
|
+
| 400 | `FiatAmountBelowMinimumError` (`minFiatAmount`) or `FiatAmountAboveMaximumError` (`maxFiatAmount`) | quote the next provider |
|
|
146
|
+
| 400 | `InvalidParameterError`: a bad request, an asset Fun cannot deliver, or no verified email | stop |
|
|
147
|
+
| 401, 5xx | any other: a missing or invalid identity assertion, or a server error | stop |
|
|
148
|
+
|
|
149
|
+
Every fall-through refusal is the named provider's alone, because the route prices only that
|
|
150
|
+
provider: `KYC_REJECTED` there means that provider refused the buyer's identity, not every provider.
|
|
151
|
+
With no provider left in the interval, refetch the table. The table route refuses with a
|
|
152
|
+
`FiatRoutingTableErrorBody`: a 429 `TooManyRequestsError` with `retryAfterSeconds` when the
|
|
153
|
+
per-login budget is spent, or a 400 or 5xx as above. The quote route has no rate limit of its own.
|
|
154
|
+
|
|
155
|
+
Both routes take geo under the `POST /fiat/quote` rules: a known `CountryCode`, a `UsStateCode`
|
|
156
|
+
region for a US buyer, and an empty `region` or `ip` read as absent. The quote route's `geo` is the
|
|
157
|
+
Fun geo endpoint's response forwarded as is. The request schemas are strict like every schema here,
|
|
158
|
+
so a client sends only the declared keys and no blank strings. `provider` is one of the five
|
|
159
|
+
providers the backend routes (`RoutingProvider`). The quote route is temporary: `POST /fiat/next`
|
|
160
|
+
replaces it for each provider that moves there.
|
|
161
|
+
|
|
162
|
+
**MoonPay (`./moonpay`).** MoonPay runs on the shared `POST /fiat/next` (`MOONPAY_ENDPOINTS.next`).
|
|
163
|
+
Every `/next` body is a `FiatNextRequest`: `provider`, `geo` (a `FiatGeo`, the geo endpoint's
|
|
164
|
+
response), and the provider's own request, which names its `action`. The orchestrator reads
|
|
165
|
+
`provider` and `geo`; the provider's action schema reads the rest. MoonPay's body is a
|
|
166
|
+
`MoonPayNextRequest`, and every MoonPay request also carries the context `checkout` and, after
|
|
167
|
+
`connect`, `credentials` (`customerId`, `accessToken`).
|
|
168
|
+
|
|
169
|
+
Each `MoonPayAction` has one name. A response's prompt names it in `next` and carries what the
|
|
170
|
+
client needs; the client's request names it in `action` and carries the result:
|
|
171
|
+
|
|
172
|
+
| Action | Prompt carries | Request carries | A repeat |
|
|
173
|
+
| --- | --- | --- | --- |
|
|
174
|
+
| `observe` | no prompt: the answer is the current prompt | nothing | writes nothing |
|
|
175
|
+
| `consent` | `documents` (at least one, unique ids) | `acceptances`, the `POST /fiat/terms` items | writes nothing new |
|
|
176
|
+
| `connect` | `sessionToken`, `quoteInput`, `resetConnection?` (`true` when the device's MoonPay connection is another buyer's) | `customerId`, `accessToken` | rebinds the same customer |
|
|
177
|
+
| `kyc` | `fields`, `fieldErrors?` (each naming a field in `fields`, with `code?` `PHONE_IN_USE` on `PHONE_NUMBER` or `TAX_ID_IN_USE` on `TAX_IDENTIFIER` when another MoonPay customer holds the value) | `credentials`, `values` keyed by `KycFieldId` (no form id) | drops values MoonPay no longer asks for |
|
|
178
|
+
| `wait` | `retryAfterMs` (at most 60 s) | no request: the client sends `observe` after the delay | |
|
|
179
|
+
| `order` | `orderId`, `quoteInput` | `orderId`, `quotedAmount`, `transactionId?` | returns the recorded order |
|
|
180
|
+
|
|
181
|
+
Every response is the next prompt, `done`, or `refused` (`MoonPayRefused`), and
|
|
182
|
+
`MoonPayResponseFor` maps each request action to its union. `observe`, `consent`, `connect` and `kyc`
|
|
183
|
+
answer a `MoonPayStepResponse`: any `MoonPayPrompt`, or `refused` with `reason` (`NOT_ELIGIBLE`,
|
|
184
|
+
`DOCUMENTS_REQUIRED`, `FINAL_REJECTION`, `CUSTOMER_MISMATCH`) and `recovery: REQUOTE | NONE`.
|
|
185
|
+
|
|
186
|
+
`order` runs after every charge attempt, a failed one too, and answers a `MoonPayOrderResponse`:
|
|
187
|
+
`done` with `order`, the `GET /fiat/orders/:orderId` response; `order` again while MoonPay does not
|
|
188
|
+
list the transaction yet; or `refused` with `reason` (`CUSTOMER_MISMATCH`, `WALLET_MISMATCH`,
|
|
189
|
+
`UNRECORDABLE`) and `recovery: NONE`. `transactionId` is optional because a failed charge may have
|
|
190
|
+
none; without it the backend records the failed charges MoonPay lists for `orderId`. `quotedAmount`
|
|
191
|
+
is the fiat amount the device quote showed. The order records the body's `geo.alpha2` as its country.
|
|
192
|
+
A failed charge answers `done` with the order in `FAILED`, and the client requotes for a new `orderId`.
|
|
193
|
+
|
|
194
|
+
A first-time buyer goes `observe` → `consent` → `connect` → `kyc` → `wait` → `observe` → `order` →
|
|
195
|
+
`done`; a returning buyer `observe` → `connect` → `order` → `done`. Both walks are recorded in
|
|
196
|
+
`test/fixtures/moonpay-next/`.
|
|
158
197
|
|
|
159
198
|
A fault is a 4xx or 5xx with a `MoonPayErrorBody`: the shared `errorCode`, `errorMsg` and `reqId`,
|
|
160
199
|
plus `reason` (`SESSION_BUDGET_SPENT`, `RATE_LIMITED`, `PROVIDER_UNAVAILABLE`, `INVALID_REQUEST`)
|
|
161
200
|
and `recovery`:
|
|
162
|
-
`RETRY` (send the same request again), `REQUOTE`, or `NONE`. A field error is a `
|
|
163
|
-
|
|
164
|
-
are exported from the root only.
|
|
201
|
+
`RETRY` (send the same request again), `REQUOTE`, or `NONE`. A field error is a `kyc`
|
|
202
|
+
prompt, not a fault. The zod schemas (`MoonPay*Schema`, `FiatGeoSchema`, `FiatNextRequestSchema`,
|
|
203
|
+
`TermsDocumentSchema`, `FiatTerms*Schema`) are exported from the root only.
|
|
165
204
|
|
|
166
205
|
## KYC SDK hints, results, and client delivery
|
|
167
206
|
|
|
@@ -420,7 +459,7 @@ loadFixture('screen-10-kyc-on-hold'); // raw JSON, fresh deep copy, `unknown`
|
|
|
420
459
|
|
|
421
460
|
### Use a schema directly
|
|
422
461
|
|
|
423
|
-
All
|
|
462
|
+
All 77 schemas are exported when you need to validate a fragment rather than a whole step response.
|
|
424
463
|
They are typed `z.ZodType<T>`, so you get `.parse` / `.safeParse` / `.optional()` — not `.shape` or
|
|
425
464
|
`.extend`, deliberately.
|
|
426
465
|
|
|
@@ -513,8 +552,8 @@ Dual CJS + ESM, same shape as `@funkit/fun-relay`, because the two consumers loa
|
|
|
513
552
|
| --- | --- | --- |
|
|
514
553
|
| `require()` must work | `fun-backend/apps/api-server` compiles `module: commonjs` and runs plain node — no bundler | esbuild emits `dist/index.js` (CJS) + `dist/index.mjs` (ESM); `exports` maps `require`/`import`; no `"type": "module"` |
|
|
515
554
|
| Zero Node builtins | `connect-core` is React Native — Metro cannot resolve `node:fs` | fixtures are inlined as generated TS data (`src/fixtures/data.ts`), so nothing touches the filesystem; `platform: browser`, every bare import external |
|
|
516
|
-
| Subpath imports must resolve without `exports` support | Metro only reads `package.json#exports` from RN 0.79 on, and `@funkit/connect-rn` accepts `react-native: >=0.74` | root compat stubs `table.js` / `types.js` / `moonpay.js` / `terms.js` (+ `.d.ts`) that re-export `dist/`. Without them Metro fails with *"Unable to resolve module"* — verified, not theorised. Resolvers that do read `exports` never see the stubs |
|
|
517
|
-
| Production code must not pull zod or fixtures | `connect-core` ships to React Native, where Metro does not tree-shake unused exports by default |
|
|
555
|
+
| Subpath imports must resolve without `exports` support | Metro only reads `package.json#exports` from RN 0.79 on, and `@funkit/connect-rn` accepts `react-native: >=0.74` | root compat stubs `table.js` / `types.js` / `moonpay.js` / `terms.js` / `routing.js` (+ `.d.ts`) that re-export `dist/`. Without them Metro fails with *"Unable to resolve module"* — verified, not theorised. Resolvers that do read `exports` never see the stubs |
|
|
556
|
+
| Production code must not pull zod or fixtures | `connect-core` ships to React Native, where Metro does not tree-shake unused exports by default | six entry points: `./types` (erased), `./table` (13.8 KB, zero deps), `./moonpay`, `./terms` and `./routing` (zero deps), root (schemas + fixtures, test-time). CI asserts `require('.../table')`, `require('.../moonpay')`, `require('.../terms')` and `require('.../routing')` never load zod |
|
|
518
557
|
| `.d.ts` must not lock a zod major | Both repos run `skipLibCheck: true`, which turns a broken declaration into a silent `any` | every exported schema is annotated `z.ZodType<T>`, so declarations name only `z.ZodType`; the structural drift checks stay module-private |
|
|
519
558
|
|
|
520
559
|
Declarations come from `tsc --emitDeclarationOnly`; esbuild only emits JS. The fixture `.json` files
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/routing.ts
|
|
2
|
+
var ROUTING_ENDPOINTS = {
|
|
3
|
+
table: "GET /fiat/routing/table",
|
|
4
|
+
/** Temporary: `POST /fiat/next` replaces it for each provider that moves there. */
|
|
5
|
+
quote: "POST /fiat/routing/quote"
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
ROUTING_ENDPOINTS
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=chunk-5UEPWL7B.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// package.json
|
|
2
|
-
var version = "0.
|
|
2
|
+
var version = "0.24.0";
|
|
3
3
|
|
|
4
4
|
// src/table.ts
|
|
5
5
|
var TABLE_VERSION = version;
|
|
@@ -514,4 +514,4 @@ export {
|
|
|
514
514
|
tableEntry,
|
|
515
515
|
isTerminal
|
|
516
516
|
};
|
|
517
|
-
//# sourceMappingURL=chunk-
|
|
517
|
+
//# sourceMappingURL=chunk-IPOPANXS.mjs.map
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from './schemas';
|
|
|
11
11
|
export * from './table';
|
|
12
12
|
export * from './assert';
|
|
13
13
|
export * from './terms';
|
|
14
|
+
export * from './routing';
|
|
14
15
|
export * from './providers/moonpay';
|
|
15
16
|
export * from './providers/moonpay.schemas';
|
|
16
17
|
export { FIXTURES, FIXTURE_COVERAGE_GAPS, fixtureMeta, loadFixture, loadFixtures, type FixtureMeta, type FixtureSource, type LoadedFixture, } from './fixtures/index';
|