@0block.io/sdk 0.1.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 ADDED
@@ -0,0 +1,172 @@
1
+ # @0block.io/sdk
2
+
3
+ [![npm version](https://img.shields.io/npm/v/%400block.io%2Fsdk.svg)](https://www.npmjs.com/package/@0block.io/sdk)
4
+ [![types](https://img.shields.io/npm/types/%400block.io%2Fsdk.svg)](https://www.npmjs.com/package/@0block.io/sdk)
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.
10
+
11
+ - **Zero network calls at trade time.** Market context, lookup tables, and a
12
+ blockhash are resolved once server-side and streamed; the client assembles
13
+ the transaction synchronously.
14
+ - **One signature.** The end user is the only required signer, in both modes.
15
+ - **Fees are on-chain, not inputs.** Tenant and platform commissions are read
16
+ and enforced by the programs; the SDK never accepts a fee parameter.
17
+ - **External-signer native.** Returns the exact message bytes to sign, for
18
+ wallets, signing services, or HSMs — no `Keypair` ever touches the SDK.
19
+
20
+ ## Installation
21
+
22
+ ```sh
23
+ npm install @0block.io/sdk
24
+ ```
25
+
26
+ Requires Node ≥ 20 server-side; browser builds work with any modern bundler.
27
+
28
+ ## Modes
29
+
30
+ The mode is fixed at initialization:
31
+
32
+ ```ts
33
+ import { createContext } from "@0block.io/sdk";
34
+
35
+ // Tenant mode — swaps route through your tenant wrapper program.
36
+ // Your tenant fee (set on-chain) and the 0Block fee apply.
37
+ const ctx = createContext({
38
+ tenant: {
39
+ wrapperProgramId: "<your tenant wrapper program id>",
40
+ tenantFeeVault: "<your tenant fee vault>",
41
+ },
42
+ });
43
+
44
+ // Direct mode — swaps invoke the 0Block router directly.
45
+ // Only the 0Block platform fee applies.
46
+ const ctx = createContext();
47
+ ```
48
+
49
+ ## Quick start
50
+
51
+ ### 1. Backend — resolve market context once, stream it
52
+
53
+ ```ts
54
+ import { connectionFetcher, resolvePumpfunMarketContext } from "@0block.io/sdk";
55
+
56
+ const fetcher = connectionFetcher(connection);
57
+ const market = await resolvePumpfunMarketContext(fetcher, { mint });
58
+ // `market` is plain strings — push it to clients over your existing
59
+ // stream together with quotes and a FINALIZED blockhash.
60
+ ```
61
+
62
+ ### 2. Backend ops — shared lookup table, once per venue set
63
+
64
+ ```ts
65
+ import {
66
+ buildLookupTableAddresses,
67
+ buildInitLookupTableInstructions,
68
+ } from "@0block.io/sdk";
69
+
70
+ const addresses = buildLookupTableAddresses({ context: ctx, market });
71
+ const { tableAddress, transactions } = buildInitLookupTableInstructions({
72
+ authority: opsKey,
73
+ recentSlot,
74
+ addresses,
75
+ });
76
+ // Send each batch as one transaction, in order. Wait ~1 slot before first use.
77
+ ```
78
+
79
+ ### 3. Client — preset → unsigned transaction → your signer
80
+
81
+ ```ts
82
+ import { applyPreset, buildPumpfunSwapTransaction } from "@0block.io/sdk";
83
+
84
+ const { minReturn, tip, computeBudget } = applyPreset(
85
+ { slippagePct: 20, prioritySol: 0.001, bribeSol: 0.001 },
86
+ { expectedOut: quote.expectedOut, tipAccount: streamed.tipAccount }
87
+ );
88
+
89
+ const built = buildPumpfunSwapTransaction({
90
+ context: ctx,
91
+ market,
92
+ user: wallet.address,
93
+ side: "buy",
94
+ amountIn: 10_000_000n, // 0.01 SOL
95
+ minReturn,
96
+ recentBlockhash: streamed.finalizedBlockhash,
97
+ lookupTables: streamed.lookupTables,
98
+ tip,
99
+ computeBudget,
100
+ });
101
+
102
+ // built.messageBytes → sign with the user's key (exactly one signature)
103
+ // built.orderId → correlate the fill via SwapResultEvent
104
+ // built.estimatedSize → wire size, pre-checked against the 1232-byte limit
105
+ ```
106
+
107
+ Attach the signature and get wire bytes with `serializeWithSignatures(built, [sig])`,
108
+ then submit through the 0Block gateway.
109
+
110
+ ### 4. Indexer — fee accounting from confirmed transactions
111
+
112
+ ```ts
113
+ import { decodeSwapResultEvents } from "@0block.io/sdk";
114
+
115
+ const events = decodeSwapResultEvents(tx.meta.logMessages);
116
+ // [{ orderId, status, commissions: { feeMint, totalFeeAmount,
117
+ // items: [{ kind: "platform" | "tenant", bps, amount, tenantProgram }] } }]
118
+ ```
119
+
120
+ ## API overview
121
+
122
+ | Runs | Function | Purpose |
123
+ |---|---|---|
124
+ | anywhere | `createContext` | Resolve tenant vs direct mode from init params |
125
+ | backend | `resolvePumpfunMarketContext` | Chain-derived accounts for a mint, as streamable strings |
126
+ | backend | `buildLookupTableAddresses` / `buildInitLookupTableInstructions` | Create the shared static-accounts table |
127
+ | client | `applyPreset` | Slippage % / priority SOL / bribe SOL → build params |
128
+ | client | `buildPumpfunSwapTransaction` | Complete unsigned v0 transaction (offline) |
129
+ | client | `serializeWithSignatures` | Attach external signatures → wire bytes |
130
+ | indexer | `decodeSwapResultEvents` | `SwapResultEvent` → normalized commission breakdown |
131
+
132
+ Lower-level building blocks (`buildPumpfunSwapInstruction`, PDA derivations,
133
+ wSOL wrap/unwrap helpers, tip and compute-budget builders, vendored IDLs) are
134
+ exported as well.
135
+
136
+ ## Operational notes
137
+
138
+ - **Tenant mode prerequisites.** Your wrapper must be the build in which the
139
+ `wrapper_config` PDA signs the router CPI, and the router's
140
+ `TenantConfig.tenant_authority` must equal that PDA
141
+ (`deriveWrapperConfig(wrapperProgramId)`). Otherwise swaps fail
142
+ `UnauthorizedTenant`.
143
+ - **Direct mode prerequisite.** The router requires *a* `TenantConfig` account
144
+ in the `tenant_cfg` slot even on direct calls (ignored for fees). The router
145
+ operator provisions a standing "direct" config; override its key with
146
+ `directTenantConfigKey` if needed.
147
+ - **Tips must stay static message keys.** The gateway verifies the tip transfer
148
+ on raw bytes without resolving lookup tables. The builder masks tip accounts
149
+ out of table copies defensively — never add tip accounts to an ALT.
150
+ - **Use a FINALIZED blockhash.** The gateway forwards to a different staked
151
+ node; a `confirmed` hash may be unknown there.
152
+ - **Native SOL handling.** Buys wrap SOL → wSOL in-transaction (idempotent ATA
153
+ + transfer + SyncNative); sells close the wSOL ATA back to SOL. Opt out with
154
+ `wrapSol` / `unwrapWsol` if your users manage wSOL themselves.
155
+ - **Venue coverage.** v1 ships the pump.fun bonding-curve venue (buy/sell,
156
+ single hop). Additional venues follow the same shape — one
157
+ remaining-accounts builder per venue.
158
+
159
+ ## Development
160
+
161
+ ```sh
162
+ npm install
163
+ npm test # compiles, then runs the offline smoke test (no network, no keys)
164
+ ```
165
+
166
+ The vendored IDLs under `src/idl/` are generated from the on-chain programs'
167
+ Anchor builds. Regenerate them after any program change — account ordering and
168
+ discriminators must match on-chain reality.
169
+
170
+ ## License
171
+
172
+ UNLICENSED — all rights reserved. Contact 0block.io for usage terms.
package/dist/alt.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import type { ZeroBlockContext } from "./context.js";
3
+ import type { PumpfunMarketContext } from "./types.js";
4
+ /**
5
+ * The static account set for pump.fun swaps under a context — everything that
6
+ * is identical across users and trades. Load these into a shared address
7
+ * lookup table once (ops), and every user's v0 transaction references it.
8
+ *
9
+ * Never add gateway tip accounts: the gateway requires the tip destination as
10
+ * a static message key (the build path masks it defensively either way).
11
+ */
12
+ export declare function buildLookupTableAddresses(params: {
13
+ context: ZeroBlockContext;
14
+ /** Any market context for the venue — only its static venue fields are used. */
15
+ market: Pick<PumpfunMarketContext, "pumpfunProgram" | "pumpfunFeeProgram" | "global" | "eventAuthority" | "globalVolumeAccumulator" | "feeConfig" | "buybackFeeRecipients">;
16
+ extra?: (string | web3.PublicKey)[];
17
+ }): web3.PublicKey[];
18
+ export interface InitLookupTableResult {
19
+ tableAddress: web3.PublicKey;
20
+ /**
21
+ * Instruction batches: send each inner array as one transaction, in order
22
+ * (create+first extend, then further extends of ≤20 addresses). Wait ~1
23
+ * slot after the last extend before first use.
24
+ */
25
+ transactions: web3.TransactionInstruction[][];
26
+ }
27
+ /** Build the create/extend instruction batches for a shared lookup table (ops path). */
28
+ export declare function buildInitLookupTableInstructions(params: {
29
+ authority: web3.PublicKey;
30
+ payer?: web3.PublicKey;
31
+ /** A recent (finalized) slot, required by createLookupTable. */
32
+ recentSlot: number;
33
+ addresses: web3.PublicKey[];
34
+ }): InitLookupTableResult;
package/dist/alt.js ADDED
@@ -0,0 +1,83 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import { ASSOCIATED_TOKEN_PROGRAM_ID, INSTRUCTIONS_SYSVAR_ID, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, WSOL_MINT, } from "./constants.js";
3
+ import { deriveAta, deriveRouterGlobalConfig, deriveRouterTenantConfig, deriveWrapperConfig, toPk, } from "./pda.js";
4
+ /**
5
+ * The static account set for pump.fun swaps under a context — everything that
6
+ * is identical across users and trades. Load these into a shared address
7
+ * lookup table once (ops), and every user's v0 transaction references it.
8
+ *
9
+ * Never add gateway tip accounts: the gateway requires the tip destination as
10
+ * a static message key (the build path masks it defensively either way).
11
+ */
12
+ export function buildLookupTableAddresses(params) {
13
+ const { context } = params;
14
+ const routerProgramId = context.routerProgramId;
15
+ const zeroblockFeeVault = context.zeroblockFeeVault;
16
+ const tenantAddresses = context.mode === "tenant"
17
+ ? [
18
+ context.wrapperProgramId,
19
+ deriveWrapperConfig(context.wrapperProgramId),
20
+ deriveRouterTenantConfig(routerProgramId, context.wrapperProgramId),
21
+ context.tenantFeeVault,
22
+ deriveAta(context.tenantFeeVault, WSOL_MINT, TOKEN_PROGRAM_ID),
23
+ ]
24
+ : [deriveRouterTenantConfig(routerProgramId, context.directTenantConfigKey)];
25
+ const addresses = [
26
+ routerProgramId,
27
+ deriveRouterGlobalConfig(routerProgramId),
28
+ ...tenantAddresses,
29
+ zeroblockFeeVault,
30
+ deriveAta(zeroblockFeeVault, WSOL_MINT, TOKEN_PROGRAM_ID),
31
+ TOKEN_PROGRAM_ID,
32
+ TOKEN_2022_PROGRAM_ID,
33
+ ASSOCIATED_TOKEN_PROGRAM_ID,
34
+ web3.SystemProgram.programId,
35
+ INSTRUCTIONS_SYSVAR_ID,
36
+ WSOL_MINT,
37
+ toPk(params.market.pumpfunProgram),
38
+ toPk(params.market.global),
39
+ toPk(params.market.eventAuthority),
40
+ toPk(params.market.globalVolumeAccumulator),
41
+ toPk(params.market.feeConfig),
42
+ toPk(params.market.pumpfunFeeProgram),
43
+ ...params.market.buybackFeeRecipients.map((r) => toPk(r)),
44
+ ...(params.extra ?? []).map((a) => toPk(a)),
45
+ ];
46
+ const seen = new Set();
47
+ return addresses.filter((a) => {
48
+ const k = a.toBase58();
49
+ if (seen.has(k))
50
+ return false;
51
+ seen.add(k);
52
+ return true;
53
+ });
54
+ }
55
+ /** Build the create/extend instruction batches for a shared lookup table (ops path). */
56
+ export function buildInitLookupTableInstructions(params) {
57
+ const payer = params.payer ?? params.authority;
58
+ const CHUNK = 20;
59
+ const [createIx, tableAddress] = web3.AddressLookupTableProgram.createLookupTable({
60
+ authority: params.authority,
61
+ payer,
62
+ recentSlot: params.recentSlot,
63
+ });
64
+ const transactions = [];
65
+ const first = web3.AddressLookupTableProgram.extendLookupTable({
66
+ payer,
67
+ authority: params.authority,
68
+ lookupTable: tableAddress,
69
+ addresses: params.addresses.slice(0, CHUNK),
70
+ });
71
+ transactions.push([createIx, first]);
72
+ for (let i = CHUNK; i < params.addresses.length; i += CHUNK) {
73
+ transactions.push([
74
+ web3.AddressLookupTableProgram.extendLookupTable({
75
+ payer,
76
+ authority: params.authority,
77
+ lookupTable: tableAddress,
78
+ addresses: params.addresses.slice(i, i + CHUNK),
79
+ }),
80
+ ]);
81
+ }
82
+ return { tableAddress, transactions };
83
+ }
@@ -0,0 +1,8 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import type { ComputeBudgetParams } from "./types.js";
3
+ /**
4
+ * Convert a UI-level "priority fee in SOL" (the preset's Priority field) into
5
+ * compute-budget instructions: a CU limit plus a per-CU price such that the
6
+ * total priority fee equals approximately `prioritySol`.
7
+ */
8
+ export declare function buildComputeBudgetInstructions(params: ComputeBudgetParams): web3.TransactionInstruction[];
@@ -0,0 +1,25 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import { DEFAULT_COMPUTE_UNIT_LIMIT } from "./constants.js";
3
+ /**
4
+ * Convert a UI-level "priority fee in SOL" (the preset's Priority field) into
5
+ * compute-budget instructions: a CU limit plus a per-CU price such that the
6
+ * total priority fee equals approximately `prioritySol`.
7
+ */
8
+ export function buildComputeBudgetInstructions(params) {
9
+ const units = params.computeUnitLimit ?? DEFAULT_COMPUTE_UNIT_LIMIT;
10
+ if (!Number.isInteger(units) || units <= 0 || units > 1_400_000) {
11
+ throw new Error("computeUnitLimit must be an integer in (0, 1_400_000]");
12
+ }
13
+ if (!(params.prioritySol >= 0)) {
14
+ throw new Error("prioritySol must be >= 0");
15
+ }
16
+ const ixs = [
17
+ web3.ComputeBudgetProgram.setComputeUnitLimit({ units }),
18
+ ];
19
+ // total lamports = units * microLamports / 1e6 => micro-lamports per CU:
20
+ const microLamports = BigInt(Math.round((params.prioritySol * 1e9 * 1e6) / units));
21
+ if (microLamports > 0n) {
22
+ ixs.push(web3.ComputeBudgetProgram.setComputeUnitPrice({ microLamports }));
23
+ }
24
+ return ixs;
25
+ }
@@ -0,0 +1,45 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ /** Solana wire-format ceiling for a serialized transaction (packet MTU). */
3
+ export declare const MAX_TX_SIZE = 1232;
4
+ /** 0Block DEX router program (the default when no tenant is configured). */
5
+ export declare const ROUTER_PROGRAM_ID: web3.PublicKey;
6
+ /** 0Block platform fee authority (owner of 0Block fee token accounts). */
7
+ export declare const ZEROBLOCK_FEE_VAULT: web3.PublicKey;
8
+ export declare const TOKEN_PROGRAM_ID: web3.PublicKey;
9
+ export declare const TOKEN_2022_PROGRAM_ID: web3.PublicKey;
10
+ export declare const ASSOCIATED_TOKEN_PROGRAM_ID: web3.PublicKey;
11
+ export declare const WSOL_MINT: web3.PublicKey;
12
+ export declare const USDC_MINT: web3.PublicKey;
13
+ export declare const USD1_MINT: web3.PublicKey;
14
+ export declare const INSTRUCTIONS_SYSVAR_ID: web3.PublicKey;
15
+ /**
16
+ * Mints the router accepts as the fee mint. Fee base = amount_in when the
17
+ * SOURCE mint is allowlisted, else amount_out (see router config.rs fee math).
18
+ */
19
+ export declare const FEE_MINT_ALLOWLIST: readonly string[];
20
+ export declare const PUMPFUN_PROGRAM_ID: web3.PublicKey;
21
+ export declare const PUMPFUN_FEE_PROGRAM_ID: web3.PublicKey;
22
+ export declare const SEED_WRAPPER_CONFIG: Uint8Array<ArrayBuffer>;
23
+ export declare const SEED_ROUTER_GLOBAL_CONFIG: Uint8Array<ArrayBuffer>;
24
+ export declare const SEED_ROUTER_TENANT: Uint8Array<ArrayBuffer>;
25
+ export declare const SEED_PUMPFUN_GLOBAL: Uint8Array<ArrayBuffer>;
26
+ export declare const SEED_PUMPFUN_BONDING_CURVE: Uint8Array<ArrayBuffer>;
27
+ export declare const SEED_PUMPFUN_BONDING_CURVE_V2: Uint8Array<ArrayBuffer>;
28
+ export declare const SEED_PUMPFUN_EVENT_AUTHORITY: Uint8Array<ArrayBuffer>;
29
+ export declare const SEED_PUMPFUN_CREATOR_VAULT: Uint8Array<ArrayBuffer>;
30
+ export declare const SEED_PUMPFUN_GLOBAL_VOLUME: Uint8Array<ArrayBuffer>;
31
+ export declare const SEED_PUMPFUN_USER_VOLUME: Uint8Array<ArrayBuffer>;
32
+ export declare const SEED_PUMPFUN_FEE_CONFIG: Uint8Array<ArrayBuffer>;
33
+ /**
34
+ * Byte offset of Global.buyback_fee_recipients ([Pubkey; 8]) inside the
35
+ * pump.fun Global account data (8-byte discriminator + preceding fields).
36
+ */
37
+ export declare const PUMPFUN_GLOBAL_BUYBACK_RECIPIENTS_OFFSET = 741;
38
+ /** Default gateway tip when the caller does not specify one (0.001 SOL). */
39
+ export declare const DEFAULT_TIP_LAMPORTS = 1000000n;
40
+ /**
41
+ * Default compute-unit limit for a single-hop wrapper swap. Router swaps CPI
42
+ * through the wrapper into the venue program, so they are heavier than a
43
+ * direct venue call.
44
+ */
45
+ export declare const DEFAULT_COMPUTE_UNIT_LIMIT = 400000;
@@ -0,0 +1,54 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ /** Solana wire-format ceiling for a serialized transaction (packet MTU). */
3
+ export const MAX_TX_SIZE = 1232;
4
+ /** 0Block DEX router program (the default when no tenant is configured). */
5
+ export const ROUTER_PROGRAM_ID = new web3.PublicKey("zeroRxfemoQCz6JT7RyShjuuRCN7VZof1WSekfXhmEn");
6
+ /** 0Block platform fee authority (owner of 0Block fee token accounts). */
7
+ export const ZEROBLOCK_FEE_VAULT = new web3.PublicKey("zerobvXpiuDCSrpmw1dJNbfAXPLp8hFYeQ62u4XRQZf");
8
+ export const TOKEN_PROGRAM_ID = new web3.PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
9
+ export const TOKEN_2022_PROGRAM_ID = new web3.PublicKey("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
10
+ export const ASSOCIATED_TOKEN_PROGRAM_ID = new web3.PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
11
+ export const WSOL_MINT = new web3.PublicKey("So11111111111111111111111111111111111111112");
12
+ export const USDC_MINT = new web3.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
13
+ export const USD1_MINT = new web3.PublicKey("USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB");
14
+ export const INSTRUCTIONS_SYSVAR_ID = new web3.PublicKey("Sysvar1nstructions1111111111111111111111111");
15
+ /**
16
+ * Mints the router accepts as the fee mint. Fee base = amount_in when the
17
+ * SOURCE mint is allowlisted, else amount_out (see router config.rs fee math).
18
+ */
19
+ export const FEE_MINT_ALLOWLIST = [
20
+ web3.SystemProgram.programId.toBase58(),
21
+ WSOL_MINT.toBase58(),
22
+ USDC_MINT.toBase58(),
23
+ USD1_MINT.toBase58(),
24
+ ];
25
+ // ---------------------------------------------------------------------------
26
+ // pump.fun (the v1 venue)
27
+ // ---------------------------------------------------------------------------
28
+ export const PUMPFUN_PROGRAM_ID = new web3.PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
29
+ export const PUMPFUN_FEE_PROGRAM_ID = new web3.PublicKey("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ");
30
+ const te = new TextEncoder();
31
+ export const SEED_WRAPPER_CONFIG = te.encode("tenant_wrapper_config");
32
+ export const SEED_ROUTER_GLOBAL_CONFIG = te.encode("config");
33
+ export const SEED_ROUTER_TENANT = te.encode("tenant");
34
+ export const SEED_PUMPFUN_GLOBAL = te.encode("global");
35
+ export const SEED_PUMPFUN_BONDING_CURVE = te.encode("bonding-curve");
36
+ export const SEED_PUMPFUN_BONDING_CURVE_V2 = te.encode("bonding-curve-v2");
37
+ export const SEED_PUMPFUN_EVENT_AUTHORITY = te.encode("__event_authority");
38
+ export const SEED_PUMPFUN_CREATOR_VAULT = te.encode("creator-vault");
39
+ export const SEED_PUMPFUN_GLOBAL_VOLUME = te.encode("global_volume_accumulator");
40
+ export const SEED_PUMPFUN_USER_VOLUME = te.encode("user_volume_accumulator");
41
+ export const SEED_PUMPFUN_FEE_CONFIG = te.encode("fee_config");
42
+ /**
43
+ * Byte offset of Global.buyback_fee_recipients ([Pubkey; 8]) inside the
44
+ * pump.fun Global account data (8-byte discriminator + preceding fields).
45
+ */
46
+ export const PUMPFUN_GLOBAL_BUYBACK_RECIPIENTS_OFFSET = 741;
47
+ /** Default gateway tip when the caller does not specify one (0.001 SOL). */
48
+ export const DEFAULT_TIP_LAMPORTS = 1000000n;
49
+ /**
50
+ * Default compute-unit limit for a single-hop wrapper swap. Router swaps CPI
51
+ * through the wrapper into the venue program, so they are heavier than a
52
+ * direct venue call.
53
+ */
54
+ export const DEFAULT_COMPUTE_UNIT_LIMIT = 400_000;
@@ -0,0 +1,47 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import type { Address } from "./types.js";
3
+ /** Tenant initialization: the tenant's wrapper program + fee vault. */
4
+ export interface TenantInit {
5
+ /** The tenant's deployed wrapper program. */
6
+ wrapperProgramId: Address;
7
+ /** Tenant fee authority (owns the tenant fee token accounts). */
8
+ tenantFeeVault: Address;
9
+ }
10
+ export interface CreateContextParams {
11
+ /**
12
+ * Provide to route swaps through the tenant's wrapper program (tenant fees
13
+ * apply). Omit to invoke the 0Block router directly with 0Block fee
14
+ * accounts only.
15
+ */
16
+ tenant?: TenantInit;
17
+ /** Router program id. Defaults to the mainnet router. */
18
+ routerProgramId?: Address;
19
+ /** 0Block platform fee authority. Defaults to the mainnet vault. */
20
+ zeroblockFeeVault?: Address;
21
+ /**
22
+ * Direct mode only: the wrapper key whose router TenantConfig PDA is passed
23
+ * as `tenant_cfg` (the account must exist; the router ignores it for fee
24
+ * purposes on direct calls). Defaults to the default pubkey — the router
25
+ * operator initializes this "direct" TenantConfig once.
26
+ */
27
+ directTenantConfigKey?: Address;
28
+ }
29
+ /** Resolved build context: tenant-wrapped or direct-to-router. */
30
+ export type ZeroBlockContext = {
31
+ mode: "tenant";
32
+ routerProgramId: web3.PublicKey;
33
+ zeroblockFeeVault: web3.PublicKey;
34
+ wrapperProgramId: web3.PublicKey;
35
+ tenantFeeVault: web3.PublicKey;
36
+ } | {
37
+ mode: "direct";
38
+ routerProgramId: web3.PublicKey;
39
+ zeroblockFeeVault: web3.PublicKey;
40
+ directTenantConfigKey: web3.PublicKey;
41
+ };
42
+ /**
43
+ * Resolve how transactions are built. With a `tenant`, swaps go through the
44
+ * tenant's wrapper program (tenant + 0Block fees); without one, they invoke
45
+ * the 0Block router directly (0Block fee only).
46
+ */
47
+ export declare function createContext(params?: CreateContextParams): ZeroBlockContext;
@@ -0,0 +1,27 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import { ROUTER_PROGRAM_ID, ZEROBLOCK_FEE_VAULT } from "./constants.js";
3
+ import { toPk } from "./pda.js";
4
+ /**
5
+ * Resolve how transactions are built. With a `tenant`, swaps go through the
6
+ * tenant's wrapper program (tenant + 0Block fees); without one, they invoke
7
+ * the 0Block router directly (0Block fee only).
8
+ */
9
+ export function createContext(params = {}) {
10
+ const routerProgramId = toPk(params.routerProgramId ?? ROUTER_PROGRAM_ID);
11
+ const zeroblockFeeVault = toPk(params.zeroblockFeeVault ?? ZEROBLOCK_FEE_VAULT);
12
+ if (params.tenant) {
13
+ return {
14
+ mode: "tenant",
15
+ routerProgramId,
16
+ zeroblockFeeVault,
17
+ wrapperProgramId: toPk(params.tenant.wrapperProgramId),
18
+ tenantFeeVault: toPk(params.tenant.tenantFeeVault),
19
+ };
20
+ }
21
+ return {
22
+ mode: "direct",
23
+ routerProgramId,
24
+ zeroblockFeeVault,
25
+ directTenantConfigKey: toPk(params.directTenantConfigKey ?? web3.PublicKey.default),
26
+ };
27
+ }
@@ -0,0 +1,9 @@
1
+ import type { DecodedSwapResult } from "./types.js";
2
+ /**
3
+ * Decode every router SwapResultEvent found in a transaction's log messages
4
+ * (tx.meta.logMessages). This is the accounting hook: on success the event
5
+ * carries the full CommissionBreakdown — fee mint, base, total, and one item
6
+ * per recipient (platform vs tenant, with the tenant wrapper program id) —
7
+ * plus the order_id that correlates back to the submitted order.
8
+ */
9
+ export declare function decodeSwapResultEvents(logMessages: readonly string[]): DecodedSwapResult[];
package/dist/events.js ADDED
@@ -0,0 +1,106 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ // Anchor ships CJS-only; a default import + destructure works everywhere
3
+ // (bundlers and raw Node ESM), named imports do not.
4
+ import anchor from "@coral-xyz/anchor";
5
+ const { BN, BorshEventCoder } = anchor;
6
+ import { ROUTER_IDL } from "./idl/router.js";
7
+ // Event layouts derive from the vendored (trimmed) router IDL.
8
+ const eventCoder = new BorshEventCoder(ROUTER_IDL);
9
+ const PROGRAM_DATA_PREFIX = "Program data: ";
10
+ function toBigint(v) {
11
+ if (typeof v === "bigint")
12
+ return v;
13
+ if (BN.isBN(v))
14
+ return BigInt(v.toString());
15
+ return BigInt(String(v));
16
+ }
17
+ function toBase58(v) {
18
+ return v instanceof web3.PublicKey ? v.toBase58() : new web3.PublicKey(String(v)).toBase58();
19
+ }
20
+ /** Case-insensitive enum-variant lookup ({Success:{…}} vs {success:{…}}). */
21
+ function variant(obj, name) {
22
+ for (const key of Object.keys(obj)) {
23
+ if (key.toLowerCase() === name.toLowerCase())
24
+ return obj[key];
25
+ }
26
+ return undefined;
27
+ }
28
+ /**
29
+ * Anchor decodes tuple enum variants as { "0": value } (numeric string keys);
30
+ * named-field variants decode as plain objects. Normalize to the inner value.
31
+ */
32
+ function unwrapTuple(value) {
33
+ if (Array.isArray(value))
34
+ return value[0];
35
+ const obj = value;
36
+ if (obj && typeof obj === "object" && "0" in obj && Object.keys(obj).length === 1) {
37
+ return obj["0"];
38
+ }
39
+ return obj;
40
+ }
41
+ /**
42
+ * Decode every router SwapResultEvent found in a transaction's log messages
43
+ * (tx.meta.logMessages). This is the accounting hook: on success the event
44
+ * carries the full CommissionBreakdown — fee mint, base, total, and one item
45
+ * per recipient (platform vs tenant, with the tenant wrapper program id) —
46
+ * plus the order_id that correlates back to the submitted order.
47
+ */
48
+ export function decodeSwapResultEvents(logMessages) {
49
+ const results = [];
50
+ for (const line of logMessages) {
51
+ const idx = line.indexOf(PROGRAM_DATA_PREFIX);
52
+ if (idx === -1)
53
+ continue;
54
+ const base64 = line.slice(idx + PROGRAM_DATA_PREFIX.length).trim();
55
+ let decoded = null;
56
+ try {
57
+ decoded = eventCoder.decode(base64);
58
+ }
59
+ catch {
60
+ continue; // some other program's data log
61
+ }
62
+ if (!decoded || decoded.name !== "SwapResultEvent")
63
+ continue;
64
+ const data = decoded.data;
65
+ const orderId = toBigint(data.order_id ?? data.orderId);
66
+ const resultData = (data.data ?? {});
67
+ const successRaw = variant(resultData, "Success");
68
+ if (successRaw) {
69
+ const success = unwrapTuple(successRaw);
70
+ const commissions = success.commissions;
71
+ const feeSource = variant((commissions.fee_source ?? commissions.feeSource ?? {}), "Input")
72
+ ? "input"
73
+ : "output";
74
+ const items = (commissions.items ?? []).map((item) => ({
75
+ kind: (variant((item.kind ?? {}), "Platform")
76
+ ? "platform"
77
+ : "tenant"),
78
+ bps: Number(item.bps),
79
+ amount: toBigint(item.amount),
80
+ tenantProgram: toBase58(item.tenant_program ?? item.tenantProgram),
81
+ }));
82
+ results.push({
83
+ orderId,
84
+ status: "success",
85
+ commissions: {
86
+ feeMint: toBase58(commissions.fee_mint ?? commissions.feeMint),
87
+ feeSource,
88
+ feeBaseAmount: toBigint(commissions.fee_base_amount ?? commissions.feeBaseAmount),
89
+ totalFeeAmount: toBigint(commissions.total_fee_amount ?? commissions.totalFeeAmount),
90
+ items,
91
+ },
92
+ });
93
+ continue;
94
+ }
95
+ const failureRaw = variant(resultData, "Failure");
96
+ if (failureRaw) {
97
+ const f = unwrapTuple(failureRaw);
98
+ results.push({
99
+ orderId,
100
+ status: "failure",
101
+ failure: { code: toBigint(f.code), message: String(f.message) },
102
+ });
103
+ }
104
+ }
105
+ return results;
106
+ }
@@ -0,0 +1,34 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import type { Address, PumpfunMarketContext } from "./types.js";
3
+ /**
4
+ * The one seam the resolver needs to read chain state. Implement it with a
5
+ * web3.js Connection (see connectionFetcher) or any RPC client. The builder
6
+ * itself never fetches — resolve once server-side, stream the context out.
7
+ */
8
+ export interface AccountFetcher {
9
+ getAccountInfo(address: web3.PublicKey): Promise<{
10
+ data: Uint8Array;
11
+ owner: web3.PublicKey;
12
+ } | null>;
13
+ }
14
+ /** AccountFetcher backed by a @solana/web3.js Connection. */
15
+ export declare function connectionFetcher(connection: web3.Connection): AccountFetcher;
16
+ /** Which token program owns a mint (classic SPL vs Token-2022). */
17
+ export declare function resolveTokenProgram(fetcher: AccountFetcher, mint: Address): Promise<web3.PublicKey>;
18
+ /** Global.fee_recipient (offset: discriminator + initialized flag + authority). */
19
+ export declare function fetchPumpfunGlobalFeeRecipient(fetcher: AccountFetcher, globalAccount: web3.PublicKey): Promise<web3.PublicKey>;
20
+ /** Non-zero entries of Global.buyback_fee_recipients ([Pubkey; 8]). */
21
+ export declare function fetchPumpfunBuybackFeeRecipients(fetcher: AccountFetcher, globalAccount: web3.PublicKey): Promise<web3.PublicKey[]>;
22
+ /** BondingCurve.creator (offset: discriminator + 5×u64 + bool). */
23
+ export declare function fetchPumpfunBondingCurveCreator(fetcher: AccountFetcher, bondingCurve: web3.PublicKey): Promise<web3.PublicKey>;
24
+ /**
25
+ * Resolve everything a pump.fun swap needs for one mint. Run this server-side
26
+ * next to an RPC (the tenant's backend) and stream the result to clients — every field
27
+ * is a plain base58 string. Only the per-user volume accumulator is left for
28
+ * build time (it depends on the user key).
29
+ */
30
+ export declare function resolvePumpfunMarketContext(fetcher: AccountFetcher, params: {
31
+ mint: Address;
32
+ pumpfunProgram?: Address;
33
+ pumpfunFeeProgram?: Address;
34
+ }): Promise<PumpfunMarketContext>;