seatlayer 0.1.0__tar.gz
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.
- seatlayer-0.1.0/.gitignore +9 -0
- seatlayer-0.1.0/CHANGELOG.md +27 -0
- seatlayer-0.1.0/LICENSE +21 -0
- seatlayer-0.1.0/PKG-INFO +337 -0
- seatlayer-0.1.0/README.md +303 -0
- seatlayer-0.1.0/RELEASE.md +192 -0
- seatlayer-0.1.0/pyproject.toml +74 -0
- seatlayer-0.1.0/src/seatlayer/__init__.py +32 -0
- seatlayer-0.1.0/src/seatlayer/client.py +51 -0
- seatlayer-0.1.0/src/seatlayer/errors.py +104 -0
- seatlayer-0.1.0/src/seatlayer/http.py +213 -0
- seatlayer-0.1.0/src/seatlayer/py.typed +0 -0
- seatlayer-0.1.0/src/seatlayer/resources.py +541 -0
- seatlayer-0.1.0/src/seatlayer/webhooks.py +61 -0
- seatlayer-0.1.0/tests/test_client.py +249 -0
- seatlayer-0.1.0/tests/test_webhooks.py +71 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-08-04
|
|
4
|
+
|
|
5
|
+
First public release: PyPI `seatlayer`.
|
|
6
|
+
|
|
7
|
+
Initial contents of the SeatLayer Python server SDK.
|
|
8
|
+
|
|
9
|
+
- `inventory.extend_hold` — keep a server-side hold alive past the checkout window.
|
|
10
|
+
- `charts.list` / `events.list` take `limit` and `cursor`; `list_all()` pages transparently as a
|
|
11
|
+
generator and skips the per-event availability fanout.
|
|
12
|
+
- `SeatLayer` client with secret-key auth, per-attempt timeouts, and a typed escape hatch.
|
|
13
|
+
- Resources: `charts`, `events`, `inventory`, `sessions`, `webhooks`, `workspaces`.
|
|
14
|
+
- Automatic `Idempotency-Key` on every mutation, reused across retries so a retried
|
|
15
|
+
booking cannot become two bookings.
|
|
16
|
+
- Retries on 429/408/5xx with exponential backoff and full jitter; honours `Retry-After`.
|
|
17
|
+
4xx is never retried.
|
|
18
|
+
- Typed errors: `SeatLayerAuthError` (with `is_mode_mismatch`), `SeatLayerConflictError`
|
|
19
|
+
(with `conflicts` and `is_sold_out`), `SeatLayerRateLimitError`, `SeatLayerValidationError`,
|
|
20
|
+
`SeatLayerNotFoundError`, `SeatLayerConnectionError`.
|
|
21
|
+
- `verify_webhook` — raw-body HMAC-SHA256 verification via `hmac.compare_digest`.
|
|
22
|
+
- `create_manage_session` requires explicit `capabilities`; the API's default grants
|
|
23
|
+
`event:cancel`, which unbooks paid seats and authorises gateway refunds.
|
|
24
|
+
- Constructor rejects a `pk_` key by name rather than failing as a 401 later.
|
|
25
|
+
- Ships a PEP 561 `py.typed` marker, so the annotations are visible to mypy and pyright in
|
|
26
|
+
your own project rather than only used internally.
|
|
27
|
+
- Zero runtime dependencies; standard library only.
|
seatlayer-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SeatLayer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
seatlayer-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: seatlayer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python server SDK for the SeatLayer reserved-seating API.
|
|
5
|
+
Project-URL: Homepage, https://seatlayer.io
|
|
6
|
+
Project-URL: Documentation, https://docs.seatlayer.io/server-sdk/install/
|
|
7
|
+
Project-URL: Changelog, https://github.com/seatlayer/seatlayer-python/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Source, https://github.com/seatlayer/seatlayer-python
|
|
9
|
+
Project-URL: Issues, https://github.com/seatlayer/seatlayer-python/issues
|
|
10
|
+
Author-email: SeatLayer <hello@seatlayer.io>
|
|
11
|
+
Maintainer-email: SeatLayer <hello@seatlayer.io>
|
|
12
|
+
License-Expression: MIT
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Keywords: box-office,reserved-seating,seating-chart,seatlayer,ticketing
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Office/Business
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.10
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# SeatLayer Python SDK
|
|
36
|
+
|
|
37
|
+
Official Python server SDK for the [SeatLayer](https://seatlayer.io) reserved-seating API.
|
|
38
|
+
|
|
39
|
+
> **Server-side only.** This package authenticates with your secret key. Never run it anywhere a
|
|
40
|
+
> ticket buyer can reach — browser surfaces get short-lived, origin-bound tokens that you mint here.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install seatlayer
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Requires Python 3.10 or newer. No runtime dependencies.
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import os
|
|
54
|
+
from seatlayer import SeatLayer
|
|
55
|
+
|
|
56
|
+
seatlayer = SeatLayer(os.environ["SEATLAYER_SECRET_KEY"])
|
|
57
|
+
|
|
58
|
+
# 1. Provision a venue for a new organiser from one of your templates.
|
|
59
|
+
chart = seatlayer.charts.copy("c_template_arena")["meta"]
|
|
60
|
+
seatlayer.charts.publish(chart["id"])
|
|
61
|
+
|
|
62
|
+
# 2. Create an event on it.
|
|
63
|
+
event = seatlayer.events.create(chart_id=chart["id"], name="Spring Gala")["meta"]
|
|
64
|
+
|
|
65
|
+
# 3. Sell four seats over the phone.
|
|
66
|
+
held = seatlayer.inventory.hold_best_available(event["key"], qty=4)
|
|
67
|
+
# … take payment against held["items"], which carry authoritative prices …
|
|
68
|
+
seatlayer.inventory.book(event["key"], hold_id=held["holdId"], booking_ref="order-8842")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Test vs live
|
|
72
|
+
|
|
73
|
+
Keys carry their own mode. `sk_test_…` keys can only touch test-mode events and `sk_live_…` only
|
|
74
|
+
live ones; crossing them returns `403 mode_mismatch`, surfaced as `SeatLayerAuthError` with
|
|
75
|
+
`is_mode_mismatch`.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
seatlayer = SeatLayer(os.environ["SEATLAYER_SECRET_KEY"])
|
|
79
|
+
if os.environ.get("ENV") == "production" and seatlayer.mode != "live":
|
|
80
|
+
raise RuntimeError("Refusing to boot production against test-mode seating data.")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## The two selling flows
|
|
84
|
+
|
|
85
|
+
**Buyer picks seats in the browser.** Your frontend holds them; your backend confirms the price and
|
|
86
|
+
books. Never price from what the browser sent you — `retrieve_hold` is authoritative.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
hold = seatlayer.inventory.retrieve_hold(event_key, hold_id)
|
|
90
|
+
total = sum(item["unitPrice"] for item in hold["items"])
|
|
91
|
+
# … charge `total` in hold["currency"] …
|
|
92
|
+
seatlayer.inventory.book(event_key, hold_id=hold_id, booking_ref=charge.id)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Your backend picks the seats.** Phone orders, box office, comps.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# Payment already taken — book outright, so nothing is stranded if a second call fails.
|
|
99
|
+
seatlayer.inventory.book_best_available(event_key, qty=2, booking_ref="phone-1183")
|
|
100
|
+
|
|
101
|
+
# Or name the seats yourself.
|
|
102
|
+
seatlayer.inventory.box_office_book(event_key, labels=["A-1", "A-2"], booking_ref="comp-14")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Listing and pagination
|
|
106
|
+
|
|
107
|
+
`list()` returns one page plus a `nextCursor`. When you want everything, `list_all()` pages for you
|
|
108
|
+
and yields as it goes — a generator rather than a list, because the point of paginating is to *not*
|
|
109
|
+
hold an unbounded result set in memory.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
# One page, your own paging.
|
|
113
|
+
page = seatlayer.events.list(limit=50)
|
|
114
|
+
page["events"]
|
|
115
|
+
page.get("nextCursor") # absent once exhausted
|
|
116
|
+
|
|
117
|
+
# Or let the SDK walk it.
|
|
118
|
+
for event in seatlayer.events.list_all():
|
|
119
|
+
sync(event)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Listing events includes live availability `counts` by default, which costs the server one
|
|
123
|
+
round-trip **per event**. `list_all()` turns them off automatically — walking a whole catalogue is
|
|
124
|
+
exactly when you don't want that — and you can control it explicitly:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
seatlayer.events.list(limit=50, counts=False)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Keeping a hold alive
|
|
131
|
+
|
|
132
|
+
When an order takes longer than the checkout window — an invoice, a phone sale — extend rather than
|
|
133
|
+
release and re-hold. Releasing first hands the seats to whoever is racing for them in between.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from seatlayer import SeatLayerConflictError
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
seatlayer.inventory.extend_hold(event_key, hold_id, ttl_ms=10 * 60_000)
|
|
140
|
+
except SeatLayerConflictError:
|
|
141
|
+
# Gone, expired, or at its renewal cap — the buyer has to re-pick.
|
|
142
|
+
...
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Embedding the control room
|
|
146
|
+
|
|
147
|
+
Your secret key never reaches a browser. Mint a scoped token instead.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
session = seatlayer.sessions.create_manage_session(
|
|
151
|
+
event_key,
|
|
152
|
+
allowed_origin="https://box-office.yourplatform.com",
|
|
153
|
+
capabilities=["event:view", "event:block"],
|
|
154
|
+
expires_in_seconds=3600,
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`capabilities` is **required** by this SDK even though the API defaults it. Omit it at the API level
|
|
159
|
+
and you get `event:view`, `event:block`, `event:cancel` and `event:reports` — including
|
|
160
|
+
`event:cancel`, which unbooks paid seats **and authorises refunds against the organiser's connected
|
|
161
|
+
payment gateway**. That is real money, moved by a token you handed to a browser; it should not
|
|
162
|
+
arrive by forgetting an argument. Grant the smallest set the page needs.
|
|
163
|
+
|
|
164
|
+
The full set, all opt-in:
|
|
165
|
+
|
|
166
|
+
| Capability | Grants |
|
|
167
|
+
|---|---|
|
|
168
|
+
| `event:view` | Read the seat map and its live states |
|
|
169
|
+
| `event:block` | Block and unblock seats |
|
|
170
|
+
| `event:cancel` | Unbook paid seats and issue gateway refunds — destructive, moves money |
|
|
171
|
+
| `event:reports` | Read sales and availability reports |
|
|
172
|
+
| `event:channels:view` | Read sales channels and their allocations |
|
|
173
|
+
| `event:channels:manage` | Create, pause and archive channels; rotate access links |
|
|
174
|
+
|
|
175
|
+
The two `event:channels:*` capabilities are **not** in the default — a token minted before sales
|
|
176
|
+
channels existed must not silently acquire channel authority — so ask for them explicitly if the
|
|
177
|
+
page manages channels.
|
|
178
|
+
|
|
179
|
+
The same pattern embeds the Designer in your own UI:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
chart = seatlayer.charts.create(name="Riverside Theatre")["meta"]
|
|
183
|
+
designer = seatlayer.sessions.create_designer_session(
|
|
184
|
+
workspace_id=workspace_id,
|
|
185
|
+
chart_id=chart["id"],
|
|
186
|
+
allowed_origin="https://app.yourplatform.com",
|
|
187
|
+
authority="edit",
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Webhooks
|
|
192
|
+
|
|
193
|
+
Verify every delivery against the **raw** body. Re-serialising it changes the bytes and
|
|
194
|
+
verification will fail.
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from flask import request
|
|
198
|
+
from seatlayer import verify_webhook, WebhookVerificationError
|
|
199
|
+
|
|
200
|
+
@app.post("/webhooks/seatlayer")
|
|
201
|
+
def seatlayer_webhook():
|
|
202
|
+
try:
|
|
203
|
+
event = verify_webhook(
|
|
204
|
+
request.get_data(), # raw bytes, not request.json
|
|
205
|
+
request.headers.get("X-SeatLayer-Signature"),
|
|
206
|
+
os.environ["SEATLAYER_WEBHOOK_SECRET"],
|
|
207
|
+
)
|
|
208
|
+
except WebhookVerificationError:
|
|
209
|
+
return "", 400
|
|
210
|
+
|
|
211
|
+
# The signed body carries `at`, but nothing enforces a freshness window, so
|
|
212
|
+
# a captured delivery stays valid indefinitely. Deduplicate on occurrenceId —
|
|
213
|
+
# this is your replay protection, not an optimisation.
|
|
214
|
+
if already_processed(event["occurrenceId"]):
|
|
215
|
+
return "", 200
|
|
216
|
+
|
|
217
|
+
handle(event)
|
|
218
|
+
return "", 200
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Errors
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from seatlayer import SeatLayerAuthError, SeatLayerConflictError, SeatLayerRateLimitError
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
seatlayer.inventory.hold_best_available(event_key, qty=6)
|
|
228
|
+
except SeatLayerConflictError as error:
|
|
229
|
+
if error.is_sold_out:
|
|
230
|
+
return show_alternative_dates() # a business outcome, not a bug
|
|
231
|
+
raise
|
|
232
|
+
except SeatLayerRateLimitError as error:
|
|
233
|
+
return retry_after(error.retry_after_seconds)
|
|
234
|
+
except SeatLayerAuthError as error:
|
|
235
|
+
if error.is_mode_mismatch:
|
|
236
|
+
raise RuntimeError("Test key pointed at a live event (or the reverse.)") from error
|
|
237
|
+
raise
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Every error carries `status`, `code`, `body`, and `request_id` — quote the request id in support
|
|
241
|
+
requests.
|
|
242
|
+
|
|
243
|
+
## Reliability
|
|
244
|
+
|
|
245
|
+
**Retries.** 429, 408 and 5xx are retried with exponential backoff and full jitter; `Retry-After`
|
|
246
|
+
wins when the server sends it. 4xx is never retried — it will not start succeeding.
|
|
247
|
+
|
|
248
|
+
**Idempotency.** Every mutating request carries an `Idempotency-Key`, generated if you do not supply
|
|
249
|
+
one, and **reused across retries** so a retried booking cannot become two bookings. Pass your own
|
|
250
|
+
order id for end-to-end deduplication:
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
seatlayer.inventory.book(event_key, hold_id=hold_id, idempotency_key=f"order-{order_id}")
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
SeatLayer(
|
|
258
|
+
os.environ["SEATLAYER_SECRET_KEY"],
|
|
259
|
+
max_retries=3, # total attempts
|
|
260
|
+
timeout=30.0, # seconds, per attempt
|
|
261
|
+
)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Escape hatch
|
|
265
|
+
|
|
266
|
+
For surface this SDK does not wrap yet — same auth, retries, idempotency and error mapping:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
seatlayer.request("POST", "/v1/events/ev_1/some-new-route", body={...})
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## API surface
|
|
273
|
+
|
|
274
|
+
| Resource | Methods |
|
|
275
|
+
| --- | --- |
|
|
276
|
+
| `charts` | `list` `list_all` `create` `retrieve` `update` `delete` `copy` `archive` `unarchive` `publish` |
|
|
277
|
+
| `events` | `list` `list_all` `create` `retrieve` `update` `delete` `update_chart` `close` `reopen` `archive` `retrieve_hold_ttl` `update_hold_ttl` `retrieve_report` `retrieve_log` |
|
|
278
|
+
| `inventory` | `hold` `hold_best_available` `book_best_available` `extend_hold` `retrieve_hold` `release` `book` `box_office_book` `unbook` `block` `unblock` `unblock_all` `retrieve_availability` `update_availability` |
|
|
279
|
+
| `sessions` | `create_manage_session` `revoke_manage_session` `create_designer_session` `revoke_designer_session` |
|
|
280
|
+
| `webhooks` | `list` `create` `update` `delete` `list_deliveries` |
|
|
281
|
+
| `workspaces` | `list` `create` `retrieve` `update` |
|
|
282
|
+
|
|
283
|
+
Full reference: [docs.seatlayer.io/server-api](https://docs.seatlayer.io/server-api/)
|
|
284
|
+
|
|
285
|
+
### Deliberately not in this SDK
|
|
286
|
+
|
|
287
|
+
Some API surface is intentionally unwrapped, not merely pending:
|
|
288
|
+
|
|
289
|
+
- **Hosted-checkout orders and refunds.** Reading or refunding a SeatLayer-hosted-checkout sale is
|
|
290
|
+
not a server-SDK capability. Those records only exist for organisations using hosted checkout; if
|
|
291
|
+
you run your own commerce store you refund in that store, through your own gateway.
|
|
292
|
+
- **Connecting or assigning payment gateways.** Connecting one is a dashboard flow, so shipping only
|
|
293
|
+
the assignment half across seven SDKs would hand you a method that cannot yet succeed.
|
|
294
|
+
- **Realtime seat updates.** Live seat state reaches the *browser* through the widget's own socket.
|
|
295
|
+
There is no server-side subscribe; a secret-key caller gets authoritative state from
|
|
296
|
+
`events.retrieve_report()` and `inventory.retrieve_availability()`.
|
|
297
|
+
|
|
298
|
+
None of these are reachable through `request()` as a supported path either — they are excluded from
|
|
299
|
+
the public manifest, not just from the wrapper.
|
|
300
|
+
|
|
301
|
+
## Related resources
|
|
302
|
+
|
|
303
|
+
- [Server SDK guide](https://docs.seatlayer.io/server-sdk/install/)
|
|
304
|
+
- [Errors, retries and idempotency](https://docs.seatlayer.io/server-sdk/reliability/)
|
|
305
|
+
- [Webhook verification](https://docs.seatlayer.io/server-sdk/webhooks/)
|
|
306
|
+
- [Server API reference](https://docs.seatlayer.io/server-api/events/)
|
|
307
|
+
- [OpenAPI description](https://docs.seatlayer.io/openapi.json)
|
|
308
|
+
- [Agent-readable documentation](https://docs.seatlayer.io/llms.txt)
|
|
309
|
+
- [SeatLayer GitHub organization](https://github.com/seatlayer)
|
|
310
|
+
|
|
311
|
+
### Other SeatLayer SDKs
|
|
312
|
+
|
|
313
|
+
| Surface | Package |
|
|
314
|
+
|---|---|
|
|
315
|
+
| Browser (vanilla) | [`@seatlayer/js`](https://github.com/seatlayer/seatlayer-sdk) |
|
|
316
|
+
| React | [`@seatlayer/react`](https://github.com/seatlayer/seatlayer-sdk) |
|
|
317
|
+
| React Native | [`@seatlayer/react-native`](https://github.com/seatlayer/seatlayer-react-native) |
|
|
318
|
+
| iOS | [`seatlayer-ios`](https://github.com/seatlayer/seatlayer-ios) |
|
|
319
|
+
| Android | [`seatlayer-android`](https://github.com/seatlayer/seatlayer-android) |
|
|
320
|
+
| Flutter | [`seatlayer_flutter`](https://github.com/seatlayer/seatlayer-flutter) |
|
|
321
|
+
| Node.js (server) | [`@seatlayer/server`](https://github.com/seatlayer/seatlayer-node) |
|
|
322
|
+
| PHP (server) | [`seatlayer/seatlayer-php`](https://github.com/seatlayer/seatlayer-php) |
|
|
323
|
+
| Java (server) | [`io.seatlayer:seatlayer-java`](https://github.com/seatlayer/seatlayer-java) |
|
|
324
|
+
| Go (server) | [`github.com/seatlayer/seatlayer-go`](https://github.com/seatlayer/seatlayer-go) |
|
|
325
|
+
| Ruby (server) | [`seatlayer`](https://github.com/seatlayer/seatlayer-ruby) |
|
|
326
|
+
| .NET (server) | [`SeatLayer`](https://github.com/seatlayer/seatlayer-dotnet) |
|
|
327
|
+
|
|
328
|
+
## Development
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
pip install -e ".[dev]"
|
|
332
|
+
ruff check src tests && mypy && pytest -q
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## License
|
|
336
|
+
|
|
337
|
+
MIT
|