@theliem/xmarket-sdk 3.23.0 → 3.25.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/dist/index.d.mts CHANGED
@@ -661,6 +661,14 @@ declare class ClobClient {
661
661
  * Returns undefined if ctfClient/qmConfigPda not injected or marketOracle not configured.
662
662
  */
663
663
  private getMarketOracleVault;
664
+ /**
665
+ * Returns a createATA ix for the oracle vault when:
666
+ * - takerFee > 0
667
+ * - marketFeeOverride exists for the condition
668
+ * - oracle vault ATA is not yet initialized
669
+ * Idempotent — safe to call every tx; returns null if vault already exists.
670
+ */
671
+ private buildInitOracleVaultIfNeeded;
664
672
  get walletPubkey(): PublicKey;
665
673
  /**
666
674
  * Get or create an ALT for a condition.
@@ -684,6 +692,19 @@ declare class ClobClient {
684
692
  registerOrder(signed: SignedOrder): Promise<string>;
685
693
  /** Register only if the PDA doesn't exist yet (idempotent). */
686
694
  private registerOrderIfNeeded;
695
+ /**
696
+ * Build a legacy Transaction to register a CollectFeeOrder on-chain.
697
+ * Must be signed and sent before calling buildBatchCollectRedeemEarlyTx.
698
+ *
699
+ * Flow: [Ed25519 ix (1 user sig) + register_collect_fee_order ix]
700
+ * Caller signs with payer keypair and sends.
701
+ */
702
+ buildRegisterCollectFeeOrderTx(signedOrder: SignedCollectFeeOrder, payer: PublicKey): Promise<Transaction>;
703
+ /**
704
+ * Register a CollectFeeOrder on-chain and send immediately (operator-signed flow).
705
+ * Idempotent — skips if PDA already exists.
706
+ */
707
+ registerCollectFeeOrderIfNeeded(signedOrder: SignedCollectFeeOrder): Promise<void>;
687
708
  /** Cancel an order — closes its OrderRecord PDA and returns rent to maker. */
688
709
  cancelOrder(nonce: anchor.BN): Promise<TxResult>;
689
710
  initialize(operators: PublicKey[], feeRecipient: PublicKey, feeRateBps: number): Promise<TxResult>;
@@ -826,11 +847,13 @@ declare class ClobClient {
826
847
  /**
827
848
  * Build VersionedTransaction for batchCollectRedeemEarly.
828
849
  *
829
- * Flow: Ed25519 ix (user sigs) + batchCollectRedeemEarly ix.
830
- * Each SignedCollectFeeOrder authorizes CLOB to collect `amount` winning tokens
831
- * from that user, redeem via CTF, then distribute as fees.
850
+ * Prerequisite: each user's CollectFeeOrder must be registered on-chain via
851
+ * buildRegisterCollectFeeOrderTx (one tx per user).
852
+ *
853
+ * Flow: batchCollectRedeemEarly ix reads CollectFeeOrderRecord PDAs — no Ed25519 inline.
854
+ * No tx-size limit from signatures, supports 10+ orders in one tx.
832
855
  *
833
- * @param signedOrders - Array of { order, signature } from winning users
856
+ * @param signedOrders - Array of { order, signature } (used to derive record PDAs)
834
857
  * @param condition - Market condition PDA
835
858
  * @param outcomeIndex - 0 = NO wins, 1 = YES wins
836
859
  * @param operator - Whitelisted operator pubkey (must sign)
@@ -841,6 +864,11 @@ declare class ClobClient {
841
864
  marketOracleVault?: PublicKey;
842
865
  lookupTable?: AddressLookupTableAccount;
843
866
  }): Promise<VersionedTransaction>;
867
+ /**
868
+ * Build ALT for batchCollectRedeemEarly — includes per-user accounts so all
869
+ * remaining_accounts fit as 1-byte ALT indices instead of 32-byte inline addresses.
870
+ */
871
+ buildAltForCollectBatch(condition: PublicKey, payer: PublicKey, collateralMint: PublicKey, signedOrders: SignedCollectFeeOrder[], outcomeIndex: 0 | 1): Promise<AddressLookupTableAccount>;
844
872
  /**
845
873
  * Build ALT with static accounts for a condition — no order-specific PDAs needed.
846
874
  * Use before buildBatchCollectRedeemEarlyTx when no prior buildMatchOrdersTx ran in this session.
@@ -1158,6 +1186,7 @@ declare class PDA {
1158
1186
  static clobConfig(programIds: Pick<ProgramIds, "clobExchange">): [PublicKey, number];
1159
1187
  static orderStatus(maker: PublicKey, nonce: BN, programIds: Pick<ProgramIds, "clobExchange">): [PublicKey, number];
1160
1188
  static orderRecord(maker: PublicKey, nonce: BN, programIds: Pick<ProgramIds, "clobExchange">): [PublicKey, number];
1189
+ static collectFeeOrderRecord(user: PublicKey, nonce: BN, programIds: Pick<ProgramIds, "clobExchange">): [PublicKey, number];
1161
1190
  static feeConfig(owner: PublicKey, programIds: Pick<ProgramIds, "feeManagement">): [PublicKey, number];
1162
1191
  static questionFee(conditionPda: PublicKey, programIds: Pick<ProgramIds, "feeManagement">): [PublicKey, number];
1163
1192
  static marketFeeOverride(conditionPda: PublicKey, programIds: Pick<ProgramIds, "feeManagement">): [PublicKey, number];
package/dist/index.d.ts CHANGED
@@ -661,6 +661,14 @@ declare class ClobClient {
661
661
  * Returns undefined if ctfClient/qmConfigPda not injected or marketOracle not configured.
662
662
  */
663
663
  private getMarketOracleVault;
664
+ /**
665
+ * Returns a createATA ix for the oracle vault when:
666
+ * - takerFee > 0
667
+ * - marketFeeOverride exists for the condition
668
+ * - oracle vault ATA is not yet initialized
669
+ * Idempotent — safe to call every tx; returns null if vault already exists.
670
+ */
671
+ private buildInitOracleVaultIfNeeded;
664
672
  get walletPubkey(): PublicKey;
665
673
  /**
666
674
  * Get or create an ALT for a condition.
@@ -684,6 +692,19 @@ declare class ClobClient {
684
692
  registerOrder(signed: SignedOrder): Promise<string>;
685
693
  /** Register only if the PDA doesn't exist yet (idempotent). */
686
694
  private registerOrderIfNeeded;
695
+ /**
696
+ * Build a legacy Transaction to register a CollectFeeOrder on-chain.
697
+ * Must be signed and sent before calling buildBatchCollectRedeemEarlyTx.
698
+ *
699
+ * Flow: [Ed25519 ix (1 user sig) + register_collect_fee_order ix]
700
+ * Caller signs with payer keypair and sends.
701
+ */
702
+ buildRegisterCollectFeeOrderTx(signedOrder: SignedCollectFeeOrder, payer: PublicKey): Promise<Transaction>;
703
+ /**
704
+ * Register a CollectFeeOrder on-chain and send immediately (operator-signed flow).
705
+ * Idempotent — skips if PDA already exists.
706
+ */
707
+ registerCollectFeeOrderIfNeeded(signedOrder: SignedCollectFeeOrder): Promise<void>;
687
708
  /** Cancel an order — closes its OrderRecord PDA and returns rent to maker. */
688
709
  cancelOrder(nonce: anchor.BN): Promise<TxResult>;
689
710
  initialize(operators: PublicKey[], feeRecipient: PublicKey, feeRateBps: number): Promise<TxResult>;
@@ -826,11 +847,13 @@ declare class ClobClient {
826
847
  /**
827
848
  * Build VersionedTransaction for batchCollectRedeemEarly.
828
849
  *
829
- * Flow: Ed25519 ix (user sigs) + batchCollectRedeemEarly ix.
830
- * Each SignedCollectFeeOrder authorizes CLOB to collect `amount` winning tokens
831
- * from that user, redeem via CTF, then distribute as fees.
850
+ * Prerequisite: each user's CollectFeeOrder must be registered on-chain via
851
+ * buildRegisterCollectFeeOrderTx (one tx per user).
852
+ *
853
+ * Flow: batchCollectRedeemEarly ix reads CollectFeeOrderRecord PDAs — no Ed25519 inline.
854
+ * No tx-size limit from signatures, supports 10+ orders in one tx.
832
855
  *
833
- * @param signedOrders - Array of { order, signature } from winning users
856
+ * @param signedOrders - Array of { order, signature } (used to derive record PDAs)
834
857
  * @param condition - Market condition PDA
835
858
  * @param outcomeIndex - 0 = NO wins, 1 = YES wins
836
859
  * @param operator - Whitelisted operator pubkey (must sign)
@@ -841,6 +864,11 @@ declare class ClobClient {
841
864
  marketOracleVault?: PublicKey;
842
865
  lookupTable?: AddressLookupTableAccount;
843
866
  }): Promise<VersionedTransaction>;
867
+ /**
868
+ * Build ALT for batchCollectRedeemEarly — includes per-user accounts so all
869
+ * remaining_accounts fit as 1-byte ALT indices instead of 32-byte inline addresses.
870
+ */
871
+ buildAltForCollectBatch(condition: PublicKey, payer: PublicKey, collateralMint: PublicKey, signedOrders: SignedCollectFeeOrder[], outcomeIndex: 0 | 1): Promise<AddressLookupTableAccount>;
844
872
  /**
845
873
  * Build ALT with static accounts for a condition — no order-specific PDAs needed.
846
874
  * Use before buildBatchCollectRedeemEarlyTx when no prior buildMatchOrdersTx ran in this session.
@@ -1158,6 +1186,7 @@ declare class PDA {
1158
1186
  static clobConfig(programIds: Pick<ProgramIds, "clobExchange">): [PublicKey, number];
1159
1187
  static orderStatus(maker: PublicKey, nonce: BN, programIds: Pick<ProgramIds, "clobExchange">): [PublicKey, number];
1160
1188
  static orderRecord(maker: PublicKey, nonce: BN, programIds: Pick<ProgramIds, "clobExchange">): [PublicKey, number];
1189
+ static collectFeeOrderRecord(user: PublicKey, nonce: BN, programIds: Pick<ProgramIds, "clobExchange">): [PublicKey, number];
1161
1190
  static feeConfig(owner: PublicKey, programIds: Pick<ProgramIds, "feeManagement">): [PublicKey, number];
1162
1191
  static questionFee(conditionPda: PublicKey, programIds: Pick<ProgramIds, "feeManagement">): [PublicKey, number];
1163
1192
  static marketFeeOverride(conditionPda: PublicKey, programIds: Pick<ProgramIds, "feeManagement">): [PublicKey, number];
package/dist/index.js CHANGED
@@ -187,6 +187,14 @@ var PDA = class {
187
187
  programIds.clobExchange
188
188
  );
189
189
  }
190
+ static collectFeeOrderRecord(user, nonce, programIds) {
191
+ const nonceBuf = Buffer.alloc(8);
192
+ nonceBuf.writeBigUInt64LE(BigInt(nonce.toString()));
193
+ return web3_js.PublicKey.findProgramAddressSync(
194
+ [Buffer.from("collect_order"), user.toBuffer(), nonceBuf],
195
+ programIds.clobExchange
196
+ );
197
+ }
190
198
  // ─── Fee Management ─────────────────────────────────────────────────────────
191
199
  static feeConfig(owner, programIds) {
192
200
  if (!programIds.feeManagement) throw new Error("feeManagement program ID not configured");
@@ -1644,6 +1652,39 @@ var ClobClient = class {
1644
1652
  this._marketOracleVaultCache.set(key, vault);
1645
1653
  return vault;
1646
1654
  }
1655
+ /**
1656
+ * Returns a createATA ix for the oracle vault when:
1657
+ * - takerFee > 0
1658
+ * - marketFeeOverride exists for the condition
1659
+ * - oracle vault ATA is not yet initialized
1660
+ * Idempotent — safe to call every tx; returns null if vault already exists.
1661
+ */
1662
+ async buildInitOracleVaultIfNeeded(condition, collateralMint, takerFee, payer) {
1663
+ if (takerFee.isZero()) return null;
1664
+ if (!this.feeConfigOwner || !this.programIds.feeManagement) return null;
1665
+ if (!this.ctfClient || !this.qmConfigPda || !this.programIds.marketOracle) return null;
1666
+ const companyAddr = await this.companyAddress();
1667
+ const refVault = await this.referralVault();
1668
+ if (!companyAddr || !refVault) return null;
1669
+ const feeOverridePda = PDA.marketFeeOverride(condition, this.programIds)[0];
1670
+ const cond = await this.ctfClient.fetchCondition(condition);
1671
+ if (!cond) return null;
1672
+ const [questionPda] = PDA.question(this.qmConfigPda, cond.questionId, this.programIds);
1673
+ const [marketOraclePda] = PDA.marketOraclePda(questionPda, this.programIds);
1674
+ const vault = splToken.getAssociatedTokenAddressSync(collateralMint, marketOraclePda, true);
1675
+ const [feeOverrideInfo, vaultInfo] = await Promise.all([
1676
+ this.provider.connection.getAccountInfo(feeOverridePda),
1677
+ this.provider.connection.getAccountInfo(vault)
1678
+ ]);
1679
+ if (!feeOverrideInfo) return null;
1680
+ if (vaultInfo) return null;
1681
+ return splToken.createAssociatedTokenAccountIdempotentInstruction(
1682
+ payer,
1683
+ vault,
1684
+ marketOraclePda,
1685
+ collateralMint
1686
+ );
1687
+ }
1647
1688
  get walletPubkey() {
1648
1689
  return this.provider.wallet.publicKey;
1649
1690
  }
@@ -1782,8 +1823,8 @@ var ClobClient = class {
1782
1823
  async _sendLegacyTxSig(instructions) {
1783
1824
  const { connection } = this.provider;
1784
1825
  const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
1785
- const { Transaction: Transaction11 } = await import('@solana/web3.js');
1786
- const tx = new Transaction11();
1826
+ const { Transaction: Transaction12 } = await import('@solana/web3.js');
1827
+ const tx = new Transaction12();
1787
1828
  tx.recentBlockhash = blockhash;
1788
1829
  tx.feePayer = this.walletPubkey;
1789
1830
  tx.add(...instructions);
@@ -1809,10 +1850,10 @@ ${logs.join("\n")}`);
1809
1850
  connection.getAccountInfo(clobYesAta),
1810
1851
  connection.getAccountInfo(clobNoAta)
1811
1852
  ]);
1812
- const { createAssociatedTokenAccountIdempotentInstruction: createAssociatedTokenAccountIdempotentInstruction4 } = await import('@solana/spl-token');
1853
+ const { createAssociatedTokenAccountIdempotentInstruction: createAssociatedTokenAccountIdempotentInstruction5 } = await import('@solana/spl-token');
1813
1854
  const ixs = [];
1814
1855
  if (!yesInfo) {
1815
- ixs.push(createAssociatedTokenAccountIdempotentInstruction4(
1856
+ ixs.push(createAssociatedTokenAccountIdempotentInstruction5(
1816
1857
  this.walletPubkey,
1817
1858
  clobYesAta,
1818
1859
  clobConfig,
@@ -1821,7 +1862,7 @@ ${logs.join("\n")}`);
1821
1862
  ));
1822
1863
  }
1823
1864
  if (!noInfo) {
1824
- ixs.push(createAssociatedTokenAccountIdempotentInstruction4(
1865
+ ixs.push(createAssociatedTokenAccountIdempotentInstruction5(
1825
1866
  this.walletPubkey,
1826
1867
  clobNoAta,
1827
1868
  clobConfig,
@@ -1904,6 +1945,49 @@ ${logs.join("\n")}`);
1904
1945
  await this.registerOrder(signed);
1905
1946
  }
1906
1947
  }
1948
+ // ─── Register CollectFeeOrder ────────────────────────────────────────────────
1949
+ /**
1950
+ * Build a legacy Transaction to register a CollectFeeOrder on-chain.
1951
+ * Must be signed and sent before calling buildBatchCollectRedeemEarlyTx.
1952
+ *
1953
+ * Flow: [Ed25519 ix (1 user sig) + register_collect_fee_order ix]
1954
+ * Caller signs with payer keypair and sends.
1955
+ */
1956
+ async buildRegisterCollectFeeOrderTx(signedOrder, payer) {
1957
+ const { order } = signedOrder;
1958
+ const [collectFeeOrderRecord] = PDA.collectFeeOrderRecord(order.user, order.nonce, this.programIds);
1959
+ const ed25519Ix = buildBatchedCollectFeeEd25519Instruction([signedOrder]);
1960
+ const registerIx = await this.program.methods.registerCollectFeeOrder(order.nonce).accounts({
1961
+ payer,
1962
+ clobConfig: this.configPda(),
1963
+ ixSysvar: IX_SYSVAR,
1964
+ orderSigner: order.user,
1965
+ collectFeeOrderRecord,
1966
+ systemProgram: web3_js.SystemProgram.programId
1967
+ }).instruction();
1968
+ const { blockhash } = await this.provider.connection.getLatestBlockhash();
1969
+ const tx = new web3_js.Transaction();
1970
+ tx.recentBlockhash = blockhash;
1971
+ tx.feePayer = payer;
1972
+ tx.add(ed25519Ix, registerIx);
1973
+ return tx;
1974
+ }
1975
+ /**
1976
+ * Register a CollectFeeOrder on-chain and send immediately (operator-signed flow).
1977
+ * Idempotent — skips if PDA already exists.
1978
+ */
1979
+ async registerCollectFeeOrderIfNeeded(signedOrder) {
1980
+ const [pda] = PDA.collectFeeOrderRecord(
1981
+ signedOrder.order.user,
1982
+ signedOrder.order.nonce,
1983
+ this.programIds
1984
+ );
1985
+ const existing = await this.program.account.collectFeeOrderRecord?.fetchNullable(pda);
1986
+ if (!existing) {
1987
+ const tx = await this.buildRegisterCollectFeeOrderTx(signedOrder, this.walletPubkey);
1988
+ await this._sendLegacyTx(tx.instructions);
1989
+ }
1990
+ }
1907
1991
  /** Cancel an order — closes its OrderRecord PDA and returns rent to maker. */
1908
1992
  async cancelOrder(nonce) {
1909
1993
  const [orderRecord] = PDA.orderRecord(this.walletPubkey, nonce, this.programIds);
@@ -2486,6 +2570,13 @@ ${logs.join("\n")}`);
2486
2570
  if (yIx) hookInitIxs.push(yIx);
2487
2571
  if (nIx) hookInitIxs.push(nIx);
2488
2572
  }
2573
+ const oracleVaultInitIx = await this.buildInitOracleVaultIfNeeded(
2574
+ t.condition,
2575
+ collateralMint,
2576
+ t.fee,
2577
+ payer
2578
+ );
2579
+ const preIxs = oracleVaultInitIx ? [...hookInitIxs, oracleVaultInitIx] : hookInitIxs;
2489
2580
  if (t.tokenId === m0.tokenId) {
2490
2581
  let buySignedOrder, sellCandidates;
2491
2582
  if (t.side === SIDE_BUY && makers.every((m) => m.order.side === SIDE_SELL)) {
@@ -2505,7 +2596,7 @@ ${logs.join("\n")}`);
2505
2596
  opts,
2506
2597
  false
2507
2598
  );
2508
- return this._buildUnsignedVtx([...hookInitIxs, ...ixs3], alt, payer);
2599
+ return this._buildUnsignedVtx([...preIxs, ...ixs3], alt, payer);
2509
2600
  } else {
2510
2601
  throw new InvalidParamError("COMPLEMENTARY requires one BUY and one or more SELLs on same tokenId");
2511
2602
  }
@@ -2521,7 +2612,7 @@ ${logs.join("\n")}`);
2521
2612
  operator,
2522
2613
  opts
2523
2614
  );
2524
- return this._buildUnsignedVtx([...hookInitIxs, ...ixs2], alt, payer);
2615
+ return this._buildUnsignedVtx([...preIxs, ...ixs2], alt, payer);
2525
2616
  }
2526
2617
  const allBuy = t.side === SIDE_BUY && makers.every((m) => m.order.side === SIDE_BUY);
2527
2618
  const allSell = t.side === SIDE_SELL && makers.every((m) => m.order.side === SIDE_SELL);
@@ -2542,7 +2633,7 @@ ${logs.join("\n")}`);
2542
2633
  ]);
2543
2634
  await this._ensureClobOutcomeAtas(yesMint, noMint, clobConfig, clobYesAta, clobNoAta);
2544
2635
  const ix = allBuy ? await this._buildMintIx(taker, makers, collateralMint, operator, payer) : await this._buildMergeIx(taker, makers, collateralMint, operator, payer, opts);
2545
- return this._buildUnsignedVtx([...hookInitIxs, ix], alt, payer);
2636
+ return this._buildUnsignedVtx([...preIxs, ix], alt, payer);
2546
2637
  }
2547
2638
  await Promise.all([
2548
2639
  this.registerOrderIfNeeded(taker),
@@ -2554,17 +2645,19 @@ ${logs.join("\n")}`);
2554
2645
  const ix = allBuy ? await this._buildMintIx(yesMaker, [taker], collateralMint, operator, payer) : await this._buildMergeIx(yesMaker, [taker], collateralMint, operator, payer, opts);
2555
2646
  ixs.push(ix);
2556
2647
  }
2557
- return this._buildUnsignedVtx([...hookInitIxs, ...ixs], alt, payer);
2648
+ return this._buildUnsignedVtx([...preIxs, ...ixs], alt, payer);
2558
2649
  }
2559
2650
  // ─── batchCollectRedeemEarly ─────────────────────────────────────────────────
2560
2651
  /**
2561
2652
  * Build VersionedTransaction for batchCollectRedeemEarly.
2562
2653
  *
2563
- * Flow: Ed25519 ix (user sigs) + batchCollectRedeemEarly ix.
2564
- * Each SignedCollectFeeOrder authorizes CLOB to collect `amount` winning tokens
2565
- * from that user, redeem via CTF, then distribute as fees.
2654
+ * Prerequisite: each user's CollectFeeOrder must be registered on-chain via
2655
+ * buildRegisterCollectFeeOrderTx (one tx per user).
2566
2656
  *
2567
- * @param signedOrders - Array of { order, signature } from winning users
2657
+ * Flow: batchCollectRedeemEarly ix reads CollectFeeOrderRecord PDAs no Ed25519 inline.
2658
+ * No tx-size limit from signatures, supports 10+ orders in one tx.
2659
+ *
2660
+ * @param signedOrders - Array of { order, signature } (used to derive record PDAs)
2568
2661
  * @param condition - Market condition PDA
2569
2662
  * @param outcomeIndex - 0 = NO wins, 1 = YES wins
2570
2663
  * @param operator - Whitelisted operator pubkey (must sign)
@@ -2590,9 +2683,11 @@ ${logs.join("\n")}`);
2590
2683
  const [clobNoPosition] = PDA.position(condition, 0, clobConfig, this.programIds);
2591
2684
  const userAccounts = [];
2592
2685
  for (const { order } of signedOrders) {
2686
+ const [collectFeeOrderRecord] = PDA.collectFeeOrderRecord(order.user, order.nonce, this.programIds);
2593
2687
  const userTokenAta = splToken.getAssociatedTokenAddressSync(outcomeMint, order.user, false, splToken.TOKEN_2022_PROGRAM_ID);
2594
2688
  const [userPosition] = PDA.position(condition, outcomeIndex, order.user, this.programIds);
2595
2689
  userAccounts.push(
2690
+ { pubkey: collectFeeOrderRecord, isSigner: false, isWritable: true },
2596
2691
  { pubkey: order.user, isSigner: false, isWritable: false },
2597
2692
  { pubkey: userTokenAta, isSigner: false, isWritable: true },
2598
2693
  { pubkey: userPosition, isSigner: false, isWritable: true }
@@ -2629,11 +2724,7 @@ ${logs.join("\n")}`);
2629
2724
  const ix = await this.hookClient.buildInitHookIxIfNeeded(outcomeMint, payer);
2630
2725
  if (ix) hookInitIxs.push(ix);
2631
2726
  }
2632
- const ed25519Ix = buildBatchedCollectFeeEd25519Instruction(signedOrders);
2633
- const ed25519IxIndex = 2 + hookInitIxs.length;
2634
2727
  const collectIx = await this.program.methods.batchCollectRedeemEarly(
2635
- ed25519IxIndex,
2636
- // ix_index: adjusted for any prepended hook init ixs
2637
2728
  signedOrders.length,
2638
2729
  outcomeIndex
2639
2730
  ).accounts({
@@ -2650,14 +2741,96 @@ ${logs.join("\n")}`);
2650
2741
  clobNoAta,
2651
2742
  clobYesPosition,
2652
2743
  clobNoPosition,
2653
- ixSysvar: IX_SYSVAR,
2654
2744
  conditionalTokensProgram: this.programIds.conditionalTokens,
2655
2745
  tokenProgram: splToken.TOKEN_PROGRAM_ID,
2656
2746
  token2022Program: splToken.TOKEN_2022_PROGRAM_ID,
2657
2747
  systemProgram: web3_js.SystemProgram.programId
2658
2748
  }).remainingAccounts([...userAccounts, ...hookAccounts, ...feeAccounts]).instruction();
2659
- const alt = opts?.lookupTable ?? this._altCache.get(condition.toBase58()) ?? await this.buildAltForCondition(condition, payer, collateralMint);
2660
- return this._buildUnsignedVtx([...hookInitIxs, ed25519Ix, collectIx], alt, payer);
2749
+ const alt = opts?.lookupTable ?? await this.buildAltForCollectBatch(condition, payer, collateralMint, signedOrders, outcomeIndex);
2750
+ return this._buildUnsignedVtx([...hookInitIxs, collectIx], alt, payer);
2751
+ }
2752
+ /**
2753
+ * Build ALT for batchCollectRedeemEarly — includes per-user accounts so all
2754
+ * remaining_accounts fit as 1-byte ALT indices instead of 32-byte inline addresses.
2755
+ */
2756
+ async buildAltForCollectBatch(condition, payer, collateralMint, signedOrders, outcomeIndex) {
2757
+ const userKeys = signedOrders.map((o) => o.order.user.toBase58()).sort((a, b) => a.localeCompare(b)).join(",");
2758
+ const cacheKey = `${condition.toBase58()}:collect:${userKeys}`;
2759
+ if (this._altCache.has(cacheKey)) return this._altCache.get(cacheKey);
2760
+ const { connection } = this.provider;
2761
+ const clobConfigPda = this.configPda();
2762
+ const [yesMint] = PDA.yesMint(condition, this.programIds);
2763
+ const [noMint] = PDA.noMint(condition, this.programIds);
2764
+ const outcomeMint = outcomeIndex === 1 ? yesMint : noMint;
2765
+ const [extraAccountMeta] = PDA.extraAccountMetaList(outcomeMint, this.programIds);
2766
+ const [hookConfig] = PDA.hookConfig(this.programIds);
2767
+ const [collateralVault] = PDA.collateralVault(collateralMint, this.programIds);
2768
+ const [vaultTokenAccount] = PDA.vaultToken(collateralMint, this.programIds);
2769
+ const clobYesAta = splToken.getAssociatedTokenAddressSync(yesMint, clobConfigPda, true, splToken.TOKEN_2022_PROGRAM_ID);
2770
+ const clobNoAta = splToken.getAssociatedTokenAddressSync(noMint, clobConfigPda, true, splToken.TOKEN_2022_PROGRAM_ID);
2771
+ const [clobYesPos] = PDA.position(condition, 1, clobConfigPda, this.programIds);
2772
+ const [clobNoPos] = PDA.position(condition, 0, clobConfigPda, this.programIds);
2773
+ const feeRecipientAddr = (await this.fetchConfig())?.feeRecipient;
2774
+ const addresses = [
2775
+ this.programIds.clobExchange,
2776
+ this.programIds.conditionalTokens,
2777
+ this.programIds.hook,
2778
+ splToken.TOKEN_PROGRAM_ID,
2779
+ splToken.TOKEN_2022_PROGRAM_ID,
2780
+ web3_js.SystemProgram.programId,
2781
+ clobConfigPda,
2782
+ hookConfig,
2783
+ condition,
2784
+ yesMint,
2785
+ noMint,
2786
+ extraAccountMeta,
2787
+ collateralVault,
2788
+ vaultTokenAccount,
2789
+ clobYesAta,
2790
+ clobNoAta,
2791
+ clobYesPos,
2792
+ clobNoPos
2793
+ ];
2794
+ if (feeRecipientAddr) addresses.push(feeRecipientAddr);
2795
+ if (this.feeConfigOwner && this.programIds.feeManagement) {
2796
+ const companyAddr = await this.companyAddress();
2797
+ const refVault = await this.referralVault();
2798
+ addresses.push(this.programIds.feeManagement);
2799
+ addresses.push(PDA.feeConfig(this.feeConfigOwner, this.programIds)[0]);
2800
+ addresses.push(PDA.marketFeeOverride(condition, this.programIds)[0]);
2801
+ if (companyAddr) addresses.push(splToken.getAssociatedTokenAddressSync(collateralMint, companyAddr));
2802
+ if (refVault) addresses.push(refVault);
2803
+ const oracleVault = await this.getMarketOracleVault(condition, collateralMint) ?? payer;
2804
+ addresses.push(oracleVault);
2805
+ }
2806
+ for (const { order } of signedOrders) {
2807
+ const [collectFeeOrderRecord] = PDA.collectFeeOrderRecord(order.user, order.nonce, this.programIds);
2808
+ const userTokenAta = splToken.getAssociatedTokenAddressSync(outcomeMint, order.user, false, splToken.TOKEN_2022_PROGRAM_ID);
2809
+ const [userPosition] = PDA.position(condition, outcomeIndex, order.user, this.programIds);
2810
+ addresses.push(collectFeeOrderRecord, order.user, userTokenAta, userPosition);
2811
+ }
2812
+ const slot = await connection.getSlot("finalized");
2813
+ const [createIx, altAddress] = web3_js.AddressLookupTableProgram.createLookupTable(
2814
+ { authority: payer, payer, recentSlot: slot }
2815
+ );
2816
+ const BATCH = 30;
2817
+ const extendIxs = [];
2818
+ for (let i = 0; i < addresses.length; i += BATCH) {
2819
+ extendIxs.push(web3_js.AddressLookupTableProgram.extendLookupTable(
2820
+ { payer, authority: payer, lookupTable: altAddress, addresses: addresses.slice(i, i + BATCH) }
2821
+ ));
2822
+ }
2823
+ await this._sendLegacyTx([createIx, extendIxs[0]]);
2824
+ for (let i = 1; i < extendIxs.length; i++) await this._sendLegacyTx([extendIxs[i]]);
2825
+ for (let attempt = 0; attempt < 30; attempt++) {
2826
+ await new Promise((r) => setTimeout(r, 1e3));
2827
+ const res = await connection.getAddressLookupTable(altAddress);
2828
+ if (res.value && res.value.state.addresses.length === addresses.length) {
2829
+ this._altCache.set(cacheKey, res.value);
2830
+ return res.value;
2831
+ }
2832
+ }
2833
+ throw new Error(`ALT ${altAddress.toBase58()} not active after 30s`);
2661
2834
  }
2662
2835
  /**
2663
2836
  * Build ALT with static accounts for a condition — no order-specific PDAs needed.
@@ -9420,7 +9593,8 @@ var clob_exchange_default = {
9420
9593
  name: "batch_collect_redeem_early",
9421
9594
  docs: [
9422
9595
  "Batch collect fee tokens from winning users after question resolution,",
9423
- "redeem them via CTF, and distribute USDS via fee_management."
9596
+ "redeem them via CTF, and distribute USDS via fee_management.",
9597
+ "Requires each user's CollectFeeOrderRecord to be registered via register_collect_fee_order."
9424
9598
  ],
9425
9599
  discriminator: [
9426
9600
  87,
@@ -9530,7 +9704,7 @@ var clob_exchange_default = {
9530
9704
  {
9531
9705
  name: "clob_yes_ata",
9532
9706
  docs: [
9533
- "CLOB's YES ATA (Token-2022) \u2014 must be pre-initialized (created during prior matchOrders)."
9707
+ "CLOB's YES ATA (Token-2022) \u2014 must be pre-initialized."
9534
9708
  ],
9535
9709
  writable: true
9536
9710
  },
@@ -9549,9 +9723,6 @@ var clob_exchange_default = {
9549
9723
  name: "clob_no_position",
9550
9724
  writable: true
9551
9725
  },
9552
- {
9553
- name: "ix_sysvar"
9554
- },
9555
9726
  {
9556
9727
  name: "conditional_tokens_program",
9557
9728
  address: "A6N1F8MRsdgcojAx8p6FaECvw8mo8w6qJcWsbKQBANK4"
@@ -9570,10 +9741,6 @@ var clob_exchange_default = {
9570
9741
  }
9571
9742
  ],
9572
9743
  args: [
9573
- {
9574
- name: "ix_index",
9575
- type: "u8"
9576
- },
9577
9744
  {
9578
9745
  name: "order_count",
9579
9746
  type: "u8"
@@ -10519,6 +10686,105 @@ var clob_exchange_default = {
10519
10686
  }
10520
10687
  ]
10521
10688
  },
10689
+ {
10690
+ name: "register_collect_fee_order",
10691
+ docs: [
10692
+ "Register a signed CollectFeeOrder on-chain (Ed25519 verify at registration time).",
10693
+ "ix[0] must be Ed25519 precompile with 1 entry for order.user.",
10694
+ "Call once per user before batch_collect_redeem_early."
10695
+ ],
10696
+ discriminator: [
10697
+ 63,
10698
+ 26,
10699
+ 202,
10700
+ 98,
10701
+ 195,
10702
+ 2,
10703
+ 211,
10704
+ 36
10705
+ ],
10706
+ accounts: [
10707
+ {
10708
+ name: "payer",
10709
+ writable: true,
10710
+ signer: true
10711
+ },
10712
+ {
10713
+ name: "clob_config",
10714
+ pda: {
10715
+ seeds: [
10716
+ {
10717
+ kind: "const",
10718
+ value: [
10719
+ 99,
10720
+ 108,
10721
+ 111,
10722
+ 98,
10723
+ 95,
10724
+ 99,
10725
+ 111,
10726
+ 110,
10727
+ 102,
10728
+ 105,
10729
+ 103
10730
+ ]
10731
+ }
10732
+ ]
10733
+ }
10734
+ },
10735
+ {
10736
+ name: "ix_sysvar",
10737
+ address: "Sysvar1nstructions1111111111111111111111111"
10738
+ },
10739
+ {
10740
+ name: "order_signer"
10741
+ },
10742
+ {
10743
+ name: "collect_fee_order_record",
10744
+ writable: true,
10745
+ pda: {
10746
+ seeds: [
10747
+ {
10748
+ kind: "const",
10749
+ value: [
10750
+ 99,
10751
+ 111,
10752
+ 108,
10753
+ 108,
10754
+ 101,
10755
+ 99,
10756
+ 116,
10757
+ 95,
10758
+ 111,
10759
+ 114,
10760
+ 100,
10761
+ 101,
10762
+ 114
10763
+ ]
10764
+ },
10765
+ {
10766
+ kind: "account",
10767
+ path: "order_signer"
10768
+ },
10769
+ {
10770
+ kind: "arg",
10771
+ path: "nonce"
10772
+ }
10773
+ ]
10774
+ }
10775
+ },
10776
+ {
10777
+ name: "system_program",
10778
+ address: "11111111111111111111111111111111"
10779
+ }
10780
+ ],
10781
+ args: [
10782
+ {
10783
+ name: "nonce",
10784
+ type: "u64"
10785
+ }
10786
+ ]
10787
+ },
10522
10788
  {
10523
10789
  name: "register_order",
10524
10790
  docs: [
@@ -10809,6 +11075,19 @@ var clob_exchange_default = {
10809
11075
  160
10810
11076
  ]
10811
11077
  },
11078
+ {
11079
+ name: "CollectFeeOrderRecord",
11080
+ discriminator: [
11081
+ 145,
11082
+ 140,
11083
+ 193,
11084
+ 74,
11085
+ 12,
11086
+ 98,
11087
+ 74,
11088
+ 130
11089
+ ]
11090
+ },
10812
11091
  {
10813
11092
  name: "OrderStatus",
10814
11093
  discriminator: [
@@ -11018,6 +11297,53 @@ var clob_exchange_default = {
11018
11297
  ]
11019
11298
  }
11020
11299
  },
11300
+ {
11301
+ name: "CollectFeeOrderRecord",
11302
+ docs: [
11303
+ "On-chain record of a signed CollectFeeOrder.",
11304
+ "Created by `register_collect_fee_order` after Ed25519 verification.",
11305
+ "Read and marked collected by `batch_collect_redeem_early`.",
11306
+ "",
11307
+ 'PDA seeds: [b"collect_order", user, nonce_le_bytes]'
11308
+ ],
11309
+ type: {
11310
+ kind: "struct",
11311
+ fields: [
11312
+ {
11313
+ name: "user",
11314
+ type: "pubkey"
11315
+ },
11316
+ {
11317
+ name: "condition",
11318
+ type: "pubkey"
11319
+ },
11320
+ {
11321
+ name: "token_mint",
11322
+ type: "pubkey"
11323
+ },
11324
+ {
11325
+ name: "amount",
11326
+ type: "u64"
11327
+ },
11328
+ {
11329
+ name: "nonce",
11330
+ type: "u64"
11331
+ },
11332
+ {
11333
+ name: "expiry",
11334
+ type: "i64"
11335
+ },
11336
+ {
11337
+ name: "is_collected",
11338
+ type: "bool"
11339
+ },
11340
+ {
11341
+ name: "bump",
11342
+ type: "u8"
11343
+ }
11344
+ ]
11345
+ }
11346
+ },
11021
11347
  {
11022
11348
  name: "ComplementaryMatchEvent",
11023
11349
  type: {