@klappay/types 1.0.0 → 1.0.1
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/CHANGELOG.md +25 -0
- package/README.md +5 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/docs/auth-and-accounts.md +188 -0
- package/docs/charges.md +303 -0
- package/docs/distributions.md +93 -0
- package/docs/errors-and-health.md +52 -0
- package/docs/getting-started.md +96 -0
- package/docs/sandbox.md +80 -0
- package/docs/tokens-and-networks.md +108 -0
- package/docs/webhooks.md +165 -0
- package/package.json +13 -7
- package/logo.png +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Tokens and networks
|
|
2
|
+
|
|
3
|
+
Exported from `tokens.ts` and `networks.ts`.
|
|
4
|
+
|
|
5
|
+
## `Token`
|
|
6
|
+
|
|
7
|
+
`TokenSchema`/`Token` — currently `'USDC' | 'USDT'`. This is today's
|
|
8
|
+
supported list, not a permanent ceiling — more tokens are expected to be
|
|
9
|
+
added over time as new networks and assets come online. Support depends
|
|
10
|
+
on both `network` and `environment`, and not every combination is
|
|
11
|
+
symmetric: `live` has both `USDC` and `USDT` on every operational
|
|
12
|
+
network *except* `bnb` (see the `USDC`/`bnb` note below), and `test`
|
|
13
|
+
coverage varies per network for different reasons — `base`, `optimism`,
|
|
14
|
+
and `ethereum` each have a `test` environment with `USDC` only (none has
|
|
15
|
+
an official Sepolia USDT, since Tether doesn't issue one), while
|
|
16
|
+
`arbitrum`, `polygon`, and `avalanche` have no `test` environment at all
|
|
17
|
+
yet, for any token (0xSplits hasn't deployed its split factory on
|
|
18
|
+
Arbitrum Sepolia, and has no Polygon or Avalanche Fuji testnet support at
|
|
19
|
+
all — a network-level gap, not a token-level one), and `bnb` has none
|
|
20
|
+
either (no BNB testnet support in 0xSplits). An unconfigured combination
|
|
21
|
+
is rejected with `422 token_not_supported`, not silently accepted. See
|
|
22
|
+
`TOKEN_ADDRESSES` below for the exact current matrix rather than
|
|
23
|
+
assuming full coverage.
|
|
24
|
+
|
|
25
|
+
`TOKEN_DECIMALS` (`6`) — both tokens Klap supports use 6 decimals on
|
|
26
|
+
every network they're deployed on. A fact about these specific tokens,
|
|
27
|
+
not a per-network setting, so it's one shared constant rather than
|
|
28
|
+
something you'd look up per-`Network`.
|
|
29
|
+
|
|
30
|
+
`TOKEN_ADDRESSES: Record<Token, Partial<Record<Network, Partial<Record<Environment, `0x${string}`>>>>>`
|
|
31
|
+
— the real on-chain contract address for each token/network/environment
|
|
32
|
+
combination that's actually deployed. Three levels deep because `test`
|
|
33
|
+
and `live` are genuinely different chains for at least one network today
|
|
34
|
+
(`base` → Base mainnet for `live`, Base Sepolia for `test` — see
|
|
35
|
+
[`charges.md`](./charges.md) for `Environment`), so the same token can
|
|
36
|
+
have a different contract address per environment, not just per network.
|
|
37
|
+
`Partial` at every level matters: index it as
|
|
38
|
+
`TOKEN_ADDRESSES[token][network]?.[environment]` and check for
|
|
39
|
+
`undefined`, since a combination that doesn't exist (e.g. no official
|
|
40
|
+
Base Sepolia USDT, or any `arbitrum` `test` entry at all) has no entry
|
|
41
|
+
rather than a placeholder value. One naming quirk on `arbitrum`: the
|
|
42
|
+
`USDT` contract there is real and Tether-backed, but reports its
|
|
43
|
+
on-chain name/`symbol()` as `"USD₮0"` (Tether's "USDT0" cross-chain
|
|
44
|
+
standard, migrated in place in Jan 2025) — this codebase always verifies
|
|
45
|
+
by address, never by `symbol()`, so it doesn't matter functionally, but
|
|
46
|
+
don't be surprised seeing "USD₮0" instead of "USDT" on a block explorer.
|
|
47
|
+
|
|
48
|
+
**`USDC` on `bnb` is Binance-Peg USDC, not a Circle deployment.** Circle
|
|
49
|
+
issues no native USDC on BNB Chain at all — it's absent from both
|
|
50
|
+
Circle's own address list and its CCTP-supported chain list. The address
|
|
51
|
+
in `TOKEN_ADDRESSES` is a Binance-custodied, 1:1-pegged BEP-20 token
|
|
52
|
+
instead (Binance locks real USDC/equivalent collateral in its own wallet
|
|
53
|
+
and mints this against it) — real liquidity and adoption (it's what
|
|
54
|
+
"USDC" means on BNB Chain in practice), but a centralized-custody trust
|
|
55
|
+
model, not the direct-issuer verification every other `USDC`/`USDT`
|
|
56
|
+
entry in this table has. See `docs/payments.md`'s note on it before
|
|
57
|
+
building anything that treats every `TOKEN_ADDRESSES` entry as
|
|
58
|
+
equally trusted. `USDT` on `bnb` is Tether's own official BEP-20
|
|
59
|
+
issuance — same trust model as everywhere else.
|
|
60
|
+
|
|
61
|
+
## `Network`
|
|
62
|
+
|
|
63
|
+
`NetworkSchema`/`Network` — `'base' | 'optimism' | 'polygon' |
|
|
64
|
+
'ethereum' | 'arbitrum' | 'avalanche' | 'bnb'`. This is the full type —
|
|
65
|
+
every one of these values is a valid `Network` for reading/filtering
|
|
66
|
+
(e.g. `ListChargesInput.network`). `solana` used to be part of this type
|
|
67
|
+
but was removed entirely (not just left non-operational) once there was
|
|
68
|
+
no near-term plan to wire it — see `docs/security.md`'s finding #13.
|
|
69
|
+
|
|
70
|
+
`OPERATIONAL_NETWORKS` (`readonly ['base', 'arbitrum', 'optimism',
|
|
71
|
+
'polygon', 'ethereum', 'avalanche', 'bnb']`) and its matching
|
|
72
|
+
`OperationalNetwork` type — the narrower list of networks actually
|
|
73
|
+
wired end-to-end today, which right now is exactly every `Network`
|
|
74
|
+
value. `CreateChargeSchema.network` enforces this at creation: `POST
|
|
75
|
+
/v1/charges` with any `network` outside this list is rejected with
|
|
76
|
+
`400 validation_error` naming the network and what's currently
|
|
77
|
+
supported, not silently accepted and left to strand funds. Not a
|
|
78
|
+
permanent ceiling and not guaranteed to stay in lockstep with
|
|
79
|
+
`Network` — the day a genuinely new chain is added to the type before
|
|
80
|
+
its wiring lands, this list will again be the narrower one. Being in
|
|
81
|
+
`OPERATIONAL_NETWORKS` means `live` works; it says nothing about `test`
|
|
82
|
+
on its own — see `Token` above for why
|
|
83
|
+
`arbitrum`/`polygon`/`avalanche`/`bnb` specifically have no `test`
|
|
84
|
+
environment yet (`optimism`/`ethereum` do).
|
|
85
|
+
|
|
86
|
+
`EVM_NETWORKS` (`readonly ['base', 'optimism', 'polygon', 'ethereum',
|
|
87
|
+
'arbitrum', 'avalanche', 'bnb']`) and its matching `EvmNetwork` type —
|
|
88
|
+
every current `Network` value (all EVM-based, now that `solana` is
|
|
89
|
+
gone). Use this to guard any logic that assumes an EVM-style
|
|
90
|
+
address/RPC — a future non-EVM network would reintroduce a real gap
|
|
91
|
+
here.
|
|
92
|
+
|
|
93
|
+
`NETWORK_LABELS: Record<Network, string>` — display names (`'Base'`,
|
|
94
|
+
`'Optimism'`, ...) for UI use.
|
|
95
|
+
|
|
96
|
+
`NETWORK_EXPLORERS: Record<Network, string>` — each network's block
|
|
97
|
+
explorer base URL (e.g. `https://basescan.org`), used to build a direct
|
|
98
|
+
transaction link (see `explorerTxUrl` on `VerifyCharge`, in
|
|
99
|
+
[`charges.md`](./charges.md)).
|
|
100
|
+
|
|
101
|
+
## See also
|
|
102
|
+
|
|
103
|
+
- [`charges.md`](./charges.md) — where `Token`/`Network` are actually
|
|
104
|
+
used (`CreateChargeInput.acceptedPayments`, `Charge.acceptedPayments`/
|
|
105
|
+
`paidWith`, `VerifyCharge`), and `GET /v1/networks`
|
|
106
|
+
(`CapabilitiesSchema`) — the live version of this same matrix, scoped
|
|
107
|
+
to your API key's `environment`, rather than the full type-level
|
|
108
|
+
listing on this page.
|
package/docs/webhooks.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Webhooks
|
|
2
|
+
|
|
3
|
+
Everything here is exported from `@klappay/types`'s webhook-related
|
|
4
|
+
modules (`webhook-events.ts`, `webhooks.ts`, `webhook-event-data.ts`) —
|
|
5
|
+
import from the package root either way, this split is an internal
|
|
6
|
+
implementation detail.
|
|
7
|
+
|
|
8
|
+
## Event types, and the four categories
|
|
9
|
+
|
|
10
|
+
Every event Klap can send is one `WebhookEventTypeSchema` value, built
|
|
11
|
+
from four sub-enums (each independently exported, useful if you only
|
|
12
|
+
ever care about one group):
|
|
13
|
+
|
|
14
|
+
| Sub-schema | Category | Events |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `ChargeWebhookEventTypeSchema` | `payments` | `charge.created`, `charge.partially_paid`, `charge.confirmed`, `charge.expired`, `charge.underpaid`, `charge.settled`, `charge.settlement_failed`, `charge.overpaid`, `charge.paused`, `charge.reactivated`, `charge.contribution_received`, `charge.contribution_settled` |
|
|
17
|
+
| `AccountWebhookEventTypeSchema` | `account` | `payout_address.changed`, `api_key.created`, `api_key.revoked`, `webhook.created`, `webhook.deleted`, `webhook.secret_rotated`, `fee_tier.updated`, `member.removed`, `member.role_changed`, `member.invited` |
|
|
18
|
+
| `WebhookDeliveryEventTypeSchema` | `webhooks` | `webhook.delivery_failed`, `webhook.delivery_recovered`, `webhook.endpoint_unhealthy` |
|
|
19
|
+
| `SecurityWebhookEventTypeSchema` | `security` | `auth.login`, `auth.login_failed`, `auth.suspicious_activity`, `auth.email_verified`, `auth.password_reset_requested`, `auth.password_reset_completed` |
|
|
20
|
+
|
|
21
|
+
`EVENT_CATEGORY_MAP` (`Record<WebhookEventType, WebhookCategory>`) is the
|
|
22
|
+
single source of truth for which category an event belongs to —
|
|
23
|
+
generated from the four enums above, never hand-duplicated. Its inverse,
|
|
24
|
+
`WEBHOOK_EVENT_CATEGORIES` (`Record<WebhookCategory, readonly
|
|
25
|
+
WebhookEventType[]>`), gives you every event in a category, e.g. for
|
|
26
|
+
building a subscription UI.
|
|
27
|
+
|
|
28
|
+
Two events worth calling out specifically:
|
|
29
|
+
|
|
30
|
+
- **`charge.confirmed` vs. `charge.settled`** — `confirmed` means the
|
|
31
|
+
payment was *detected on-chain*; `settled` means the merchant's wallet
|
|
32
|
+
*actually received* the funds, a separate, later step (see
|
|
33
|
+
`settlementStatus` in [`charges.md`](./charges.md)). Subscribe to
|
|
34
|
+
`confirmed` if you only need "will I get paid", or `settled` if you
|
|
35
|
+
need "has the money actually arrived".
|
|
36
|
+
- **`charge.overpaid`** fires *alongside* `charge.confirmed`/
|
|
37
|
+
`charge.partially_paid` whenever the cumulative amount received ends
|
|
38
|
+
up above `amount` — it's an additional signal, not a replacement
|
|
39
|
+
status.
|
|
40
|
+
- **`charge.paused`/`charge.reactivated`/`charge.contribution_received`/
|
|
41
|
+
`charge.contribution_settled`** are the only events in this category
|
|
42
|
+
that don't carry the full `Charge` object as `data`. `paused`/
|
|
43
|
+
`reactivated` only ever fire for a charge with no `expiresAt` that's
|
|
44
|
+
gone inactive for longer than its inactivity window, carrying
|
|
45
|
+
`{ chargeId, lastActivityAt, pausedAt }` / `{ chargeId,
|
|
46
|
+
reactivatedAt }`. `contribution_received`/`contribution_settled` are
|
|
47
|
+
exclusive to `mode: 'continuous'` charges (see `mode` in
|
|
48
|
+
[`charges.md`](./charges.md)) — a continuous charge never fires
|
|
49
|
+
`charge.confirmed`/`charge.settled` at all, since `status` never
|
|
50
|
+
leaves `pending`; instead every individual transfer fires
|
|
51
|
+
`contribution_received` on detection and `contribution_settled` once
|
|
52
|
+
its payout completes, carrying `{ chargeId, token, network, amount,
|
|
53
|
+
txHash, payerAddress }` / `{ chargeId, token, network, amount,
|
|
54
|
+
txHash, distributorAddress }` — one pair-scoped event per
|
|
55
|
+
contribution instead of one event for the whole charge.
|
|
56
|
+
|
|
57
|
+
`member.invited` does not exist — there's no invite endpoint in the API
|
|
58
|
+
today.
|
|
59
|
+
|
|
60
|
+
## Subscribing — `CreateWebhookSchema`
|
|
61
|
+
|
|
62
|
+
`POST /v1/webhooks`'s body. At least one of `events` or `eventCategories`
|
|
63
|
+
is required (enforced by a `.refine()`, not just documentation):
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { CreateWebhookSchema } from '@klappay/types'
|
|
67
|
+
|
|
68
|
+
CreateWebhookSchema.parse({
|
|
69
|
+
url: 'https://example.com/webhooks/klap',
|
|
70
|
+
eventCategories: ['payments'],
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- `events` — individual event types, or `"*"` (`WEBHOOK_EVENTS_WILDCARD`)
|
|
75
|
+
for every event.
|
|
76
|
+
- `eventCategories` — subscribe to a whole category at once (see the
|
|
77
|
+
table above) — new events added to that category later arrive
|
|
78
|
+
automatically, no subscription update needed.
|
|
79
|
+
- `excludeEvents` — opt back out of specific events even under a `"*"`
|
|
80
|
+
or category subscription.
|
|
81
|
+
- `url` must be HTTPS and resolve to a public address — private/internal
|
|
82
|
+
IPs are rejected server-side.
|
|
83
|
+
|
|
84
|
+
`WebhookSchema` / `Webhook` is the response to creating one — critically,
|
|
85
|
+
`secret` (the HMAC signing secret) is only ever present in **this**
|
|
86
|
+
response. `WebhookListItemSchema` / `WebhookListItem` is what every
|
|
87
|
+
subsequent list/read returns instead: the same shape minus `secret`,
|
|
88
|
+
plus `hint` (a truncated, safe-to-display form, e.g. `whsec_...ab12`).
|
|
89
|
+
|
|
90
|
+
## The payload envelope
|
|
91
|
+
|
|
92
|
+
`WebhookPayloadSchema` / `WebhookPayload` is the wire shape of every
|
|
93
|
+
delivery: `{ id, event, createdAt, data }`. `data` is typed `unknown`
|
|
94
|
+
here on purpose — its real shape depends on `event`, which a single flat
|
|
95
|
+
schema can't express. `id` is also sent as the `X-Klap-Delivery` header,
|
|
96
|
+
distinct from the delivery-id-per-registered-webhook used internally.
|
|
97
|
+
|
|
98
|
+
For the *typed*, narrowed version, use `TypedWebhookPayload` — a
|
|
99
|
+
discriminated union over `event` (same pattern as Stripe's
|
|
100
|
+
`Event.data.object`):
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import type { TypedWebhookPayload } from '@klappay/types'
|
|
104
|
+
|
|
105
|
+
function handle(payload: TypedWebhookPayload) {
|
|
106
|
+
if (payload.event === 'charge.confirmed') {
|
|
107
|
+
payload.data.amountReceived // typed as Charge, no cast needed
|
|
108
|
+
} else if (payload.event === 'payout_address.changed') {
|
|
109
|
+
payload.data.to // typed as PayoutAddressChangedData
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`WebhookEventDataMap` is the underlying `{ event: dataShape }` mapping
|
|
115
|
+
`TypedWebhookPayload` is built from — every `charge.*` event carries a
|
|
116
|
+
full `Charge`; every other event carries a small, event-specific object
|
|
117
|
+
(all plain TypeScript types, not Zod schemas, since they're never
|
|
118
|
+
validated standalone, only as part of `TypedWebhookPayload`):
|
|
119
|
+
|
|
120
|
+
| Event(s) | `data` shape |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `payout_address.changed` | `PayoutAddressChangedData` — `{ organizationId, from, to }` (`from` nullable) |
|
|
123
|
+
| `api_key.created` / `api_key.revoked` | `ApiKeyEventData` — `{ apiKeyId, name, environment, hint }` |
|
|
124
|
+
| `webhook.created` / `webhook.deleted` / `webhook.secret_rotated` | `WebhookConfigEventData` — `{ webhookId, url }` |
|
|
125
|
+
| `webhook.delivery_failed` / `webhook.delivery_recovered` / `webhook.endpoint_unhealthy` | `WebhookHealthEventData` — `{ webhookId, url, failureRatio? }` (`failureRatio` only on `endpoint_unhealthy`) |
|
|
126
|
+
| `fee_tier.updated` | `FeeTierUpdatedData` — `{ organizationId, previousFeePercent, newFeePercent }` |
|
|
127
|
+
| `member.removed` | `MemberEventData` — `{ userId, email, role }` |
|
|
128
|
+
| `member.role_changed` | `MemberRoleChangedData` — `MemberEventData & { previousRole }` |
|
|
129
|
+
| `member.invited` | `MemberInvitedData` — `{ organizationId, email, role, invitedByUserId }` |
|
|
130
|
+
| `auth.login` | `AuthLoginData` — `{ userId, ipAddress }` |
|
|
131
|
+
| `auth.login_failed` | `AuthLoginFailedData` — `{ email, ipAddress }` |
|
|
132
|
+
| `auth.suspicious_activity` | `AuthSuspiciousActivityData` — `AuthLoginData & { previousIpAddress }` |
|
|
133
|
+
| `auth.email_verified` | `AuthEmailVerifiedData` — `{ userId, email }` |
|
|
134
|
+
| `auth.password_reset_requested` | `AuthPasswordResetRequestedData` — `{ userId, email }` |
|
|
135
|
+
| `auth.password_reset_completed` | `AuthPasswordResetCompletedData` — `{ userId, email }` |
|
|
136
|
+
|
|
137
|
+
## Delivery health
|
|
138
|
+
|
|
139
|
+
`WebhookDeliveryStatusSchema` / `WebhookDeliveryStatus` —
|
|
140
|
+
`'pending' | 'delivered' | 'failed'` (`failed` means all 5 retry
|
|
141
|
+
attempts over ~24h were exhausted; retry manually via
|
|
142
|
+
`POST /v1/webhooks/{id}/deliveries/{deliveryId}/retry`).
|
|
143
|
+
`WebhookDeliverySchema` / `WebhookDelivery` is one entry from `GET
|
|
144
|
+
/v1/webhooks/{id}/deliveries` — `responseCode: null` means every attempt
|
|
145
|
+
failed to connect at all, not just a non-2xx response.
|
|
146
|
+
`ListWebhookDeliveriesSchema`/`PaginatedWebhookDeliveriesSchema` are the
|
|
147
|
+
query/response shapes for that endpoint — same shared cursor pagination
|
|
148
|
+
as `GET /v1/charges`/`GET /v1/organizations/{id}/api-keys`/
|
|
149
|
+
`GET /v1/organizations/{id}/users`, see
|
|
150
|
+
[`charges.md`](./charges.md#the-shared-cursor-pagination-pattern). `GET
|
|
151
|
+
/v1/webhooks` itself (listing the webhooks, not their deliveries) stays
|
|
152
|
+
unpaginated — it's hard-capped at 20 active webhooks per organization.
|
|
153
|
+
|
|
154
|
+
The `webhook.*` meta-events (`WebhookDeliveryEventTypeSchema`) let you
|
|
155
|
+
monitor this without polling: `webhook.endpoint_unhealthy` fires once
|
|
156
|
+
when a webhook's trailing-24h failure rate crosses 20%,
|
|
157
|
+
`webhook.delivery_recovered` fires once when a delivery next succeeds
|
|
158
|
+
afterward.
|
|
159
|
+
|
|
160
|
+
## See also
|
|
161
|
+
|
|
162
|
+
- [`charges.md`](./charges.md) — the `Charge` shape carried by every
|
|
163
|
+
`charge.*` event's `data`.
|
|
164
|
+
- [`sandbox.md`](./sandbox.md) — `TriggerableChargeEvent`, the subset of
|
|
165
|
+
charge events you can simulate without a real on-chain transfer.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klappay/types",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "TypeScript types and Zod schemas for the Klap Core API — the request/response contracts, published separately so integrators get them without needing access to the (closed-source) API implementation.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,19 +12,25 @@
|
|
|
12
12
|
"require": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
-
"files": [
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"docs",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"README.md",
|
|
20
|
+
"CHANGELOG.md"
|
|
21
|
+
],
|
|
16
22
|
"publishConfig": {
|
|
17
23
|
"access": "public"
|
|
18
24
|
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsup",
|
|
21
|
-
"dev": "tsup --watch"
|
|
22
|
-
},
|
|
23
25
|
"dependencies": {
|
|
24
26
|
"zod": "^3.23.0"
|
|
25
27
|
},
|
|
26
28
|
"devDependencies": {
|
|
27
29
|
"tsup": "^8.3.0",
|
|
28
30
|
"typescript": "^5.6.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"dev": "tsup --watch"
|
|
29
35
|
}
|
|
30
|
-
}
|
|
36
|
+
}
|
package/logo.png
DELETED
|
Binary file
|