@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.
@@ -1,7 +1,9 @@
1
1
  // Vendored from the tenant-wrapper build of nyx-solana-dex-router (target/idl/zeroblock_tenant_wrapper.json).
2
2
  // The instruction layout is identical for every tenant wrapper deployment; the program
3
3
  // address is per-tenant and intentionally stripped — pass wrapperProgramId at init.
4
- // tenant_authority is not an account: the wrapper_config PDA signs the router CPI.
4
+ // swap: order_marker replay gate (server-authoritative non-zero order_id); the
5
+ // wrapper_config PDA signs the router CPI; native-SOL wrap/fee-headroom/unwrap/
6
+ // ATA-cleanup are PROGRAM-MANAGED — clients add no wSOL instructions.
5
7
  export const TENANT_WRAPPER_IDL = {
6
8
  "address": "11111111111111111111111111111111",
7
9
  "metadata": {
@@ -201,6 +203,30 @@ export const TENANT_WRAPPER_IDL = {
201
203
  },
202
204
  {
203
205
  "name": "swap",
206
+ "docs": [
207
+ "Tenant swap with a hard, exactly-once-SUCCESS replay gate (R-6 / H1).",
208
+ "",
209
+ "The `order_marker` account is created with Anchor `init` (NOT",
210
+ "`init_if_needed`) ATOMICALLY in this same instruction, BEFORE the router",
211
+ "CPI runs. A second instruction with the same `(payer, order_id)` fails at",
212
+ "`create_account` (\"account already in use\") and reverts the WHOLE",
213
+ "instruction — including the router CPI — so a duplicate business order can",
214
+ "never execute (and never pays fees) twice.",
215
+ "",
216
+ "INVARIANTS (off-chain encoder MUST uphold):",
217
+ "- `order_id` is SERVER-AUTHORITATIVE. It is minted by the backend per",
218
+ "business order and is NEVER a client-supplied value. A client that could",
219
+ "choose `order_id` could grief a victim only inside its own `payer`",
220
+ "namespace (see namespacing note below), but a server-authoritative id",
221
+ "also guarantees that all per-gate provider-variants (0block / Jito /",
222
+ "Helius / ...) of ONE business order reuse the SAME `(payer, order_id)`,",
223
+ "so at most one variant can ever succeed.",
224
+ "- On land-but-revert (the tx is included on-chain but the instruction",
225
+ "reverts for any reason), the `init` is rolled back: the marker does NOT",
226
+ "persist, the `order_id` is reclaimable, and the order may be retried with",
227
+ "a FRESH durable nonce. (Durable-nonce dedup is at-most-one-LAND; this",
228
+ "marker is exactly-once-SUCCESS — complementary, not redundant.)"
229
+ ],
204
230
  "discriminator": [
205
231
  248,
206
232
  198,
@@ -253,6 +279,48 @@ export const TENANT_WRAPPER_IDL = {
253
279
  ]
254
280
  }
255
281
  },
282
+ {
283
+ "name": "order_marker",
284
+ "docs": [
285
+ "Per-order replay marker (R-6 / H1). `init` (NOT `init_if_needed`) makes a",
286
+ "duplicate `(payer, order_id)` fail at `create_account` → the whole",
287
+ "instruction (router CPI included) reverts.",
288
+ "",
289
+ "Namespaced by `payer` for griefing isolation: a third party can only ever",
290
+ "init markers inside its OWN payer namespace, so it cannot pre-burn another",
291
+ "user's `order_id`.",
292
+ "",
293
+ "ALTERNATIVE NAMESPACING: keying by `tenant_authority` instead of `payer`",
294
+ "would dedup per business tenant (so the same logical order is unique across",
295
+ "all of a tenant's relayer payers). We choose `payer` here because the RAX",
296
+ "flow has one user-payer per order and `payer`-namespacing gives the",
297
+ "strongest third-party griefing isolation; revisit if a single business",
298
+ "order can legitimately arrive under multiple distinct payers."
299
+ ],
300
+ "writable": true,
301
+ "pda": {
302
+ "seeds": [
303
+ {
304
+ "kind": "const",
305
+ "value": [
306
+ 111,
307
+ 114,
308
+ 100,
309
+ 101,
310
+ 114
311
+ ]
312
+ },
313
+ {
314
+ "kind": "account",
315
+ "path": "payer"
316
+ },
317
+ {
318
+ "kind": "arg",
319
+ "path": "order_id"
320
+ }
321
+ ]
322
+ }
323
+ },
256
324
  {
257
325
  "name": "router_program",
258
326
  "docs": [
@@ -263,14 +331,19 @@ export const TENANT_WRAPPER_IDL = {
263
331
  {
264
332
  "name": "source_token_account",
265
333
  "docs": [
266
- "SOURCE token account (authority = payer)"
334
+ "SOURCE token account (authority = payer).",
335
+ "buys (Anchor deserializes typed accounts before the handler runs, which",
336
+ "would reject a not-yet-existing ATA). The router CPI re-validates",
337
+ "`token::mint = source_mint, token::authority = payer` on this account."
267
338
  ],
268
339
  "writable": true
269
340
  },
270
341
  {
271
342
  "name": "destination_token_account",
272
343
  "docs": [
273
- "DESTINATION token account (receives the swap output)"
344
+ "DESTINATION token account (receives the swap output).",
345
+ "M2: authority must be `payer` so output cannot be routed to an arbitrary",
346
+ "same-mint account — enforced by the ROUTER's account constraints."
274
347
  ],
275
348
  "writable": true
276
349
  },
@@ -283,13 +356,16 @@ export const TENANT_WRAPPER_IDL = {
283
356
  {
284
357
  "name": "global_cfg",
285
358
  "docs": [
286
- "Global config (router PDA)"
359
+ "Global config (router PDA; owner/discriminator checked here, PDA seeds",
360
+ "re-validated by the router). Typed so the wrapper can read the 0Block",
361
+ "fee bps for exact wSOL fee-headroom sizing."
287
362
  ]
288
363
  },
289
364
  {
290
365
  "name": "tenant_cfg",
291
366
  "docs": [
292
- "Tenant config (router PDA)"
367
+ "Tenant config (router PDA; owner/discriminator checked here, PDA seeds",
368
+ "re-validated by the router)."
293
369
  ]
294
370
  },
295
371
  {
@@ -335,7 +411,10 @@ export const TENANT_WRAPPER_IDL = {
335
411
  },
336
412
  {
337
413
  "name": "system_program",
338
- "optional": true,
414
+ "docs": [
415
+ "REQUIRED (was optional): the `order_marker` `init` needs the System",
416
+ "program, and it is also forwarded to the router for fee-ATA creation."
417
+ ],
339
418
  "address": "11111111111111111111111111111111"
340
419
  },
341
420
  {
@@ -363,6 +442,45 @@ export const TENANT_WRAPPER_IDL = {
363
442
  }
364
443
  ],
365
444
  "accounts": [
445
+ {
446
+ "name": "GlobalConfig",
447
+ "discriminator": [
448
+ 149,
449
+ 8,
450
+ 156,
451
+ 202,
452
+ 160,
453
+ 252,
454
+ 176,
455
+ 217
456
+ ]
457
+ },
458
+ {
459
+ "name": "OrderMarker",
460
+ "discriminator": [
461
+ 221,
462
+ 221,
463
+ 169,
464
+ 152,
465
+ 245,
466
+ 69,
467
+ 110,
468
+ 108
469
+ ]
470
+ },
471
+ {
472
+ "name": "TenantConfig",
473
+ "discriminator": [
474
+ 195,
475
+ 252,
476
+ 97,
477
+ 104,
478
+ 160,
479
+ 30,
480
+ 231,
481
+ 112
482
+ ]
483
+ },
366
484
  {
367
485
  "name": "TenantWrapperConfig",
368
486
  "discriminator": [
@@ -387,6 +505,31 @@ export const TENANT_WRAPPER_IDL = {
387
505
  "code": 6001,
388
506
  "name": "InvalidAdmin",
389
507
  "msg": "Admin cannot be the default pubkey"
508
+ },
509
+ {
510
+ "code": 6002,
511
+ "name": "ZeroOrderId",
512
+ "msg": "order_id must be non-zero"
513
+ },
514
+ {
515
+ "code": 6003,
516
+ "name": "MissingTokenProgram",
517
+ "msg": "token program account is required for native-SOL handling"
518
+ },
519
+ {
520
+ "code": 6004,
521
+ "name": "MissingAssociatedTokenProgram",
522
+ "msg": "associated token program account is required for native-SOL handling"
523
+ },
524
+ {
525
+ "code": 6005,
526
+ "name": "AmountOverflow",
527
+ "msg": "amount_in plus fees overflows u64"
528
+ },
529
+ {
530
+ "code": 6006,
531
+ "name": "InvalidTokenAccount",
532
+ "msg": "account is not a valid SPL token account"
390
533
  }
391
534
  ],
392
535
  "types": [
@@ -596,6 +739,156 @@ export const TENANT_WRAPPER_IDL = {
596
739
  ]
597
740
  }
598
741
  },
742
+ {
743
+ "name": "GlobalConfig",
744
+ "serialization": "bytemuckunsafe",
745
+ "repr": {
746
+ "kind": "c"
747
+ },
748
+ "type": {
749
+ "kind": "struct",
750
+ "fields": [
751
+ {
752
+ "name": "bump",
753
+ "docs": [
754
+ "PDA bump"
755
+ ],
756
+ "type": "u8"
757
+ },
758
+ {
759
+ "name": "admin",
760
+ "docs": [
761
+ "Program admin (controls global/tenant settings)"
762
+ ],
763
+ "type": "pubkey"
764
+ },
765
+ {
766
+ "name": "zeroblock_fee_bps",
767
+ "docs": [
768
+ "0BLOCK platform fee default (bps out of 10_000)"
769
+ ],
770
+ "type": "u16"
771
+ },
772
+ {
773
+ "name": "zeroblock_fee_vault",
774
+ "docs": [
775
+ "Fee authority that owns 0BLOCK fee token accounts"
776
+ ],
777
+ "type": "pubkey"
778
+ },
779
+ {
780
+ "name": "paused",
781
+ "docs": [
782
+ "A2 (remediation-2026-07-02): global instant-halt switch. `0` = not",
783
+ "paused (default; matches the zeroed tail of every already-deployed",
784
+ "account), `1` = paused. Plain `u8` rather than `bool` — zero_copy/Pod",
785
+ "requires every byte pattern to be valid, which `bool` does not",
786
+ "guarantee. Use `is_paused()`/`set_paused()`, never the raw field."
787
+ ],
788
+ "type": "u8"
789
+ },
790
+ {
791
+ "name": "spl_token_swap_allowlist",
792
+ "docs": [
793
+ "A1 (remediation-2026-07-02): admin-provisioned CPI-target allowlist",
794
+ "for the `SplTokenSwap` (Dex=0) adapter — the router invokes",
795
+ "`swap_accounts.dex_program_id` with the `0block_sa` PDA as signer on",
796
+ "hop ≥ 1, so an unconstrained target is a confused-deputy sweep of any",
797
+ "PDA-held funds. Unlike the other ~60 adapters this venue has no",
798
+ "single hardcoded upstream program id (multiple live forks share the",
799
+ "same instruction interface), so the allowed set is admin-configured",
800
+ "instead of a compile-time const. Unset slots are `Pubkey::default()`",
801
+ "and never match; an all-default allowlist rejects every CPI (fail",
802
+ "closed) until an admin provisions it via `set_spl_token_swap_allowlist`",
803
+ "(see `// TODO(remediation-2026-07-02):` in `adapters/spl_token_swap.rs`",
804
+ "— provisioning real venue ids is a deploy-config/operator action, out",
805
+ "of scope for this remediation pass, same as the plan's other",
806
+ "\"provisioning\" items)."
807
+ ],
808
+ "type": {
809
+ "array": [
810
+ "pubkey",
811
+ 2
812
+ ]
813
+ }
814
+ },
815
+ {
816
+ "name": "stable_swap_allowlist",
817
+ "docs": [
818
+ "A1 (remediation-2026-07-02): same contract as",
819
+ "`spl_token_swap_allowlist`, for the `StableSwap` (Dex=1) adapter."
820
+ ],
821
+ "type": {
822
+ "array": [
823
+ "pubkey",
824
+ 2
825
+ ]
826
+ }
827
+ },
828
+ {
829
+ "name": "padding",
830
+ "docs": [
831
+ "Reserved to keep struct size stable for future upgrades. Shrunk from",
832
+ "the original 160 bytes by exactly the width of the fields above (see",
833
+ "`REMAINING_PADDING_BYTES`) so total account size is unchanged."
834
+ ],
835
+ "type": {
836
+ "array": [
837
+ "u8",
838
+ 31
839
+ ]
840
+ }
841
+ }
842
+ ]
843
+ }
844
+ },
845
+ {
846
+ "name": "OrderMarker",
847
+ "docs": [
848
+ "Per-order replay marker (R-6 / H1). Audit-only fields — the account's",
849
+ "EXISTENCE under seeds `[b\"order\", payer, order_id_le]` IS the dedup signal."
850
+ ],
851
+ "type": {
852
+ "kind": "struct",
853
+ "fields": [
854
+ {
855
+ "name": "order_id",
856
+ "docs": [
857
+ "The server-authoritative business order id this marker pins."
858
+ ],
859
+ "type": "u128"
860
+ },
861
+ {
862
+ "name": "authority",
863
+ "docs": [
864
+ "The payer namespace this marker was created under (== `payer`)."
865
+ ],
866
+ "type": "pubkey"
867
+ },
868
+ {
869
+ "name": "slot",
870
+ "docs": [
871
+ "Slot at creation (forensics only)."
872
+ ],
873
+ "type": "u64"
874
+ },
875
+ {
876
+ "name": "unix_timestamp",
877
+ "docs": [
878
+ "Unix timestamp at creation (forensics only)."
879
+ ],
880
+ "type": "i64"
881
+ },
882
+ {
883
+ "name": "bump",
884
+ "docs": [
885
+ "PDA bump."
886
+ ],
887
+ "type": "u8"
888
+ }
889
+ ]
890
+ }
891
+ },
599
892
  {
600
893
  "name": "Route",
601
894
  "type": {
@@ -707,6 +1000,78 @@ export const TENANT_WRAPPER_IDL = {
707
1000
  ]
708
1001
  }
709
1002
  },
1003
+ {
1004
+ "name": "TenantConfig",
1005
+ "docs": [
1006
+ "----------------------------------------------------------------------------",
1007
+ "TenantConfig (+ strict wrapper enforcement)",
1008
+ "----------------------------------------------------------------------------"
1009
+ ],
1010
+ "type": {
1011
+ "kind": "struct",
1012
+ "fields": [
1013
+ {
1014
+ "name": "bump",
1015
+ "type": "u8"
1016
+ },
1017
+ {
1018
+ "name": "tenant_authority",
1019
+ "docs": [
1020
+ "Signer (EOA or PDA) authorized to act for this tenant"
1021
+ ],
1022
+ "type": "pubkey"
1023
+ },
1024
+ {
1025
+ "name": "tenant_wrapper_program",
1026
+ "docs": [
1027
+ "Wrapper program id used to validate tenant-mode calls."
1028
+ ],
1029
+ "type": "pubkey"
1030
+ },
1031
+ {
1032
+ "name": "zeroblock_fee_bps",
1033
+ "docs": [
1034
+ "0BLOCK fee (bps out of 10_000) for this tenant. Use 0 to fall back to global default."
1035
+ ],
1036
+ "type": "u16"
1037
+ },
1038
+ {
1039
+ "name": "tenant_fee_vault",
1040
+ "docs": [
1041
+ "Fee authority that owns tenant fee token accounts"
1042
+ ],
1043
+ "type": "pubkey"
1044
+ },
1045
+ {
1046
+ "name": "whitelisted",
1047
+ "docs": [
1048
+ "Whether this tenant is active/whitelisted"
1049
+ ],
1050
+ "type": "bool"
1051
+ },
1052
+ {
1053
+ "name": "max_total_fee_bps",
1054
+ "docs": [
1055
+ "Optional per-tenant cap: (tenant_fee_bps + zeroblock_fee_bps) must be <= this if > 0.",
1056
+ "tenant_fee_bps is supplied per swap."
1057
+ ],
1058
+ "type": "u16"
1059
+ },
1060
+ {
1061
+ "name": "_reserved",
1062
+ "docs": [
1063
+ "Reserved for upgrades (fixed size)"
1064
+ ],
1065
+ "type": {
1066
+ "array": [
1067
+ "u8",
1068
+ 64
1069
+ ]
1070
+ }
1071
+ }
1072
+ ]
1073
+ }
1074
+ },
710
1075
  {
711
1076
  "name": "TenantWrapperConfig",
712
1077
  "type": {
package/dist/index.d.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  export * from "./constants.js";
2
2
  export * from "./types.js";
3
3
  export { createContext, type CreateContextParams, type TenantInit, type ZeroBlockContext, } from "./context.js";
4
- export { toPk, deriveAta, deriveWrapperConfig, deriveRouterGlobalConfig, deriveRouterTenantConfig, derivePumpfunGlobal, derivePumpfunBondingCurve, derivePumpfunBondingCurveV2, derivePumpfunEventAuthority, derivePumpfunCreatorVault, derivePumpfunGlobalVolumeAccumulator, derivePumpfunUserVolumeAccumulator, derivePumpfunFeeConfig, } from "./pda.js";
4
+ export { buildSendTransactionRequest, createLandingClient, withLandingTip, type LandingClient, type LandingServiceConfig, type SendTransactionOptions, } from "./landing.js";
5
+ export { toPk, deriveAta, deriveOrderMarker, deriveWrapperConfig, deriveRouterGlobalConfig, deriveRouterTenantConfig, derivePumpfunGlobal, derivePumpfunBondingCurve, derivePumpfunBondingCurveV2, derivePumpfunEventAuthority, derivePumpfunCreatorVault, derivePumpfunGlobalVolumeAccumulator, derivePumpfunUserVolumeAccumulator, derivePumpfunFeeConfig, } from "./pda.js";
5
6
  export { type AccountFetcher, connectionFetcher, resolveTokenProgram, resolvePumpfunMarketContext, fetchPumpfunGlobalFeeRecipient, fetchPumpfunBondingCurveCreator, fetchPumpfunBuybackFeeRecipients, } from "./fetch.js";
6
7
  export { buildPumpfunRemainingAccounts, type PumpfunRemainingAccountsParams, } from "./venues/pumpfun.js";
7
8
  export { buildPumpfunSwapInstruction, decodeSwapData, randomOrderId, resolveFeeMint, type SwapInstructionResult, } from "./swap.js";
8
9
  export { buildTipInstruction, maskTipAccountInTables } from "./tip.js";
9
10
  export { buildComputeBudgetInstructions } from "./computeBudget.js";
10
11
  export { createAtaIdempotentInstruction, syncNativeInstruction, closeAccountInstruction, wrapSolInstructions, unwrapWsolInstruction, } from "./wsol.js";
11
- export { buildPumpfunSwapTransaction, serializeWithSignatures, toLookupTableAccount, } from "./transaction.js";
12
+ export { buildPumpfunSwapTransaction, serializeWithSignatures, buildSubmitTemplate, toLookupTableAccount, type SubmitTemplate, } from "./transaction.js";
12
13
  export { buildLookupTableAddresses, buildInitLookupTableInstructions, type InitLookupTableResult, } from "./alt.js";
13
14
  export { applyPreset, slippagePctToBps, solToLamports, type PresetSwapInputs, type PresetSwapOutputs, } from "./presets.js";
14
15
  export { decodeSwapResultEvents } from "./events.js";
package/dist/index.js CHANGED
@@ -1,25 +1,28 @@
1
- // @0block/sdk — browser-first transaction builder for the 0Block DEX router.
1
+ // @0block.io/sdk — SDK for 0Block's two independent products:
2
2
  //
3
- // Initialize with createContext(): pass a tenant (wrapper program + fee vault)
4
- // to route through that tenant's wrapper, or nothing to invoke the 0Block
5
- // router directly with 0Block fee accounts.
3
+ // 1. DEX SMART ROUTER build swap transactions (tenant-wrapped or direct).
4
+ // createContext, applyPreset, buildPumpfunSwapTransaction,
5
+ // resolvePumpfunMarketContext, ALT helpers, decodeSwapResultEvents.
6
6
  //
7
- // Split of responsibilities:
8
- // server-side (near an RPC): resolvePumpfunMarketContext, buildLookupTableAddresses,
9
- // buildInitLookupTableInstructions
10
- // client-side (fully offline): createContext, applyPreset, buildPumpfunSwapTransaction
11
- // indexer-side: decodeSwapResultEvents
7
+ // 2. LANDING SERVICE — land ANY fully-signed Solana transaction (tip-gated
8
+ // submission to staked infra + leader TPUs). createLandingClient,
9
+ // withLandingTip, buildTipInstruction, maskTipAccountInTables.
10
+ //
11
+ // Neither depends on the other: router transactions can be submitted through
12
+ // any RPC, and the landing service accepts any transaction. They compose
13
+ // naturally when used together.
12
14
  export * from "./constants.js";
13
15
  export * from "./types.js";
14
16
  export { createContext, } from "./context.js";
15
- export { toPk, deriveAta, deriveWrapperConfig, deriveRouterGlobalConfig, deriveRouterTenantConfig, derivePumpfunGlobal, derivePumpfunBondingCurve, derivePumpfunBondingCurveV2, derivePumpfunEventAuthority, derivePumpfunCreatorVault, derivePumpfunGlobalVolumeAccumulator, derivePumpfunUserVolumeAccumulator, derivePumpfunFeeConfig, } from "./pda.js";
17
+ export { buildSendTransactionRequest, createLandingClient, withLandingTip, } from "./landing.js";
18
+ export { toPk, deriveAta, deriveOrderMarker, deriveWrapperConfig, deriveRouterGlobalConfig, deriveRouterTenantConfig, derivePumpfunGlobal, derivePumpfunBondingCurve, derivePumpfunBondingCurveV2, derivePumpfunEventAuthority, derivePumpfunCreatorVault, derivePumpfunGlobalVolumeAccumulator, derivePumpfunUserVolumeAccumulator, derivePumpfunFeeConfig, } from "./pda.js";
16
19
  export { connectionFetcher, resolveTokenProgram, resolvePumpfunMarketContext, fetchPumpfunGlobalFeeRecipient, fetchPumpfunBondingCurveCreator, fetchPumpfunBuybackFeeRecipients, } from "./fetch.js";
17
20
  export { buildPumpfunRemainingAccounts, } from "./venues/pumpfun.js";
18
21
  export { buildPumpfunSwapInstruction, decodeSwapData, randomOrderId, resolveFeeMint, } from "./swap.js";
19
22
  export { buildTipInstruction, maskTipAccountInTables } from "./tip.js";
20
23
  export { buildComputeBudgetInstructions } from "./computeBudget.js";
21
24
  export { createAtaIdempotentInstruction, syncNativeInstruction, closeAccountInstruction, wrapSolInstructions, unwrapWsolInstruction, } from "./wsol.js";
22
- export { buildPumpfunSwapTransaction, serializeWithSignatures, toLookupTableAccount, } from "./transaction.js";
25
+ export { buildPumpfunSwapTransaction, serializeWithSignatures, buildSubmitTemplate, toLookupTableAccount, } from "./transaction.js";
23
26
  export { buildLookupTableAddresses, buildInitLookupTableInstructions, } from "./alt.js";
24
27
  export { applyPreset, slippagePctToBps, solToLamports, } from "./presets.js";
25
28
  export { decodeSwapResultEvents } from "./events.js";
@@ -0,0 +1,66 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import type { Address, TipParams } from "./types.js";
3
+ /**
4
+ * 0Block Landing service — a standalone product, independent of the DEX
5
+ * router. It lands ANY fully-signed Solana transaction: JSON-RPC
6
+ * `sendTransaction` gated by a tip transfer, forwarded to staked
7
+ * infrastructure and current/next leader TPUs.
8
+ *
9
+ * Contract (independent of what the transaction does):
10
+ * - include a SystemProgram transfer ≥ the minimum tip to an allowlisted tip
11
+ * account, kept as a STATIC message key (never behind an address lookup
12
+ * table — the service verifies raw bytes; see maskTipAccountInTables),
13
+ * - use a FINALIZED blockhash (the service submits via different nodes than
14
+ * the one you read from),
15
+ * - confirm by polling: the service does not serve websockets.
16
+ */
17
+ export interface LandingServiceConfig {
18
+ /** Landing service endpoint, e.g. "https://gateway.0block.io". */
19
+ url: string;
20
+ /** API key, sent as X-API-Key. */
21
+ apiKey?: string;
22
+ /** Injectable fetch (testing / non-standard runtimes). Defaults to global fetch. */
23
+ fetchImpl?: typeof fetch;
24
+ }
25
+ export interface SendTransactionOptions {
26
+ /**
27
+ * Skip the upstream's preflight simulation (default true — the low-latency
28
+ * path; a still-propagating blockhash can fail simulation spuriously).
29
+ */
30
+ skipPreflight?: boolean;
31
+ }
32
+ /** The exact HTTP request the Landing service expects (pure; useful for tests and custom transports). */
33
+ export declare function buildSendTransactionRequest(config: Pick<LandingServiceConfig, "url" | "apiKey">, wireTransaction: Uint8Array, options?: SendTransactionOptions): {
34
+ url: string;
35
+ method: "POST";
36
+ headers: Record<string, string>;
37
+ body: string;
38
+ };
39
+ /**
40
+ * Prepend the landing tip to an instruction list for ANY transaction (router
41
+ * swap or otherwise). The swap builder does this internally when given `tip`;
42
+ * use this for arbitrary transactions you land through the service.
43
+ */
44
+ export declare function withLandingTip(instructions: web3.TransactionInstruction[], payer: Address, tip: TipParams | {
45
+ account: Address;
46
+ lamports?: bigint;
47
+ }): web3.TransactionInstruction[];
48
+ export interface LandingClient {
49
+ /** Submit wire bytes (fully signed). Resolves to the transaction signature (submission ack, NOT confirmation). */
50
+ sendTransaction(wireTransaction: Uint8Array, options?: SendTransactionOptions): Promise<string>;
51
+ /**
52
+ * Submit, then poll the given read connection until the signature confirms,
53
+ * errors, or the blockhash lapses. The read RPC is separate from the
54
+ * landing endpoint by design.
55
+ */
56
+ submitAndConfirm(wireTransaction: Uint8Array, readConnection: web3.Connection, params: {
57
+ lastValidBlockHeight: number;
58
+ timeoutMs?: number;
59
+ pollIntervalMs?: number;
60
+ }, options?: SendTransactionOptions): Promise<{
61
+ signature: string;
62
+ slot: number;
63
+ }>;
64
+ }
65
+ /** Client for the Landing service. Usable with or without the DEX router. */
66
+ export declare function createLandingClient(config: LandingServiceConfig): LandingClient;
@@ -0,0 +1,78 @@
1
+ import { DEFAULT_TIP_LAMPORTS } from "./constants.js";
2
+ import { buildTipInstruction } from "./tip.js";
3
+ import { toPk } from "./pda.js";
4
+ /** The exact HTTP request the Landing service expects (pure; useful for tests and custom transports). */
5
+ export function buildSendTransactionRequest(config, wireTransaction, options) {
6
+ const headers = { "Content-Type": "application/json" };
7
+ if (config.apiKey)
8
+ headers["X-API-Key"] = config.apiKey;
9
+ return {
10
+ url: config.url,
11
+ method: "POST",
12
+ headers,
13
+ body: JSON.stringify({
14
+ jsonrpc: "2.0",
15
+ id: 1,
16
+ method: "sendTransaction",
17
+ params: [
18
+ Buffer.from(wireTransaction).toString("base64"),
19
+ { encoding: "base64", skipPreflight: options?.skipPreflight ?? true },
20
+ ],
21
+ }),
22
+ };
23
+ }
24
+ /**
25
+ * Prepend the landing tip to an instruction list for ANY transaction (router
26
+ * swap or otherwise). The swap builder does this internally when given `tip`;
27
+ * use this for arbitrary transactions you land through the service.
28
+ */
29
+ export function withLandingTip(instructions, payer, tip) {
30
+ const lamports = tip.lamports ?? DEFAULT_TIP_LAMPORTS;
31
+ return [
32
+ buildTipInstruction(toPk(payer), { account: tip.account, lamports }),
33
+ ...instructions,
34
+ ];
35
+ }
36
+ /** Client for the Landing service. Usable with or without the DEX router. */
37
+ export function createLandingClient(config) {
38
+ const fetchImpl = config.fetchImpl ?? fetch;
39
+ async function sendTransaction(wireTransaction, options) {
40
+ const req = buildSendTransactionRequest(config, wireTransaction, options);
41
+ const res = await fetchImpl(req.url, {
42
+ method: req.method,
43
+ headers: req.headers,
44
+ body: req.body,
45
+ });
46
+ const body = (await res.json().catch(() => ({})));
47
+ if (body.error) {
48
+ throw new Error(`landing service rejected the transaction (HTTP ${res.status}): ${JSON.stringify(body.error)}`);
49
+ }
50
+ if (!body.result) {
51
+ throw new Error(`unexpected landing service response (HTTP ${res.status}): ${JSON.stringify(body)}`);
52
+ }
53
+ return body.result;
54
+ }
55
+ async function submitAndConfirm(wireTransaction, readConnection, params, options) {
56
+ const signature = await sendTransaction(wireTransaction, options);
57
+ const timeoutMs = params.timeoutMs ?? 90_000;
58
+ const pollIntervalMs = params.pollIntervalMs ?? 1_000;
59
+ const deadline = Date.now() + timeoutMs;
60
+ for (;;) {
61
+ const { value } = await readConnection.getSignatureStatuses([signature]);
62
+ const status = value?.[0];
63
+ if (status &&
64
+ (status.confirmationStatus === "confirmed" || status.confirmationStatus === "finalized")) {
65
+ if (status.err) {
66
+ throw new Error(`transaction landed but FAILED on-chain: ${JSON.stringify(status.err)} (${signature})`);
67
+ }
68
+ return { signature, slot: status.slot };
69
+ }
70
+ const height = await readConnection.getBlockHeight("confirmed");
71
+ if (height > params.lastValidBlockHeight || Date.now() > deadline) {
72
+ throw new Error(`transaction expired without confirming (${signature})`);
73
+ }
74
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
75
+ }
76
+ }
77
+ return { sendTransaction, submitAndConfirm };
78
+ }