@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.
@@ -0,0 +1,16 @@
1
+ export * from "./constants.js";
2
+ export * from "./types.js";
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";
5
+ export { type AccountFetcher, connectionFetcher, resolveTokenProgram, resolvePumpfunMarketContext, fetchPumpfunGlobalFeeRecipient, fetchPumpfunBondingCurveCreator, fetchPumpfunBuybackFeeRecipients, } from "./fetch.js";
6
+ export { buildPumpfunRemainingAccounts, type PumpfunRemainingAccountsParams, } from "./venues/pumpfun.js";
7
+ export { buildPumpfunSwapInstruction, decodeSwapData, randomOrderId, resolveFeeMint, type SwapInstructionResult, } from "./swap.js";
8
+ export { buildTipInstruction, maskTipAccountInTables } from "./tip.js";
9
+ export { buildComputeBudgetInstructions } from "./computeBudget.js";
10
+ export { createAtaIdempotentInstruction, syncNativeInstruction, closeAccountInstruction, wrapSolInstructions, unwrapWsolInstruction, } from "./wsol.js";
11
+ export { buildPumpfunSwapTransaction, serializeWithSignatures, toLookupTableAccount, } from "./transaction.js";
12
+ export { buildLookupTableAddresses, buildInitLookupTableInstructions, type InitLookupTableResult, } from "./alt.js";
13
+ export { applyPreset, slippagePctToBps, solToLamports, type PresetSwapInputs, type PresetSwapOutputs, } from "./presets.js";
14
+ export { decodeSwapResultEvents } from "./events.js";
15
+ export { TENANT_WRAPPER_IDL } from "./idl/tenantWrapper.js";
16
+ export { ROUTER_IDL } from "./idl/router.js";
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ // @0block/sdk — browser-first transaction builder for the 0Block DEX router.
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.
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
12
+ export * from "./constants.js";
13
+ export * from "./types.js";
14
+ export { createContext, } from "./context.js";
15
+ export { toPk, deriveAta, deriveWrapperConfig, deriveRouterGlobalConfig, deriveRouterTenantConfig, derivePumpfunGlobal, derivePumpfunBondingCurve, derivePumpfunBondingCurveV2, derivePumpfunEventAuthority, derivePumpfunCreatorVault, derivePumpfunGlobalVolumeAccumulator, derivePumpfunUserVolumeAccumulator, derivePumpfunFeeConfig, } from "./pda.js";
16
+ export { connectionFetcher, resolveTokenProgram, resolvePumpfunMarketContext, fetchPumpfunGlobalFeeRecipient, fetchPumpfunBondingCurveCreator, fetchPumpfunBuybackFeeRecipients, } from "./fetch.js";
17
+ export { buildPumpfunRemainingAccounts, } from "./venues/pumpfun.js";
18
+ export { buildPumpfunSwapInstruction, decodeSwapData, randomOrderId, resolveFeeMint, } from "./swap.js";
19
+ export { buildTipInstruction, maskTipAccountInTables } from "./tip.js";
20
+ export { buildComputeBudgetInstructions } from "./computeBudget.js";
21
+ export { createAtaIdempotentInstruction, syncNativeInstruction, closeAccountInstruction, wrapSolInstructions, unwrapWsolInstruction, } from "./wsol.js";
22
+ export { buildPumpfunSwapTransaction, serializeWithSignatures, toLookupTableAccount, } from "./transaction.js";
23
+ export { buildLookupTableAddresses, buildInitLookupTableInstructions, } from "./alt.js";
24
+ export { applyPreset, slippagePctToBps, solToLamports, } from "./presets.js";
25
+ export { decodeSwapResultEvents } from "./events.js";
26
+ export { TENANT_WRAPPER_IDL } from "./idl/tenantWrapper.js";
27
+ export { ROUTER_IDL } from "./idl/router.js";
package/dist/pda.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import type { Address } from "./types.js";
3
+ /** Normalize an Address (base58 string or PublicKey) to a PublicKey. */
4
+ export declare function toPk(address: Address): web3.PublicKey;
5
+ /** SPL associated token account for (owner, mint) under the given token program. */
6
+ export declare function deriveAta(owner: Address, mint: Address, tokenProgram?: Address): web3.PublicKey;
7
+ /** Tenant wrapper config PDA (seeds: ["tenant_wrapper_config"]). */
8
+ export declare function deriveWrapperConfig(wrapperProgramId: Address): web3.PublicKey;
9
+ /** Router GlobalConfig PDA (seeds: ["config"]). */
10
+ export declare function deriveRouterGlobalConfig(routerProgramId: Address): web3.PublicKey;
11
+ /** Router TenantConfig PDA (seeds: ["tenant", wrapper_program]). */
12
+ export declare function deriveRouterTenantConfig(routerProgramId: Address, wrapperProgramId: Address): web3.PublicKey;
13
+ export declare function derivePumpfunGlobal(programId: Address): web3.PublicKey;
14
+ export declare function derivePumpfunBondingCurve(programId: Address, mint: Address): web3.PublicKey;
15
+ export declare function derivePumpfunBondingCurveV2(programId: Address, mint: Address): web3.PublicKey;
16
+ export declare function derivePumpfunEventAuthority(programId: Address): web3.PublicKey;
17
+ export declare function derivePumpfunCreatorVault(programId: Address, creator: Address): web3.PublicKey;
18
+ export declare function derivePumpfunGlobalVolumeAccumulator(programId: Address): web3.PublicKey;
19
+ /** Per-user volume accumulator — the one pump.fun PDA derived at build time. */
20
+ export declare function derivePumpfunUserVolumeAccumulator(programId: Address, user: Address): web3.PublicKey;
21
+ export declare function derivePumpfunFeeConfig(feeProgramId: Address, pumpProgramId: Address): web3.PublicKey;
package/dist/pda.js ADDED
@@ -0,0 +1,53 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import { ASSOCIATED_TOKEN_PROGRAM_ID, SEED_PUMPFUN_BONDING_CURVE, SEED_PUMPFUN_BONDING_CURVE_V2, SEED_PUMPFUN_CREATOR_VAULT, SEED_PUMPFUN_EVENT_AUTHORITY, SEED_PUMPFUN_FEE_CONFIG, SEED_PUMPFUN_GLOBAL, SEED_PUMPFUN_GLOBAL_VOLUME, SEED_PUMPFUN_USER_VOLUME, SEED_ROUTER_GLOBAL_CONFIG, SEED_ROUTER_TENANT, SEED_WRAPPER_CONFIG, TOKEN_PROGRAM_ID, } from "./constants.js";
3
+ /** Normalize an Address (base58 string or PublicKey) to a PublicKey. */
4
+ export function toPk(address) {
5
+ return typeof address === "string" ? new web3.PublicKey(address) : address;
6
+ }
7
+ function pda(seeds, programId) {
8
+ return web3.PublicKey.findProgramAddressSync(seeds, programId)[0];
9
+ }
10
+ /** SPL associated token account for (owner, mint) under the given token program. */
11
+ export function deriveAta(owner, mint, tokenProgram = TOKEN_PROGRAM_ID) {
12
+ return pda([toPk(owner).toBytes(), toPk(tokenProgram).toBytes(), toPk(mint).toBytes()], ASSOCIATED_TOKEN_PROGRAM_ID);
13
+ }
14
+ /** Tenant wrapper config PDA (seeds: ["tenant_wrapper_config"]). */
15
+ export function deriveWrapperConfig(wrapperProgramId) {
16
+ return pda([SEED_WRAPPER_CONFIG], toPk(wrapperProgramId));
17
+ }
18
+ /** Router GlobalConfig PDA (seeds: ["config"]). */
19
+ export function deriveRouterGlobalConfig(routerProgramId) {
20
+ return pda([SEED_ROUTER_GLOBAL_CONFIG], toPk(routerProgramId));
21
+ }
22
+ /** Router TenantConfig PDA (seeds: ["tenant", wrapper_program]). */
23
+ export function deriveRouterTenantConfig(routerProgramId, wrapperProgramId) {
24
+ return pda([SEED_ROUTER_TENANT, toPk(wrapperProgramId).toBytes()], toPk(routerProgramId));
25
+ }
26
+ // ---------------------------------------------------------------------------
27
+ // pump.fun PDAs
28
+ // ---------------------------------------------------------------------------
29
+ export function derivePumpfunGlobal(programId) {
30
+ return pda([SEED_PUMPFUN_GLOBAL], toPk(programId));
31
+ }
32
+ export function derivePumpfunBondingCurve(programId, mint) {
33
+ return pda([SEED_PUMPFUN_BONDING_CURVE, toPk(mint).toBytes()], toPk(programId));
34
+ }
35
+ export function derivePumpfunBondingCurveV2(programId, mint) {
36
+ return pda([SEED_PUMPFUN_BONDING_CURVE_V2, toPk(mint).toBytes()], toPk(programId));
37
+ }
38
+ export function derivePumpfunEventAuthority(programId) {
39
+ return pda([SEED_PUMPFUN_EVENT_AUTHORITY], toPk(programId));
40
+ }
41
+ export function derivePumpfunCreatorVault(programId, creator) {
42
+ return pda([SEED_PUMPFUN_CREATOR_VAULT, toPk(creator).toBytes()], toPk(programId));
43
+ }
44
+ export function derivePumpfunGlobalVolumeAccumulator(programId) {
45
+ return pda([SEED_PUMPFUN_GLOBAL_VOLUME], toPk(programId));
46
+ }
47
+ /** Per-user volume accumulator — the one pump.fun PDA derived at build time. */
48
+ export function derivePumpfunUserVolumeAccumulator(programId, user) {
49
+ return pda([SEED_PUMPFUN_USER_VOLUME, toPk(user).toBytes()], toPk(programId));
50
+ }
51
+ export function derivePumpfunFeeConfig(feeProgramId, pumpProgramId) {
52
+ return pda([SEED_PUMPFUN_FEE_CONFIG, toPk(pumpProgramId).toBytes()], toPk(feeProgramId));
53
+ }
@@ -0,0 +1,23 @@
1
+ import type { Address, ComputeBudgetParams, TipParams, TradingPreset } from "./types.js";
2
+ export interface PresetSwapInputs {
3
+ /** Quoted expected output for the trade, raw units. */
4
+ expectedOut: bigint;
5
+ /** Allowlisted gateway tip account (from tenant/gateway config). */
6
+ tipAccount: Address;
7
+ computeUnitLimit?: number;
8
+ }
9
+ export interface PresetSwapOutputs {
10
+ /** expectedOut reduced by the preset's slippage tolerance. */
11
+ minReturn: bigint;
12
+ tip: TipParams | null;
13
+ computeBudget: ComputeBudgetParams | null;
14
+ }
15
+ /** Percent (UI) → basis points, clamped to [0, 10000]. */
16
+ export declare function slippagePctToBps(slippagePct: number): number;
17
+ export declare function solToLamports(sol: number): bigint;
18
+ /**
19
+ * Map a UI trading preset (Slippage %, Priority SOL, Bribe SOL) onto concrete
20
+ * build parameters. `mevMode` is intentionally not consumed here — it selects
21
+ * gateway-side submission behavior, not transaction content.
22
+ */
23
+ export declare function applyPreset(preset: TradingPreset, inputs: PresetSwapInputs): PresetSwapOutputs;
@@ -0,0 +1,36 @@
1
+ import { DEFAULT_COMPUTE_UNIT_LIMIT } from "./constants.js";
2
+ /** Percent (UI) → basis points, clamped to [0, 10000]. */
3
+ export function slippagePctToBps(slippagePct) {
4
+ if (!(slippagePct >= 0) || slippagePct > 100) {
5
+ throw new Error("slippagePct must be within [0, 100]");
6
+ }
7
+ return Math.min(10_000, Math.round(slippagePct * 100));
8
+ }
9
+ export function solToLamports(sol) {
10
+ if (!(sol >= 0))
11
+ throw new Error("SOL amount must be >= 0");
12
+ return BigInt(Math.round(sol * 1e9));
13
+ }
14
+ /**
15
+ * Map a UI trading preset (Slippage %, Priority SOL, Bribe SOL) onto concrete
16
+ * build parameters. `mevMode` is intentionally not consumed here — it selects
17
+ * gateway-side submission behavior, not transaction content.
18
+ */
19
+ export function applyPreset(preset, inputs) {
20
+ const bps = slippagePctToBps(preset.slippagePct);
21
+ const minReturn = (inputs.expectedOut * BigInt(10_000 - bps)) / 10000n;
22
+ if (minReturn <= 0n) {
23
+ throw new Error("minReturn collapsed to 0 — expectedOut too small for this slippage");
24
+ }
25
+ const tipLamports = solToLamports(preset.bribeSol);
26
+ return {
27
+ minReturn,
28
+ tip: tipLamports > 0n ? { account: inputs.tipAccount, lamports: tipLamports } : null,
29
+ computeBudget: preset.prioritySol > 0
30
+ ? {
31
+ prioritySol: preset.prioritySol,
32
+ computeUnitLimit: inputs.computeUnitLimit ?? DEFAULT_COMPUTE_UNIT_LIMIT,
33
+ }
34
+ : null,
35
+ };
36
+ }
package/dist/swap.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import type { ZeroBlockContext } from "./context.js";
3
+ import type { PumpfunMarketContext, SwapSide } from "./types.js";
4
+ /** Random u128 order id (correlates SwapResultEvent with an off-chain order). */
5
+ export declare function randomOrderId(): bigint;
6
+ /**
7
+ * The mint the router charges fees in: source if allowlisted, else
8
+ * destination. Mirrors the on-chain fee-base rule (input vs output).
9
+ */
10
+ export declare function resolveFeeMint(sourceMint: web3.PublicKey, destinationMint: web3.PublicKey): web3.PublicKey;
11
+ export interface SwapInstructionResult {
12
+ instruction: web3.TransactionInstruction;
13
+ orderId: bigint;
14
+ /** User source/destination token accounts, for wrap/unwrap composition. */
15
+ sourceTokenAccount: web3.PublicKey;
16
+ destinationTokenAccount: web3.PublicKey;
17
+ }
18
+ /**
19
+ * Build the swap instruction for a single-hop pump.fun leg — through the
20
+ * tenant's wrapper program (tenant context) or directly against the 0Block
21
+ * router (direct context). Pure and synchronous: all chain-derived inputs
22
+ * arrive via the market context; the only PDAs derived here depend on the
23
+ * user key.
24
+ */
25
+ export declare function buildPumpfunSwapInstruction(params: {
26
+ context: ZeroBlockContext;
27
+ market: PumpfunMarketContext;
28
+ user: web3.PublicKey;
29
+ side: SwapSide;
30
+ amountIn: bigint;
31
+ minReturn: bigint;
32
+ orderId?: bigint;
33
+ }): SwapInstructionResult;
34
+ /**
35
+ * Decode a swap instruction's data. Both programs share the `swap`
36
+ * discriminator (anchor hashes the instruction name), and the wrapper layout
37
+ * is a strict prefix of the router's (which appends `tenant_fee_bps:
38
+ * Option<u16>`) — so the router coder is tried FIRST: it fails on wrapper
39
+ * data (missing trailing option byte), while the wrapper coder would
40
+ * silently mis-decode router data by ignoring the tail.
41
+ */
42
+ export declare function decodeSwapData(data: Uint8Array): {
43
+ program: "tenant-wrapper" | "router";
44
+ name: string;
45
+ data: unknown;
46
+ };
package/dist/swap.js ADDED
@@ -0,0 +1,230 @@
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, BorshInstructionCoder } = anchor;
6
+ import { ASSOCIATED_TOKEN_PROGRAM_ID, FEE_MINT_ALLOWLIST, INSTRUCTIONS_SYSVAR_ID, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, WSOL_MINT, } from "./constants.js";
7
+ import { TENANT_WRAPPER_IDL } from "./idl/tenantWrapper.js";
8
+ import { ROUTER_IDL } from "./idl/router.js";
9
+ import { deriveAta, derivePumpfunUserVolumeAccumulator, deriveRouterGlobalConfig, deriveRouterTenantConfig, deriveWrapperConfig, toPk, } from "./pda.js";
10
+ import { buildPumpfunRemainingAccounts } from "./venues/pumpfun.js";
11
+ // One coder per program for the package lifetime; layouts derive from the
12
+ // vendored IDLs. NOTE: the raw (non-camelCased) IDLs are used, so encode()
13
+ // takes snake_case argument keys and PascalCase enum variants — verified
14
+ // against anchor 0.31.
15
+ const wrapperCoder = new BorshInstructionCoder(TENANT_WRAPPER_IDL);
16
+ const routerCoder = new BorshInstructionCoder(ROUTER_IDL);
17
+ /** Random u128 order id (correlates SwapResultEvent with an off-chain order). */
18
+ export function randomOrderId() {
19
+ const bytes = new Uint8Array(16);
20
+ globalThis.crypto.getRandomValues(bytes);
21
+ let n = 0n;
22
+ for (const b of bytes)
23
+ n = (n << 8n) | BigInt(b);
24
+ return n;
25
+ }
26
+ /**
27
+ * The mint the router charges fees in: source if allowlisted, else
28
+ * destination. Mirrors the on-chain fee-base rule (input vs output).
29
+ */
30
+ export function resolveFeeMint(sourceMint, destinationMint) {
31
+ if (FEE_MINT_ALLOWLIST.includes(sourceMint.toBase58()))
32
+ return sourceMint;
33
+ if (FEE_MINT_ALLOWLIST.includes(destinationMint.toBase58()))
34
+ return destinationMint;
35
+ throw new Error("Source or destination mint must be SOL/wSOL/USDC/USD1");
36
+ }
37
+ function bnFromBigint(value, name, bits) {
38
+ if (value < 0n || value >= 1n << BigInt(bits)) {
39
+ throw new Error(`${name} must fit u${bits}`);
40
+ }
41
+ return new BN(value.toString());
42
+ }
43
+ /**
44
+ * Build the swap instruction for a single-hop pump.fun leg — through the
45
+ * tenant's wrapper program (tenant context) or directly against the 0Block
46
+ * router (direct context). Pure and synchronous: all chain-derived inputs
47
+ * arrive via the market context; the only PDAs derived here depend on the
48
+ * user key.
49
+ */
50
+ export function buildPumpfunSwapInstruction(params) {
51
+ const { context, market, user, side } = params;
52
+ if (params.amountIn <= 0n)
53
+ throw new Error("amountIn must be > 0");
54
+ if (params.minReturn <= 0n)
55
+ throw new Error("minReturn must be > 0");
56
+ const routerProgramId = context.routerProgramId;
57
+ const zeroblockFeeVault = context.zeroblockFeeVault;
58
+ const pumpMint = toPk(market.mint);
59
+ const mintTokenProgram = toPk(market.mintTokenProgram);
60
+ const pumpProgram = toPk(market.pumpfunProgram);
61
+ // buy: wSOL -> token; sell: token -> wSOL. wSOL always rides the classic
62
+ // token program; the pump mint may live on Token-2022.
63
+ const sourceMint = side === "buy" ? WSOL_MINT : pumpMint;
64
+ const destinationMint = side === "buy" ? pumpMint : WSOL_MINT;
65
+ const sourceTokenProgram = side === "buy" ? TOKEN_PROGRAM_ID : mintTokenProgram;
66
+ const destinationTokenProgram = side === "buy" ? mintTokenProgram : TOKEN_PROGRAM_ID;
67
+ const sourceTokenAccount = deriveAta(user, sourceMint, sourceTokenProgram);
68
+ const destinationTokenAccount = deriveAta(user, destinationMint, destinationTokenProgram);
69
+ // Fee mint is the wSOL side for pump.fun swaps; fee ATAs belong to the fee
70
+ // authorities and are pre-created (and ALT-listed) by ops.
71
+ const feeMint = resolveFeeMint(sourceMint, destinationMint);
72
+ const feeTokenProgram = feeMint.equals(sourceMint)
73
+ ? sourceTokenProgram
74
+ : destinationTokenProgram;
75
+ const zeroblockFeeTokenAccount = deriveAta(zeroblockFeeVault, feeMint, feeTokenProgram);
76
+ const orderId = params.orderId ?? randomOrderId();
77
+ const dexVariant = side === "buy" ? { PumpfunBuy: {} } : { PumpfunSell: {} };
78
+ const amountIn = bnFromBigint(params.amountIn, "amountIn", 64);
79
+ const swapArgs = {
80
+ amount_in: amountIn,
81
+ min_return: bnFromBigint(params.minReturn, "minReturn", 64),
82
+ amounts: [amountIn],
83
+ routes: [[{ dexes: [dexVariant], weights: Buffer.from([100]) }]],
84
+ options: null,
85
+ };
86
+ // Random pick among the non-zero buyback recipients — the program validates
87
+ // membership on-chain, and ops pre-load all eight into the shared ALT.
88
+ const buyback = market.buybackFeeRecipients;
89
+ if (!buyback.length)
90
+ throw new Error("market.buybackFeeRecipients is empty");
91
+ const pick = new Uint32Array(1);
92
+ globalThis.crypto.getRandomValues(pick);
93
+ const buybackFeeRecipient = toPk(buyback[pick[0] % buyback.length]);
94
+ const remainingAccounts = buildPumpfunRemainingAccounts(side, {
95
+ dexProgramId: pumpProgram,
96
+ swapAuthority: user,
97
+ swapSourceToken: sourceTokenAccount,
98
+ swapDestinationToken: destinationTokenAccount,
99
+ pumpfunGlobal: toPk(market.global),
100
+ pumpfunFeeRecipient: toPk(market.feeRecipient),
101
+ pumpfunMint: pumpMint,
102
+ pumpfunBondingCurve: toPk(market.bondingCurve),
103
+ pumpfunAssociatedBondingCurve: toPk(market.associatedBondingCurve),
104
+ systemProgramId: web3.SystemProgram.programId,
105
+ tokenProgramId: mintTokenProgram,
106
+ pumpfunCreatorVault: toPk(market.creatorVault),
107
+ pumpfunEventAuthority: toPk(market.eventAuthority),
108
+ pumpfunGlobalVolumeAccumulator: toPk(market.globalVolumeAccumulator),
109
+ pumpfunUserVolumeAccumulator: derivePumpfunUserVolumeAccumulator(pumpProgram, user),
110
+ pumpfunFeeConfig: toPk(market.feeConfig),
111
+ pumpfunFeeProgram: toPk(market.pumpfunFeeProgram),
112
+ pumpfunBondingCurveV2: toPk(market.bondingCurveV2),
113
+ pumpfunBuybackFeeRecipient: buybackFeeRecipient,
114
+ extraTokenProgramId: mintTokenProgram.equals(TOKEN_2022_PROGRAM_ID)
115
+ ? TOKEN_PROGRAM_ID
116
+ : null,
117
+ });
118
+ const globalCfg = deriveRouterGlobalConfig(routerProgramId);
119
+ let programId;
120
+ let data;
121
+ let fixedKeys;
122
+ if (context.mode === "tenant") {
123
+ // Tenant path: wrapper swap ix. The wrapper reads its stored fee_bps and
124
+ // PDA-signs the router CPI, so the user is the only tx-level signer.
125
+ const wrapperProgramId = context.wrapperProgramId;
126
+ const tenantFeeVault = context.tenantFeeVault;
127
+ const tenantFeeTokenAccount = deriveAta(tenantFeeVault, feeMint, feeTokenProgram);
128
+ programId = wrapperProgramId;
129
+ data = wrapperCoder.encode("swap", {
130
+ args: swapArgs,
131
+ order_id: bnFromBigint(orderId, "orderId", 128),
132
+ });
133
+ // Fixed accounts in the exact order the wrapper IDL declares them.
134
+ fixedKeys = [
135
+ { pubkey: user, isWritable: true, isSigner: true },
136
+ { pubkey: deriveWrapperConfig(wrapperProgramId), isWritable: false, isSigner: false },
137
+ { pubkey: routerProgramId, isWritable: false, isSigner: false },
138
+ { pubkey: sourceTokenAccount, isWritable: true, isSigner: false },
139
+ { pubkey: destinationTokenAccount, isWritable: true, isSigner: false },
140
+ { pubkey: sourceMint, isWritable: false, isSigner: false },
141
+ { pubkey: destinationMint, isWritable: false, isSigner: false },
142
+ { pubkey: globalCfg, isWritable: false, isSigner: false },
143
+ {
144
+ pubkey: deriveRouterTenantConfig(routerProgramId, wrapperProgramId),
145
+ isWritable: false,
146
+ isSigner: false,
147
+ },
148
+ // No tenant_authority account: the wrapper_config PDA signs the router
149
+ // CPI via seeds, so the user is the only transaction-level signer.
150
+ { pubkey: zeroblockFeeVault, isWritable: true, isSigner: false },
151
+ { pubkey: tenantFeeVault, isWritable: true, isSigner: false },
152
+ { pubkey: zeroblockFeeTokenAccount, isWritable: true, isSigner: false },
153
+ { pubkey: tenantFeeTokenAccount, isWritable: true, isSigner: false },
154
+ { pubkey: sourceTokenProgram, isWritable: false, isSigner: false },
155
+ { pubkey: destinationTokenProgram, isWritable: false, isSigner: false },
156
+ { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isWritable: false, isSigner: false },
157
+ { pubkey: web3.SystemProgram.programId, isWritable: false, isSigner: false },
158
+ { pubkey: INSTRUCTIONS_SYSVAR_ID, isWritable: false, isSigner: false },
159
+ ];
160
+ }
161
+ else {
162
+ // Direct path: router swap ix, 0Block fee only (tenant_fee_bps = None).
163
+ // The router validates tenant_cfg/tenant vaults only on wrapped calls, so
164
+ // the user doubles as tenant_authority (dedupes with payer — still one
165
+ // signer) and the 0Block fee accounts fill the tenant slots.
166
+ programId = routerProgramId;
167
+ data = routerCoder.encode("swap", {
168
+ args: swapArgs,
169
+ order_id: bnFromBigint(orderId, "orderId", 128),
170
+ tenant_fee_bps: null,
171
+ });
172
+ // Fixed accounts in the exact order the router IDL declares them.
173
+ fixedKeys = [
174
+ { pubkey: user, isWritable: true, isSigner: true },
175
+ { pubkey: sourceTokenAccount, isWritable: true, isSigner: false },
176
+ { pubkey: destinationTokenAccount, isWritable: true, isSigner: false },
177
+ { pubkey: sourceMint, isWritable: false, isSigner: false },
178
+ { pubkey: destinationMint, isWritable: false, isSigner: false },
179
+ { pubkey: globalCfg, isWritable: false, isSigner: false },
180
+ {
181
+ pubkey: deriveRouterTenantConfig(routerProgramId, context.directTenantConfigKey),
182
+ isWritable: false,
183
+ isSigner: false,
184
+ },
185
+ { pubkey: user, isWritable: false, isSigner: true },
186
+ { pubkey: zeroblockFeeVault, isWritable: true, isSigner: false },
187
+ { pubkey: zeroblockFeeVault, isWritable: true, isSigner: false },
188
+ { pubkey: zeroblockFeeTokenAccount, isWritable: true, isSigner: false },
189
+ { pubkey: zeroblockFeeTokenAccount, isWritable: true, isSigner: false },
190
+ { pubkey: sourceTokenProgram, isWritable: false, isSigner: false },
191
+ { pubkey: destinationTokenProgram, isWritable: false, isSigner: false },
192
+ { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isWritable: false, isSigner: false },
193
+ { pubkey: web3.SystemProgram.programId, isWritable: false, isSigner: false },
194
+ { pubkey: INSTRUCTIONS_SYSVAR_ID, isWritable: false, isSigner: false },
195
+ ];
196
+ }
197
+ return {
198
+ instruction: new web3.TransactionInstruction({
199
+ programId,
200
+ keys: [...fixedKeys, ...remainingAccounts],
201
+ data,
202
+ }),
203
+ orderId,
204
+ sourceTokenAccount,
205
+ destinationTokenAccount,
206
+ };
207
+ }
208
+ /**
209
+ * Decode a swap instruction's data. Both programs share the `swap`
210
+ * discriminator (anchor hashes the instruction name), and the wrapper layout
211
+ * is a strict prefix of the router's (which appends `tenant_fee_bps:
212
+ * Option<u16>`) — so the router coder is tried FIRST: it fails on wrapper
213
+ * data (missing trailing option byte), while the wrapper coder would
214
+ * silently mis-decode router data by ignoring the tail.
215
+ */
216
+ export function decodeSwapData(data) {
217
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
218
+ try {
219
+ const direct = routerCoder.decode(buf);
220
+ if (direct)
221
+ return { program: "router", ...direct };
222
+ }
223
+ catch {
224
+ // fall through to the wrapper layout
225
+ }
226
+ const wrapped = wrapperCoder.decode(buf);
227
+ if (wrapped)
228
+ return { program: "tenant-wrapper", ...wrapped };
229
+ throw new Error("not a tenant-wrapper or router instruction");
230
+ }
package/dist/tip.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import type { TipParams } from "./types.js";
3
+ /**
4
+ * Gateway tip: a plain SystemProgram transfer to an allowlisted tip account.
5
+ * The 0Block gateway verifies it structurally on the RAW transaction bytes
6
+ * and does not resolve address lookup tables, so the destination must remain
7
+ * a static message key (see maskTipAccountInTables).
8
+ */
9
+ export declare function buildTipInstruction(from: web3.PublicKey, tip: TipParams): web3.TransactionInstruction;
10
+ /**
11
+ * Return copies of the lookup tables with the tip destination replaced by an
12
+ * unmatchable placeholder, so the v0 compiler cannot move the tip account
13
+ * into the lookup section. Never mutates the caller's tables.
14
+ */
15
+ export declare function maskTipAccountInTables(tables: web3.AddressLookupTableAccount[], tipAccount: web3.PublicKey): web3.AddressLookupTableAccount[];
package/dist/tip.js ADDED
@@ -0,0 +1,37 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import { toPk } from "./pda.js";
3
+ /**
4
+ * Gateway tip: a plain SystemProgram transfer to an allowlisted tip account.
5
+ * The 0Block gateway verifies it structurally on the RAW transaction bytes
6
+ * and does not resolve address lookup tables, so the destination must remain
7
+ * a static message key (see maskTipAccountInTables).
8
+ */
9
+ export function buildTipInstruction(from, tip) {
10
+ if (tip.lamports <= 0n)
11
+ throw new Error("tip lamports must be > 0");
12
+ return web3.SystemProgram.transfer({
13
+ fromPubkey: from,
14
+ toPubkey: toPk(tip.account),
15
+ lamports: tip.lamports,
16
+ });
17
+ }
18
+ /**
19
+ * Return copies of the lookup tables with the tip destination replaced by an
20
+ * unmatchable placeholder, so the v0 compiler cannot move the tip account
21
+ * into the lookup section. Never mutates the caller's tables.
22
+ */
23
+ export function maskTipAccountInTables(tables, tipAccount) {
24
+ return tables.map((table) => {
25
+ const masked = table.state.addresses.map((address) => {
26
+ if (!address.equals(tipAccount))
27
+ return address;
28
+ const random = new Uint8Array(32);
29
+ globalThis.crypto.getRandomValues(random);
30
+ return new web3.PublicKey(random);
31
+ });
32
+ return new web3.AddressLookupTableAccount({
33
+ key: table.key,
34
+ state: { ...table.state, addresses: masked },
35
+ });
36
+ });
37
+ }
@@ -0,0 +1,19 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import type { BuildPumpfunSwapParams, BuildSwapResult, LookupTableData } from "./types.js";
3
+ /** Rehydrate a streamed lookup table into web3.js form (compile-time only fields defaulted). */
4
+ export declare function toLookupTableAccount(data: LookupTableData): web3.AddressLookupTableAccount;
5
+ /**
6
+ * Build a complete, UNSIGNED v0 swap transaction for one pump.fun leg through
7
+ * a tenant wrapper. Fully offline: blockhash, market context, and lookup
8
+ * tables all arrive as inputs (streamed from the tenant backend).
9
+ *
10
+ * Instruction order: [tip] [compute budget] [ATA / wrap pre-steps] swap [unwrap].
11
+ * The tip leads so the gateway finds it without scanning past CPI-heavy ixs,
12
+ * and its destination is kept out of the lookup tables (raw-bytes rule).
13
+ */
14
+ export declare function buildPumpfunSwapTransaction(params: BuildPumpfunSwapParams): BuildSwapResult;
15
+ /**
16
+ * Attach externally-produced signatures (e.g. from sigcore) in
17
+ * requiredSigners order and return the wire-ready bytes.
18
+ */
19
+ export declare function serializeWithSignatures(result: BuildSwapResult, signatures: Uint8Array[]): Uint8Array;
@@ -0,0 +1,106 @@
1
+ import * as web3 from "@solana/web3.js";
2
+ import { MAX_TX_SIZE } from "./constants.js";
3
+ import { toPk } from "./pda.js";
4
+ import { buildComputeBudgetInstructions } from "./computeBudget.js";
5
+ import { buildPumpfunSwapInstruction } from "./swap.js";
6
+ import { buildTipInstruction, maskTipAccountInTables } from "./tip.js";
7
+ import { unwrapWsolInstruction, createAtaIdempotentInstruction, wrapSolInstructions } from "./wsol.js";
8
+ /** Rehydrate a streamed lookup table into web3.js form (compile-time only fields defaulted). */
9
+ export function toLookupTableAccount(data) {
10
+ return new web3.AddressLookupTableAccount({
11
+ key: toPk(data.key),
12
+ state: {
13
+ deactivationSlot: 2n ** 64n - 1n,
14
+ lastExtendedSlot: 0,
15
+ lastExtendedSlotStartIndex: 0,
16
+ authority: undefined,
17
+ addresses: data.addresses.map((a) => toPk(a)),
18
+ },
19
+ });
20
+ }
21
+ /**
22
+ * Build a complete, UNSIGNED v0 swap transaction for one pump.fun leg through
23
+ * a tenant wrapper. Fully offline: blockhash, market context, and lookup
24
+ * tables all arrive as inputs (streamed from the tenant backend).
25
+ *
26
+ * Instruction order: [tip] [compute budget] [ATA / wrap pre-steps] swap [unwrap].
27
+ * The tip leads so the gateway finds it without scanning past CPI-heavy ixs,
28
+ * and its destination is kept out of the lookup tables (raw-bytes rule).
29
+ */
30
+ export function buildPumpfunSwapTransaction(params) {
31
+ const user = toPk(params.user);
32
+ const swap = buildPumpfunSwapInstruction({
33
+ context: params.context,
34
+ market: params.market,
35
+ user,
36
+ side: params.side,
37
+ amountIn: params.amountIn,
38
+ minReturn: params.minReturn,
39
+ orderId: params.orderId,
40
+ });
41
+ const instructions = [];
42
+ if (params.tip) {
43
+ instructions.push(buildTipInstruction(user, params.tip));
44
+ }
45
+ if (params.computeBudget) {
46
+ instructions.push(...buildComputeBudgetInstructions(params.computeBudget));
47
+ }
48
+ if (params.side === "buy") {
49
+ // Trade from native SOL (default): fund the wSOL ATA in-transaction, and
50
+ // make sure the destination token ATA exists for first-time buyers.
51
+ if (params.wrapSol ?? true) {
52
+ instructions.push(...wrapSolInstructions({ user, lamports: params.amountIn }));
53
+ }
54
+ instructions.push(createAtaIdempotentInstruction({
55
+ payer: user,
56
+ owner: user,
57
+ mint: toPk(params.market.mint),
58
+ tokenProgram: toPk(params.market.mintTokenProgram),
59
+ }));
60
+ }
61
+ else {
62
+ // Selling into wSOL: the wSOL ATA may not exist between trades.
63
+ instructions.push(createAtaIdempotentInstruction({ payer: user, owner: user, mint: toPk("So11111111111111111111111111111111111111112") }));
64
+ }
65
+ instructions.push(swap.instruction);
66
+ if (params.side === "sell" && (params.unwrapWsol ?? true)) {
67
+ instructions.push(unwrapWsolInstruction(user));
68
+ }
69
+ let tables = (params.lookupTables ?? []).map(toLookupTableAccount);
70
+ if (params.tip) {
71
+ tables = maskTipAccountInTables(tables, toPk(params.tip.account));
72
+ }
73
+ const message = new web3.TransactionMessage({
74
+ payerKey: user,
75
+ recentBlockhash: params.recentBlockhash,
76
+ instructions,
77
+ }).compileToV0Message(tables);
78
+ const transaction = new web3.VersionedTransaction(message);
79
+ const estimatedSize = transaction.serialize().length; // placeholder sigs = wire size
80
+ if (estimatedSize > MAX_TX_SIZE) {
81
+ throw new Error(`transaction too large (${estimatedSize} > ${MAX_TX_SIZE}); ` +
82
+ "pass the shared lookup table(s) via lookupTables");
83
+ }
84
+ const requiredSigners = message.staticAccountKeys.slice(0, message.header.numRequiredSignatures);
85
+ return {
86
+ transaction,
87
+ messageBytes: message.serialize(),
88
+ requiredSigners,
89
+ orderId: swap.orderId,
90
+ estimatedSize,
91
+ };
92
+ }
93
+ /**
94
+ * Attach externally-produced signatures (e.g. from sigcore) in
95
+ * requiredSigners order and return the wire-ready bytes.
96
+ */
97
+ export function serializeWithSignatures(result, signatures) {
98
+ const expected = result.transaction.message.header.numRequiredSignatures;
99
+ if (signatures.length !== expected) {
100
+ throw new Error(`expected ${expected} signatures, got ${signatures.length}`);
101
+ }
102
+ for (const [i, signer] of result.requiredSigners.entries()) {
103
+ result.transaction.addSignature(signer, signatures[i]);
104
+ }
105
+ return result.transaction.serialize();
106
+ }