@0block.io/sdk 0.1.0 → 0.3.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 CHANGED
@@ -3,10 +3,17 @@
3
3
  [![npm version](https://img.shields.io/npm/v/%400block.io%2Fsdk.svg)](https://www.npmjs.com/package/@0block.io/sdk)
4
4
  [![types](https://img.shields.io/npm/types/%400block.io%2Fsdk.svg)](https://www.npmjs.com/package/@0block.io/sdk)
5
5
 
6
- **Client-side transaction builder for the 0Block DEX router.** Builds unsigned
7
- v0 swap transactions entirely offline — tenant-wrapped or direct — with shared
8
- address lookup tables, gateway tips, and on-chain fee-event decoding for
9
- accounting.
6
+ **The SDK for 0Block's two products independent, composable:**
7
+
8
+ | Product | What it does | Needs the other? |
9
+ |---|---|---|
10
+ | **DEX Smart Router** | Build swap transactions on-chain-routed across 60+ venues — through your tenant wrapper (your fee applies) or directly (0Block fee only) | No — submit through any RPC |
11
+ | **Landing Service** | Land **any** fully-signed Solana transaction: tip-gated submission forwarded to staked infrastructure and current/next leader TPUs | No — accepts any transaction |
12
+
13
+ Used together they form a complete trade pipeline: build with the router SDK,
14
+ sign externally, land through the service.
15
+
16
+ **Router highlights:**
10
17
 
11
18
  - **Zero network calls at trade time.** Market context, lookup tables, and a
12
19
  blockhash are resolved once server-side and streamed; the client assembles
@@ -14,6 +21,8 @@ accounting.
14
21
  - **One signature.** The end user is the only required signer, in both modes.
15
22
  - **Fees are on-chain, not inputs.** Tenant and platform commissions are read
16
23
  and enforced by the programs; the SDK never accepts a fee parameter.
24
+ - **SOL-native UX, program-managed.** In tenant mode the wrapper wraps/unwraps
25
+ and cleans up token accounts on-chain — clients never touch wSOL.
17
26
  - **External-signer native.** Returns the exact message bytes to sign, for
18
27
  wallets, signing services, or HSMs — no `Keypair` ever touches the SDK.
19
28
 
@@ -117,6 +126,36 @@ const events = decodeSwapResultEvents(tx.meta.logMessages);
117
126
  // items: [{ kind: "platform" | "tenant", bps, amount, tenantProgram }] } }]
118
127
  ```
119
128
 
129
+ ## Landing Service (standalone)
130
+
131
+ Land any transaction — router-built or otherwise. Three rules: include the
132
+ tip (a plain transfer to an allowlisted account, kept as a **static** message
133
+ key, never behind a lookup table), use a **finalized** blockhash, and confirm
134
+ by **polling** (no websocket).
135
+
136
+ ```ts
137
+ import { createLandingClient, withLandingTip } from "@0block.io/sdk";
138
+
139
+ // Any instructions at all — no router required:
140
+ const ixs = withLandingTip(myInstructions, payer, {
141
+ account: tipAccount, // allowlisted tip account
142
+ lamports: 1_000_000n, // ≥ the service minimum
143
+ });
144
+ // ...compile with a FINALIZED blockhash, sign...
145
+
146
+ const landing = createLandingClient({
147
+ url: "https://<landing-endpoint>",
148
+ apiKey: process.env.ZEROBLOCK_API_KEY,
149
+ });
150
+ const { signature, slot } = await landing.submitAndConfirm(wireBytes, readConnection, {
151
+ lastValidBlockHeight,
152
+ });
153
+ ```
154
+
155
+ Router-built transactions already carry the tip when you pass `tip` to
156
+ `buildPumpfunSwapTransaction` — hand their signed wire bytes straight to
157
+ `landing.sendTransaction`.
158
+
120
159
  ## API overview
121
160
 
122
161
  | Runs | Function | Purpose |
@@ -135,6 +174,13 @@ exported as well.
135
174
 
136
175
  ## Operational notes
137
176
 
177
+ - **Order ids are server-authoritative in tenant mode.** The wrapper enforces
178
+ an exactly-once replay gate keyed by `(payer, order_id)`: a duplicate order
179
+ id fails at account creation and the whole swap reverts. Mint `orderId` on
180
+ your backend (non-zero u128, one per business order — reuse it across
181
+ retries) and pass it to `buildPumpfunSwapTransaction`; the SDK refuses to
182
+ build tenant swaps without one. Direct mode has no marker; a random id is
183
+ generated when omitted.
138
184
  - **Tenant mode prerequisites.** Your wrapper must be the build in which the
139
185
  `wrapper_config` PDA signs the router CPI, and the router's
140
186
  `TenantConfig.tenant_authority` must equal that PDA
@@ -20,6 +20,7 @@ export declare const FEE_MINT_ALLOWLIST: readonly string[];
20
20
  export declare const PUMPFUN_PROGRAM_ID: web3.PublicKey;
21
21
  export declare const PUMPFUN_FEE_PROGRAM_ID: web3.PublicKey;
22
22
  export declare const SEED_WRAPPER_CONFIG: Uint8Array<ArrayBuffer>;
23
+ export declare const SEED_ORDER_MARKER: Uint8Array<ArrayBuffer>;
23
24
  export declare const SEED_ROUTER_GLOBAL_CONFIG: Uint8Array<ArrayBuffer>;
24
25
  export declare const SEED_ROUTER_TENANT: Uint8Array<ArrayBuffer>;
25
26
  export declare const SEED_PUMPFUN_GLOBAL: Uint8Array<ArrayBuffer>;
package/dist/constants.js CHANGED
@@ -29,6 +29,7 @@ export const PUMPFUN_PROGRAM_ID = new web3.PublicKey("6EF8rrecthR5Dkzon8Nwu78hRv
29
29
  export const PUMPFUN_FEE_PROGRAM_ID = new web3.PublicKey("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ");
30
30
  const te = new TextEncoder();
31
31
  export const SEED_WRAPPER_CONFIG = te.encode("tenant_wrapper_config");
32
+ export const SEED_ORDER_MARKER = te.encode("order");
32
33
  export const SEED_ROUTER_GLOBAL_CONFIG = te.encode("config");
33
34
  export const SEED_ROUTER_TENANT = te.encode("tenant");
34
35
  export const SEED_PUMPFUN_GLOBAL = te.encode("global");
@@ -74,6 +74,7 @@ export declare const TENANT_WRAPPER_IDL: {
74
74
  }];
75
75
  }, {
76
76
  readonly name: "swap";
77
+ readonly docs: readonly ["Tenant swap with a hard, exactly-once-SUCCESS replay gate (R-6 / H1).", "", "The `order_marker` account is created with Anchor `init` (NOT", "`init_if_needed`) ATOMICALLY in this same instruction, BEFORE the router", "CPI runs. A second instruction with the same `(payer, order_id)` fails at", "`create_account` (\"account already in use\") and reverts the WHOLE", "instruction — including the router CPI — so a duplicate business order can", "never execute (and never pays fees) twice.", "", "INVARIANTS (off-chain encoder MUST uphold):", "- `order_id` is SERVER-AUTHORITATIVE. It is minted by the backend per", "business order and is NEVER a client-supplied value. A client that could", "choose `order_id` could grief a victim only inside its own `payer`", "namespace (see namespacing note below), but a server-authoritative id", "also guarantees that all per-gate provider-variants (0block / Jito /", "Helius / ...) of ONE business order reuse the SAME `(payer, order_id)`,", "so at most one variant can ever succeed.", "- On land-but-revert (the tx is included on-chain but the instruction", "reverts for any reason), the `init` is rolled back: the marker does NOT", "persist, the `order_id` is reclaimable, and the order may be retried with", "a FRESH durable nonce. (Durable-nonce dedup is at-most-one-LAND; this", "marker is exactly-once-SUCCESS — complementary, not redundant.)"];
77
78
  readonly discriminator: readonly [248, 198, 158, 145, 225, 117, 135, 200];
78
79
  readonly accounts: readonly [{
79
80
  readonly name: "payer";
@@ -88,17 +89,33 @@ export declare const TENANT_WRAPPER_IDL: {
88
89
  readonly value: readonly [116, 101, 110, 97, 110, 116, 95, 119, 114, 97, 112, 112, 101, 114, 95, 99, 111, 110, 102, 105, 103];
89
90
  }];
90
91
  };
92
+ }, {
93
+ readonly name: "order_marker";
94
+ readonly docs: readonly ["Per-order replay marker (R-6 / H1). `init` (NOT `init_if_needed`) makes a", "duplicate `(payer, order_id)` fail at `create_account` → the whole", "instruction (router CPI included) reverts.", "", "Namespaced by `payer` for griefing isolation: a third party can only ever", "init markers inside its OWN payer namespace, so it cannot pre-burn another", "user's `order_id`.", "", "ALTERNATIVE NAMESPACING: keying by `tenant_authority` instead of `payer`", "would dedup per business tenant (so the same logical order is unique across", "all of a tenant's relayer payers). We choose `payer` here because the RAX", "flow has one user-payer per order and `payer`-namespacing gives the", "strongest third-party griefing isolation; revisit if a single business", "order can legitimately arrive under multiple distinct payers."];
95
+ readonly writable: true;
96
+ readonly pda: {
97
+ readonly seeds: readonly [{
98
+ readonly kind: "const";
99
+ readonly value: readonly [111, 114, 100, 101, 114];
100
+ }, {
101
+ readonly kind: "account";
102
+ readonly path: "payer";
103
+ }, {
104
+ readonly kind: "arg";
105
+ readonly path: "order_id";
106
+ }];
107
+ };
91
108
  }, {
92
109
  readonly name: "router_program";
93
110
  readonly docs: readonly ["Router program (hard-checked by address)"];
94
111
  readonly address: "zeroRxfemoQCz6JT7RyShjuuRCN7VZof1WSekfXhmEn";
95
112
  }, {
96
113
  readonly name: "source_token_account";
97
- readonly docs: readonly ["SOURCE token account (authority = payer)"];
114
+ readonly docs: readonly ["SOURCE token account (authority = payer).", "buys (Anchor deserializes typed accounts before the handler runs, which", "would reject a not-yet-existing ATA). The router CPI re-validates", "`token::mint = source_mint, token::authority = payer` on this account."];
98
115
  readonly writable: true;
99
116
  }, {
100
117
  readonly name: "destination_token_account";
101
- readonly docs: readonly ["DESTINATION token account (receives the swap output)"];
118
+ readonly docs: readonly ["DESTINATION token account (receives the swap output).", "M2: authority must be `payer` so output cannot be routed to an arbitrary", "same-mint account — enforced by the ROUTER's account constraints."];
102
119
  readonly writable: true;
103
120
  }, {
104
121
  readonly name: "source_mint";
@@ -106,10 +123,10 @@ export declare const TENANT_WRAPPER_IDL: {
106
123
  readonly name: "destination_mint";
107
124
  }, {
108
125
  readonly name: "global_cfg";
109
- readonly docs: readonly ["Global config (router PDA)"];
126
+ readonly docs: readonly ["Global config (router PDA; owner/discriminator checked here, PDA seeds", "re-validated by the router). Typed so the wrapper can read the 0Block", "fee bps for exact wSOL fee-headroom sizing."];
110
127
  }, {
111
128
  readonly name: "tenant_cfg";
112
- readonly docs: readonly ["Tenant config (router PDA)"];
129
+ readonly docs: readonly ["Tenant config (router PDA; owner/discriminator checked here, PDA seeds", "re-validated by the router)."];
113
130
  }, {
114
131
  readonly name: "zeroblock_sol_vault";
115
132
  readonly docs: readonly ["0BLOCK fee authority (owns 0BLOCK fee token accounts)"];
@@ -138,7 +155,7 @@ export declare const TENANT_WRAPPER_IDL: {
138
155
  readonly address: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
139
156
  }, {
140
157
  readonly name: "system_program";
141
- readonly optional: true;
158
+ readonly docs: readonly ["REQUIRED (was optional): the `order_marker` `init` needs the System", "program, and it is also forwarded to the router for fee-ATA creation."];
142
159
  readonly address: "11111111111111111111111111111111";
143
160
  }, {
144
161
  readonly name: "ix_sysvar";
@@ -158,6 +175,15 @@ export declare const TENANT_WRAPPER_IDL: {
158
175
  }];
159
176
  }];
160
177
  readonly accounts: readonly [{
178
+ readonly name: "GlobalConfig";
179
+ readonly discriminator: readonly [149, 8, 156, 202, 160, 252, 176, 217];
180
+ }, {
181
+ readonly name: "OrderMarker";
182
+ readonly discriminator: readonly [221, 221, 169, 152, 245, 69, 110, 108];
183
+ }, {
184
+ readonly name: "TenantConfig";
185
+ readonly discriminator: readonly [195, 252, 97, 104, 160, 30, 231, 112];
186
+ }, {
161
187
  readonly name: "TenantWrapperConfig";
162
188
  readonly discriminator: readonly [221, 67, 69, 41, 128, 45, 34, 31];
163
189
  }];
@@ -169,6 +195,26 @@ export declare const TENANT_WRAPPER_IDL: {
169
195
  readonly code: 6001;
170
196
  readonly name: "InvalidAdmin";
171
197
  readonly msg: "Admin cannot be the default pubkey";
198
+ }, {
199
+ readonly code: 6002;
200
+ readonly name: "ZeroOrderId";
201
+ readonly msg: "order_id must be non-zero";
202
+ }, {
203
+ readonly code: 6003;
204
+ readonly name: "MissingTokenProgram";
205
+ readonly msg: "token program account is required for native-SOL handling";
206
+ }, {
207
+ readonly code: 6004;
208
+ readonly name: "MissingAssociatedTokenProgram";
209
+ readonly msg: "associated token program account is required for native-SOL handling";
210
+ }, {
211
+ readonly code: 6005;
212
+ readonly name: "AmountOverflow";
213
+ readonly msg: "amount_in plus fees overflows u64";
214
+ }, {
215
+ readonly code: 6006;
216
+ readonly name: "InvalidTokenAccount";
217
+ readonly msg: "account is not a valid SPL token account";
172
218
  }];
173
219
  readonly types: readonly [{
174
220
  readonly name: "Dex";
@@ -307,6 +353,81 @@ export declare const TENANT_WRAPPER_IDL: {
307
353
  readonly name: "SolRfq";
308
354
  }];
309
355
  };
356
+ }, {
357
+ readonly name: "GlobalConfig";
358
+ readonly serialization: "bytemuckunsafe";
359
+ readonly repr: {
360
+ readonly kind: "c";
361
+ };
362
+ readonly type: {
363
+ readonly kind: "struct";
364
+ readonly fields: readonly [{
365
+ readonly name: "bump";
366
+ readonly docs: readonly ["PDA bump"];
367
+ readonly type: "u8";
368
+ }, {
369
+ readonly name: "admin";
370
+ readonly docs: readonly ["Program admin (controls global/tenant settings)"];
371
+ readonly type: "pubkey";
372
+ }, {
373
+ readonly name: "zeroblock_fee_bps";
374
+ readonly docs: readonly ["0BLOCK platform fee default (bps out of 10_000)"];
375
+ readonly type: "u16";
376
+ }, {
377
+ readonly name: "zeroblock_fee_vault";
378
+ readonly docs: readonly ["Fee authority that owns 0BLOCK fee token accounts"];
379
+ readonly type: "pubkey";
380
+ }, {
381
+ readonly name: "paused";
382
+ readonly docs: readonly ["A2 (remediation-2026-07-02): global instant-halt switch. `0` = not", "paused (default; matches the zeroed tail of every already-deployed", "account), `1` = paused. Plain `u8` rather than `bool` — zero_copy/Pod", "requires every byte pattern to be valid, which `bool` does not", "guarantee. Use `is_paused()`/`set_paused()`, never the raw field."];
383
+ readonly type: "u8";
384
+ }, {
385
+ readonly name: "spl_token_swap_allowlist";
386
+ readonly docs: readonly ["A1 (remediation-2026-07-02): admin-provisioned CPI-target allowlist", "for the `SplTokenSwap` (Dex=0) adapter — the router invokes", "`swap_accounts.dex_program_id` with the `0block_sa` PDA as signer on", "hop ≥ 1, so an unconstrained target is a confused-deputy sweep of any", "PDA-held funds. Unlike the other ~60 adapters this venue has no", "single hardcoded upstream program id (multiple live forks share the", "same instruction interface), so the allowed set is admin-configured", "instead of a compile-time const. Unset slots are `Pubkey::default()`", "and never match; an all-default allowlist rejects every CPI (fail", "closed) until an admin provisions it via `set_spl_token_swap_allowlist`", "(see `// TODO(remediation-2026-07-02):` in `adapters/spl_token_swap.rs`", "— provisioning real venue ids is a deploy-config/operator action, out", "of scope for this remediation pass, same as the plan's other", "\"provisioning\" items)."];
387
+ readonly type: {
388
+ readonly array: readonly ["pubkey", 2];
389
+ };
390
+ }, {
391
+ readonly name: "stable_swap_allowlist";
392
+ readonly docs: readonly ["A1 (remediation-2026-07-02): same contract as", "`spl_token_swap_allowlist`, for the `StableSwap` (Dex=1) adapter."];
393
+ readonly type: {
394
+ readonly array: readonly ["pubkey", 2];
395
+ };
396
+ }, {
397
+ readonly name: "padding";
398
+ readonly docs: readonly ["Reserved to keep struct size stable for future upgrades. Shrunk from", "the original 160 bytes by exactly the width of the fields above (see", "`REMAINING_PADDING_BYTES`) so total account size is unchanged."];
399
+ readonly type: {
400
+ readonly array: readonly ["u8", 31];
401
+ };
402
+ }];
403
+ };
404
+ }, {
405
+ readonly name: "OrderMarker";
406
+ readonly docs: readonly ["Per-order replay marker (R-6 / H1). Audit-only fields — the account's", "EXISTENCE under seeds `[b\"order\", payer, order_id_le]` IS the dedup signal."];
407
+ readonly type: {
408
+ readonly kind: "struct";
409
+ readonly fields: readonly [{
410
+ readonly name: "order_id";
411
+ readonly docs: readonly ["The server-authoritative business order id this marker pins."];
412
+ readonly type: "u128";
413
+ }, {
414
+ readonly name: "authority";
415
+ readonly docs: readonly ["The payer namespace this marker was created under (== `payer`)."];
416
+ readonly type: "pubkey";
417
+ }, {
418
+ readonly name: "slot";
419
+ readonly docs: readonly ["Slot at creation (forensics only)."];
420
+ readonly type: "u64";
421
+ }, {
422
+ readonly name: "unix_timestamp";
423
+ readonly docs: readonly ["Unix timestamp at creation (forensics only)."];
424
+ readonly type: "i64";
425
+ }, {
426
+ readonly name: "bump";
427
+ readonly docs: readonly ["PDA bump."];
428
+ readonly type: "u8";
429
+ }];
430
+ };
310
431
  }, {
311
432
  readonly name: "Route";
312
433
  readonly type: {
@@ -395,6 +516,46 @@ export declare const TENANT_WRAPPER_IDL: {
395
516
  };
396
517
  }];
397
518
  };
519
+ }, {
520
+ readonly name: "TenantConfig";
521
+ readonly docs: readonly ["----------------------------------------------------------------------------", "TenantConfig (+ strict wrapper enforcement)", "----------------------------------------------------------------------------"];
522
+ readonly type: {
523
+ readonly kind: "struct";
524
+ readonly fields: readonly [{
525
+ readonly name: "bump";
526
+ readonly type: "u8";
527
+ }, {
528
+ readonly name: "tenant_authority";
529
+ readonly docs: readonly ["Signer (EOA or PDA) authorized to act for this tenant"];
530
+ readonly type: "pubkey";
531
+ }, {
532
+ readonly name: "tenant_wrapper_program";
533
+ readonly docs: readonly ["Wrapper program id used to validate tenant-mode calls."];
534
+ readonly type: "pubkey";
535
+ }, {
536
+ readonly name: "zeroblock_fee_bps";
537
+ readonly docs: readonly ["0BLOCK fee (bps out of 10_000) for this tenant. Use 0 to fall back to global default."];
538
+ readonly type: "u16";
539
+ }, {
540
+ readonly name: "tenant_fee_vault";
541
+ readonly docs: readonly ["Fee authority that owns tenant fee token accounts"];
542
+ readonly type: "pubkey";
543
+ }, {
544
+ readonly name: "whitelisted";
545
+ readonly docs: readonly ["Whether this tenant is active/whitelisted"];
546
+ readonly type: "bool";
547
+ }, {
548
+ readonly name: "max_total_fee_bps";
549
+ readonly docs: readonly ["Optional per-tenant cap: (tenant_fee_bps + zeroblock_fee_bps) must be <= this if > 0.", "tenant_fee_bps is supplied per swap."];
550
+ readonly type: "u16";
551
+ }, {
552
+ readonly name: "_reserved";
553
+ readonly docs: readonly ["Reserved for upgrades (fixed size)"];
554
+ readonly type: {
555
+ readonly array: readonly ["u8", 64];
556
+ };
557
+ }];
558
+ };
398
559
  }, {
399
560
  readonly name: "TenantWrapperConfig";
400
561
  readonly type: {