@zcomb/programs-sdk 1.2.0 → 1.4.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/utils.d.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  export interface TxOptions {
2
2
  includeCuBudget?: boolean;
3
3
  computeUnits?: number;
4
+ /**
5
+ * Pre-create conditional token ATAs before launch to avoid exceeding
6
+ * Solana's 64 instruction trace limit. Default: true for 3+ options.
7
+ * Set to false if you've already created ATAs or want to manage them manually.
8
+ */
9
+ ensureATAs?: boolean;
4
10
  }
5
11
  export declare function parseIdlBytes(value: string): Buffer;
6
12
  export declare function getIdlConstant(idl: {
package/dist/utils.js CHANGED
@@ -25,4 +25,4 @@ function getIdlConstant(idl, name) {
25
25
  }
26
26
  return constant.value;
27
27
  }
28
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdXRpbHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7R0FHRzs7QUFhSCxzQ0FHQztBQU1ELHdDQVNDO0FBdEJEOzs7R0FHRztBQUNILFNBQWdCLGFBQWEsQ0FBQyxLQUFhO0lBQ3pDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFhLENBQUM7SUFDNUMsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQzVCLENBQUM7QUFFRDs7O0dBR0c7QUFDSCxTQUFnQixjQUFjLENBQzVCLEdBQTBELEVBQzFELElBQVk7SUFFWixNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxJQUFJLENBQUMsQ0FBQztJQUM1RCxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDZCxNQUFNLElBQUksS0FBSyxDQUFDLHlCQUF5QixJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQ25ELENBQUM7SUFDRCxPQUFPLFFBQVEsQ0FBQyxLQUFLLENBQUM7QUFDeEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qXG4gKiBTaGFyZWQgdXRpbGl0aWVzIGZvciBwYXJzaW5nIElETCB2YWx1ZXMgYW5kIGNvbW1vbiB0eXBlcy5cbiAqIFVzZWQgYWNyb3NzIHByb2dyYW0gU0RLcy5cbiAqL1xuXG4vKiBUcmFuc2FjdGlvbiBPcHRpb25zICovXG5cbmV4cG9ydCBpbnRlcmZhY2UgVHhPcHRpb25zIHtcbiAgaW5jbHVkZUN1QnVkZ2V0PzogYm9vbGVhbjsgIC8vIEluY2x1ZGUgY29tcHV0ZSBidWRnZXQgaW5zdHJ1Y3Rpb24gKGRlZmF1bHQ6IHRydWUpXG4gIGNvbXB1dGVVbml0cz86IG51bWJlcjsgICAgICAvLyBPdmVycmlkZSBkZWZhdWx0IGNvbXB1dGUgdW5pdHNcbn1cblxuLypcbiAqIFBhcnNlIElETCBieXRlcyB2YWx1ZSBzdHJpbmcgdG8gQnVmZmVyLlxuICogZS5nLiwgXCJbOTksIDEwOSwgMTA1LCAxMTAsIDExNl1cIiDihpIgQnVmZmVyLmZyb20oWzk5LCAxMDksIDEwNSwgMTEwLCAxMTZdKVxuICovXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VJZGxCeXRlcyh2YWx1ZTogc3RyaW5nKTogQnVmZmVyIHtcbiAgY29uc3QgYnl0ZXMgPSBKU09OLnBhcnNlKHZhbHVlKSBhcyBudW1iZXJbXTtcbiAgcmV0dXJuIEJ1ZmZlci5mcm9tKGJ5dGVzKTtcbn1cblxuLypcbiAqIEdldCBhIGNvbnN0YW50IGZyb20gYW4gSURMIGJ5IG5hbWUuXG4gKiBUaHJvd3MgaWYgdGhlIGNvbnN0YW50IGlzIG5vdCBmb3VuZCAoaGVscHMgY2F0Y2ggbWlzbWF0Y2hlcyBhZnRlciBJREwgc3luYykuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRJZGxDb25zdGFudChcbiAgaWRsOiB7IGNvbnN0YW50czogQXJyYXk8eyBuYW1lOiBzdHJpbmc7IHZhbHVlOiBzdHJpbmcgfT4gfSxcbiAgbmFtZTogc3RyaW5nXG4pIHtcbiAgY29uc3QgY29uc3RhbnQgPSBpZGwuY29uc3RhbnRzLmZpbmQoKGMpID0+IGMubmFtZSA9PT0gbmFtZSk7XG4gIGlmICghY29uc3RhbnQpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYElETCBtaXNzaW5nIGNvbnN0YW50OiAke25hbWV9YCk7XG4gIH1cbiAgcmV0dXJuIGNvbnN0YW50LnZhbHVlO1xufVxuIl19
28
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdXRpbHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7R0FHRzs7QUFtQkgsc0NBR0M7QUFNRCx3Q0FTQztBQXRCRDs7O0dBR0c7QUFDSCxTQUFnQixhQUFhLENBQUMsS0FBYTtJQUN6QyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBYSxDQUFDO0lBQzVDLE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUM1QixDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsU0FBZ0IsY0FBYyxDQUM1QixHQUEwRCxFQUMxRCxJQUFZO0lBRVosTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLEtBQUssSUFBSSxDQUFDLENBQUM7SUFDNUQsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ2QsTUFBTSxJQUFJLEtBQUssQ0FBQyx5QkFBeUIsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUNuRCxDQUFDO0lBQ0QsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDO0FBQ3hCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuICogU2hhcmVkIHV0aWxpdGllcyBmb3IgcGFyc2luZyBJREwgdmFsdWVzIGFuZCBjb21tb24gdHlwZXMuXG4gKiBVc2VkIGFjcm9zcyBwcm9ncmFtIFNES3MuXG4gKi9cblxuLyogVHJhbnNhY3Rpb24gT3B0aW9ucyAqL1xuXG5leHBvcnQgaW50ZXJmYWNlIFR4T3B0aW9ucyB7XG4gIGluY2x1ZGVDdUJ1ZGdldD86IGJvb2xlYW47ICAvLyBJbmNsdWRlIGNvbXB1dGUgYnVkZ2V0IGluc3RydWN0aW9uIChkZWZhdWx0OiB0cnVlKVxuICBjb21wdXRlVW5pdHM/OiBudW1iZXI7ICAgICAgLy8gT3ZlcnJpZGUgZGVmYXVsdCBjb21wdXRlIHVuaXRzXG4gIC8qKlxuICAgKiBQcmUtY3JlYXRlIGNvbmRpdGlvbmFsIHRva2VuIEFUQXMgYmVmb3JlIGxhdW5jaCB0byBhdm9pZCBleGNlZWRpbmdcbiAgICogU29sYW5hJ3MgNjQgaW5zdHJ1Y3Rpb24gdHJhY2UgbGltaXQuIERlZmF1bHQ6IHRydWUgZm9yIDMrIG9wdGlvbnMuXG4gICAqIFNldCB0byBmYWxzZSBpZiB5b3UndmUgYWxyZWFkeSBjcmVhdGVkIEFUQXMgb3Igd2FudCB0byBtYW5hZ2UgdGhlbSBtYW51YWxseS5cbiAgICovXG4gIGVuc3VyZUFUQXM/OiBib29sZWFuO1xufVxuXG4vKlxuICogUGFyc2UgSURMIGJ5dGVzIHZhbHVlIHN0cmluZyB0byBCdWZmZXIuXG4gKiBlLmcuLCBcIls5OSwgMTA5LCAxMDUsIDExMCwgMTE2XVwiIOKGkiBCdWZmZXIuZnJvbShbOTksIDEwOSwgMTA1LCAxMTAsIDExNl0pXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZUlkbEJ5dGVzKHZhbHVlOiBzdHJpbmcpOiBCdWZmZXIge1xuICBjb25zdCBieXRlcyA9IEpTT04ucGFyc2UodmFsdWUpIGFzIG51bWJlcltdO1xuICByZXR1cm4gQnVmZmVyLmZyb20oYnl0ZXMpO1xufVxuXG4vKlxuICogR2V0IGEgY29uc3RhbnQgZnJvbSBhbiBJREwgYnkgbmFtZS5cbiAqIFRocm93cyBpZiB0aGUgY29uc3RhbnQgaXMgbm90IGZvdW5kIChoZWxwcyBjYXRjaCBtaXNtYXRjaGVzIGFmdGVyIElETCBzeW5jKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldElkbENvbnN0YW50KFxuICBpZGw6IHsgY29uc3RhbnRzOiBBcnJheTx7IG5hbWU6IHN0cmluZzsgdmFsdWU6IHN0cmluZyB9PiB9LFxuICBuYW1lOiBzdHJpbmdcbikge1xuICBjb25zdCBjb25zdGFudCA9IGlkbC5jb25zdGFudHMuZmluZCgoYykgPT4gYy5uYW1lID09PSBuYW1lKTtcbiAgaWYgKCFjb25zdGFudCkge1xuICAgIHRocm93IG5ldyBFcnJvcihgSURMIG1pc3NpbmcgY29uc3RhbnQ6ICR7bmFtZX1gKTtcbiAgfVxuICByZXR1cm4gY29uc3RhbnQudmFsdWU7XG59XG4iXX0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zcomb/programs-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "SDK for ZCombinator Programs",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/amm/client.ts CHANGED
@@ -330,7 +330,7 @@ export class AMMClient {
330
330
  minOutputAmount: BN | number,
331
331
  options?: AmmActionOptions
332
332
  ) {
333
- const { autoWrapUnwrap = true, includeCuBudget = true, computeUnits } = options ?? {};
333
+ const { autoCreateTokenAccounts = true, includeCuBudget = true, computeUnits } = options ?? {};
334
334
 
335
335
  const pool = await this.fetchPool(poolPda);
336
336
  const [reserveA] = this.deriveReservePDA(poolPda, pool.mintA);
@@ -340,7 +340,6 @@ export class AMMClient {
340
340
  const traderAccountB = getAssociatedTokenAddressSync(pool.mintB, trader);
341
341
 
342
342
  const preIxs: TransactionInstruction[] = [];
343
- const postIxs: TransactionInstruction[] = [];
344
343
 
345
344
  if (includeCuBudget) {
346
345
  preIxs.push(
@@ -350,13 +349,7 @@ export class AMMClient {
350
349
  );
351
350
  }
352
351
 
353
- const inputMint = swapAToB ? pool.mintA : pool.mintB;
354
- const outputMint = swapAToB ? pool.mintB : pool.mintA;
355
- const inputAta = swapAToB ? traderAccountA : traderAccountB;
356
- const outputAta = swapAToB ? traderAccountB : traderAccountA;
357
-
358
- if (autoWrapUnwrap) {
359
- // Create ATAs idempotently
352
+ if (autoCreateTokenAccounts) {
360
353
  preIxs.push(
361
354
  createAssociatedTokenAccountIdempotentInstruction(
362
355
  trader,
@@ -371,24 +364,6 @@ export class AMMClient {
371
364
  pool.mintB
372
365
  )
373
366
  );
374
-
375
- // Wrap input SOL if needed
376
- if (inputMint.equals(NATIVE_MINT)) {
377
- const input = typeof inputAmount === "number" ? new BN(inputAmount) : inputAmount;
378
- preIxs.push(
379
- SystemProgram.transfer({
380
- fromPubkey: trader,
381
- toPubkey: inputAta,
382
- lamports: BigInt(input.toString()),
383
- }),
384
- createSyncNativeInstruction(inputAta)
385
- );
386
- }
387
-
388
- // Unwrap output SOL if needed
389
- if (outputMint.equals(NATIVE_MINT)) {
390
- postIxs.push(createCloseAccountInstruction(outputAta, trader, trader));
391
- }
392
367
  }
393
368
 
394
369
  let builder = swapIx(
@@ -408,9 +383,6 @@ export class AMMClient {
408
383
  if (preIxs.length > 0) {
409
384
  builder = builder.preInstructions(preIxs);
410
385
  }
411
- if (postIxs.length > 0) {
412
- builder = builder.postInstructions(postIxs);
413
- }
414
386
 
415
387
  return builder;
416
388
  }
@@ -434,8 +406,7 @@ export class AMMClient {
434
406
  * - Fetches current reserves and computes quote
435
407
  * - Calculates minOutput based on slippage tolerance
436
408
  * - Adds compute budget instruction (if includeCuBudget is true)
437
- * - Creates token accounts if they don't exist (if autoWrapUnwrap is true)
438
- * - Handles WSOL wrapping/unwrapping if needed (if autoWrapUnwrap is true)
409
+ * - Creates token accounts if they don't exist (if autoCreateTokenAccounts is true)
439
410
  */
440
411
  async swapWithSlippage(
441
412
  trader: PublicKey,
@@ -445,7 +416,7 @@ export class AMMClient {
445
416
  slippagePercent: number = 0.5,
446
417
  options?: AmmActionOptions
447
418
  ) {
448
- const { autoWrapUnwrap = true, includeCuBudget = true, computeUnits } = options ?? {};
419
+ const { autoCreateTokenAccounts = true, includeCuBudget = true, computeUnits } = options ?? {};
449
420
 
450
421
  const pool = await this.fetchPool(poolPda);
451
422
  const input = typeof inputAmount === "number" ? new BN(inputAmount) : inputAmount;
@@ -476,7 +447,6 @@ export class AMMClient {
476
447
  );
477
448
 
478
449
  const preIxs: TransactionInstruction[] = [];
479
- const postIxs: TransactionInstruction[] = [];
480
450
 
481
451
  if (includeCuBudget) {
482
452
  preIxs.push(
@@ -486,7 +456,7 @@ export class AMMClient {
486
456
  );
487
457
  }
488
458
 
489
- if (autoWrapUnwrap) {
459
+ if (autoCreateTokenAccounts) {
490
460
  preIxs.push(
491
461
  createAssociatedTokenAccountIdempotentInstruction(
492
462
  trader,
@@ -503,35 +473,9 @@ export class AMMClient {
503
473
  );
504
474
  }
505
475
 
506
- // Handle WSOL for input/output tokens
507
- const inputMint = swapAToB ? pool.mintA : pool.mintB;
508
- const outputMint = swapAToB ? pool.mintB : pool.mintA;
509
- const inputAta = swapAToB ? traderAccountA : traderAccountB;
510
- const outputAta = swapAToB ? traderAccountB : traderAccountA;
511
-
512
- if (autoWrapUnwrap && inputMint.equals(NATIVE_MINT)) {
513
- // Wrap SOL before swap
514
- preIxs.push(
515
- SystemProgram.transfer({
516
- fromPubkey: trader,
517
- toPubkey: inputAta,
518
- lamports: BigInt(input.toString()),
519
- }),
520
- createSyncNativeInstruction(inputAta)
521
- );
522
- }
523
-
524
- if (autoWrapUnwrap && outputMint.equals(NATIVE_MINT)) {
525
- // Unwrap SOL after swap
526
- postIxs.push(createCloseAccountInstruction(outputAta, trader, trader));
527
- }
528
-
529
476
  if (preIxs.length > 0) {
530
477
  builder = builder.preInstructions(preIxs);
531
478
  }
532
- if (postIxs.length > 0) {
533
- builder = builder.postInstructions(postIxs);
534
- }
535
479
 
536
480
  return {
537
481
  builder,
package/src/amm/types.ts CHANGED
@@ -57,5 +57,6 @@ export type AMMEvent =
57
57
  /* Options */
58
58
 
59
59
  export interface AmmActionOptions extends TxOptions {
60
- autoWrapUnwrap?: boolean; // Auto wrap/unwrap native SOL (default: true)
60
+ autoWrapUnwrap?: boolean; // Auto wrap/unwrap native SOL (default: true) - for liquidity operations
61
+ autoCreateTokenAccounts?: boolean; // Auto create token accounts (default: true) - for swaps
61
62
  }
@@ -15,7 +15,12 @@ import {
15
15
  TransactionMessage,
16
16
  VersionedTransaction,
17
17
  } from "@solana/web3.js";
18
- import { getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from "@solana/spl-token";
18
+ import {
19
+ getAssociatedTokenAddressSync,
20
+ TOKEN_PROGRAM_ID,
21
+ ASSOCIATED_TOKEN_PROGRAM_ID,
22
+ createAssociatedTokenAccountIdempotentInstruction,
23
+ } from "@solana/spl-token";
19
24
  import { PROGRAM_ID, SQUADS_PROGRAM_ID } from "./constants";
20
25
  import {
21
26
  Futarchy,
@@ -298,6 +303,14 @@ export class FutarchyClient {
298
303
  // Slice arrays to numOptions (fixed-size arrays from Rust include empty slots)
299
304
  const condBaseMints = vault.condBaseMints.slice(0, numOptions);
300
305
  const condQuoteMints = vault.condQuoteMints.slice(0, numOptions);
306
+
307
+ // Pre-create conditional ATAs for 3+ options to avoid exceeding the
308
+ // 64 instruction trace limit. Each ATA creation via vault deposit adds
309
+ // 5 inner instructions; with 4 options that's 40 extra instructions.
310
+ const shouldEnsureATAs = options?.ensureATAs ?? (numOptions >= 3);
311
+ if (shouldEnsureATAs) {
312
+ await this._createConditionalATAs(creator, condBaseMints, condQuoteMints);
313
+ }
301
314
  const pools = proposal.pools.slice(0, numOptions);
302
315
 
303
316
  // Derive all user conditional token ATAs
@@ -367,6 +380,69 @@ export class FutarchyClient {
367
380
  return { builder };
368
381
  }
369
382
 
383
+ /**
384
+ * Pre-creates all conditional token ATAs for a user before launching a proposal.
385
+ *
386
+ * This is REQUIRED for proposals with 3+ options to avoid exceeding Solana's
387
+ * max instruction trace length limit (64 instructions). The vault's deposit CPI
388
+ * creates ATAs on-the-fly, each requiring 5 inner instructions. For 4 options:
389
+ * 8 ATAs × 5 = 40 extra instructions, pushing the total over 64.
390
+ *
391
+ * Pre-creating ATAs eliminates this overhead, reducing the trace to ~32 instructions.
392
+ *
393
+ * @param creator - The user who will receive conditional tokens
394
+ * @param proposalPda - The proposal PDA (must be initialized but not launched)
395
+ * @returns Transaction signature
396
+ */
397
+ async ensureConditionalATAs(
398
+ creator: PublicKey,
399
+ proposalPda: PublicKey,
400
+ ): Promise<string> {
401
+ const proposal = await this.fetchProposal(proposalPda);
402
+ const vault = await this.vault.fetchVault(proposal.vault);
403
+ const condBaseMints = vault.condBaseMints.slice(0, proposal.numOptions);
404
+ const condQuoteMints = vault.condQuoteMints.slice(0, proposal.numOptions);
405
+ return this._createConditionalATAs(creator, condBaseMints, condQuoteMints);
406
+ }
407
+
408
+ /**
409
+ * Internal helper to create conditional ATAs given mint arrays.
410
+ * Used by both ensureConditionalATAs and launchProposal to avoid redundant fetches.
411
+ */
412
+ private async _createConditionalATAs(
413
+ creator: PublicKey,
414
+ condBaseMints: PublicKey[],
415
+ condQuoteMints: PublicKey[],
416
+ ): Promise<string> {
417
+ const provider = this.program.provider as AnchorProvider;
418
+
419
+ // Build ATA creation instructions (idempotent - won't fail if exists)
420
+ const instructions: TransactionInstruction[] = [];
421
+ for (let i = 0; i < condBaseMints.length; i++) {
422
+ const userCondBaseAta = getAssociatedTokenAddressSync(condBaseMints[i], creator);
423
+ const userCondQuoteAta = getAssociatedTokenAddressSync(condQuoteMints[i], creator);
424
+
425
+ instructions.push(
426
+ createAssociatedTokenAccountIdempotentInstruction(
427
+ creator,
428
+ userCondBaseAta,
429
+ creator,
430
+ condBaseMints[i]
431
+ ),
432
+ createAssociatedTokenAccountIdempotentInstruction(
433
+ creator,
434
+ userCondQuoteAta,
435
+ creator,
436
+ condQuoteMints[i]
437
+ )
438
+ );
439
+ }
440
+
441
+ // Send transaction
442
+ const tx = new Transaction().add(...instructions);
443
+ return provider.sendAndConfirm(tx);
444
+ }
445
+
370
446
  async finalizeProposal(signer: PublicKey, proposalPda: PublicKey, options?: TxOptions) {
371
447
  const proposal = await this.fetchProposal(proposalPda);
372
448
  const vault = await this.vault.fetchVault(proposal.vault);
@@ -455,7 +531,206 @@ export class FutarchyClient {
455
531
  remainingAccounts
456
532
  ).preInstructions(this.maybeAddComputeBudget(options));
457
533
 
458
- return { builder };
534
+ return { builder, numOptions };
535
+ }
536
+
537
+ /**
538
+ * Creates an Address Lookup Table for redemption operations.
539
+ * Required for proposals with 3+ options to avoid exceeding transaction size limits.
540
+ *
541
+ * @param creator - The user redeeming (creator of proposal or liquidity provider)
542
+ * @param proposalPda - The proposal PDA
543
+ * @returns ALT address
544
+ */
545
+ async createRedemptionALT(
546
+ creator: PublicKey,
547
+ proposalPda: PublicKey,
548
+ ): Promise<{ altAddress: PublicKey }> {
549
+ const provider = this.program.provider as AnchorProvider;
550
+ const proposal = await this.fetchProposal(proposalPda);
551
+ const vault = await this.vault.fetchVault(proposal.vault);
552
+ const numOptions = proposal.numOptions;
553
+
554
+ const { winningIdx } = parseProposalState(proposal.state);
555
+ if (winningIdx === null) {
556
+ throw new Error("Proposal not finalized");
557
+ }
558
+
559
+ const addresses: PublicKey[] = [
560
+ // Programs
561
+ this.programId,
562
+ this.vault.programId,
563
+ this.amm.programId,
564
+ SystemProgram.programId,
565
+ TOKEN_PROGRAM_ID,
566
+ ASSOCIATED_TOKEN_PROGRAM_ID,
567
+ // Core accounts
568
+ proposalPda,
569
+ proposal.vault,
570
+ proposal.moderator,
571
+ vault.baseMint.address,
572
+ vault.quoteMint.address,
573
+ // Winning pool and reserves
574
+ proposal.pools[winningIdx],
575
+ // Vault token accounts
576
+ getAssociatedTokenAddressSync(vault.baseMint.address, proposal.vault, true),
577
+ getAssociatedTokenAddressSync(vault.quoteMint.address, proposal.vault, true),
578
+ // Creator's base/quote ATAs
579
+ getAssociatedTokenAddressSync(vault.baseMint.address, creator),
580
+ getAssociatedTokenAddressSync(vault.quoteMint.address, creator),
581
+ ];
582
+
583
+ // Winning pool reserves
584
+ const [reserveA] = deriveReservePDA(proposal.pools[winningIdx], vault.condQuoteMints[winningIdx], this.amm.programId);
585
+ const [reserveB] = deriveReservePDA(proposal.pools[winningIdx], vault.condBaseMints[winningIdx], this.amm.programId);
586
+ addresses.push(reserveA, reserveB);
587
+
588
+ // Per-option accounts (conditional mints and user ATAs)
589
+ for (let i = 0; i < numOptions; i++) {
590
+ addresses.push(
591
+ vault.condBaseMints[i],
592
+ vault.condQuoteMints[i],
593
+ getAssociatedTokenAddressSync(vault.condBaseMints[i], creator),
594
+ getAssociatedTokenAddressSync(vault.condQuoteMints[i], creator),
595
+ );
596
+ }
597
+
598
+ // Get recent slot for ALT creation
599
+ const slot = await provider.connection.getSlot("finalized");
600
+
601
+ const [createIx, altAddress] = AddressLookupTableProgram.createLookupTable({
602
+ authority: creator,
603
+ payer: creator,
604
+ recentSlot: slot,
605
+ });
606
+
607
+ // Create ALT
608
+ const createTx = new Transaction().add(createIx);
609
+ createTx.recentBlockhash = (await provider.connection.getLatestBlockhash()).blockhash;
610
+ createTx.feePayer = creator;
611
+ const signedTx = await provider.wallet.signTransaction(createTx);
612
+ const sig = await provider.connection.sendRawTransaction(signedTx.serialize(), {
613
+ skipPreflight: true,
614
+ });
615
+ await provider.connection.confirmTransaction(sig, "confirmed");
616
+
617
+ // Extend ALT with addresses
618
+ const CHUNK_SIZE = 20;
619
+ for (let i = 0; i < addresses.length; i += CHUNK_SIZE) {
620
+ const chunk = addresses.slice(i, i + CHUNK_SIZE);
621
+ const extendIx = AddressLookupTableProgram.extendLookupTable({
622
+ payer: creator,
623
+ authority: creator,
624
+ lookupTable: altAddress,
625
+ addresses: chunk,
626
+ });
627
+ const extendTx = new Transaction().add(extendIx);
628
+ await provider.sendAndConfirm(extendTx);
629
+ }
630
+
631
+ return { altAddress };
632
+ }
633
+
634
+ /**
635
+ * Builds a versioned transaction for redeeming liquidity with ALT.
636
+ * Required for proposals with 3+ options to avoid exceeding transaction size limits.
637
+ *
638
+ * @param creator - The user redeeming
639
+ * @param proposalPda - The proposal PDA
640
+ * @param altAddress - Optional ALT address (will be created if not provided for 3+ options)
641
+ * @returns Unsigned versioned transaction, ALT address, and number of options
642
+ */
643
+ async redeemLiquidityVersioned(
644
+ creator: PublicKey,
645
+ proposalPda: PublicKey,
646
+ altAddress?: PublicKey,
647
+ ): Promise<{ versionedTx: VersionedTransaction; altAddress: PublicKey; numOptions: number }> {
648
+ const provider = this.program.provider as AnchorProvider;
649
+ const { builder, numOptions } = await this.redeemLiquidity(creator, proposalPda);
650
+
651
+ // Create ALT if not provided and needed
652
+ let altPubkey = altAddress;
653
+ let verifiedALT: AddressLookupTableAccount | null = null;
654
+
655
+ if (!altPubkey && numOptions >= 3) {
656
+ console.log(` Creating redemption ALT for ${numOptions} options...`);
657
+ const result = await this.createRedemptionALT(creator, proposalPda);
658
+ altPubkey = result.altAddress;
659
+ console.log(` ✓ Redemption ALT created: ${altPubkey.toBase58()}`);
660
+
661
+ // Wait for ALT to be fully available with all addresses
662
+ // Use longer delays after extending to ensure propagation
663
+ console.log(` Waiting for ALT propagation...`);
664
+ const expectedAddresses = 18 + (numOptions * 4); // Base accounts + per-option accounts
665
+ let attempts = 0;
666
+
667
+ // Initial delay after extension
668
+ await new Promise(resolve => setTimeout(resolve, 2000));
669
+
670
+ while (attempts < 30) {
671
+ const altAccount = await provider.connection.getAddressLookupTable(altPubkey, {
672
+ commitment: 'confirmed',
673
+ });
674
+ if (altAccount.value) {
675
+ const addressCount = altAccount.value.state.addresses.length;
676
+ console.log(` Attempt ${attempts + 1}: ALT has ${addressCount}/${expectedAddresses} addresses`);
677
+ if (addressCount >= expectedAddresses) {
678
+ verifiedALT = altAccount.value;
679
+ console.log(` ✓ ALT verified with ${addressCount} addresses`);
680
+ break;
681
+ }
682
+ }
683
+ await new Promise(resolve => setTimeout(resolve, 1000));
684
+ attempts++;
685
+ }
686
+
687
+ if (!verifiedALT) {
688
+ throw new Error(`ALT failed to populate with expected ${expectedAddresses} addresses after ${attempts} attempts`);
689
+ }
690
+ }
691
+
692
+ if (!altPubkey) {
693
+ throw new Error("ALT address required for multi-option redemption");
694
+ }
695
+
696
+ // Fetch ALT if we don't have it verified already
697
+ if (!verifiedALT) {
698
+ verifiedALT = await this.fetchALT(altPubkey);
699
+ }
700
+
701
+ // Build instruction
702
+ const instruction = await builder.instruction();
703
+ const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 });
704
+
705
+ // Get fresh blockhash
706
+ const { blockhash } = await provider.connection.getLatestBlockhash();
707
+
708
+ // Build versioned transaction using the verified ALT (no re-fetch)
709
+ const versionedTx = this.buildVersionedTxWithALT(
710
+ creator,
711
+ [computeBudgetIx, instruction],
712
+ verifiedALT,
713
+ blockhash,
714
+ );
715
+
716
+ // Return the versioned transaction for the caller to sign and send
717
+ // This allows the caller to use their own signing mechanism (e.g., keypair.sign)
718
+ return { versionedTx, altAddress: altPubkey, numOptions };
719
+ }
720
+
721
+ /**
722
+ * Helper to send a signed versioned transaction.
723
+ */
724
+ async sendVersionedTransaction(
725
+ signedTx: VersionedTransaction,
726
+ ): Promise<string> {
727
+ const provider = this.program.provider as AnchorProvider;
728
+ const signature = await provider.connection.sendTransaction(signedTx, {
729
+ skipPreflight: false,
730
+ preflightCommitment: 'confirmed',
731
+ });
732
+ await provider.connection.confirmTransaction(signature, 'confirmed');
733
+ return signature;
459
734
  }
460
735
 
461
736
  /* Address Lookup Table */
@@ -566,12 +841,21 @@ export class FutarchyClient {
566
841
  instructions: TransactionInstruction[],
567
842
  altAddress: PublicKey,
568
843
  ): Promise<VersionedTransaction> {
844
+ const provider = this.program.provider as AnchorProvider;
569
845
  const alt = await this.fetchALT(altAddress);
570
- const blockhash = await this.program.provider.connection.getLatestBlockhash();
846
+ const { blockhash } = await provider.connection.getLatestBlockhash();
847
+ return this.buildVersionedTxWithALT(payer, instructions, alt, blockhash);
848
+ }
571
849
 
850
+ buildVersionedTxWithALT(
851
+ payer: PublicKey,
852
+ instructions: TransactionInstruction[],
853
+ alt: AddressLookupTableAccount,
854
+ blockhash: string,
855
+ ): VersionedTransaction {
572
856
  const message = new TransactionMessage({
573
857
  payerKey: payer,
574
- recentBlockhash: blockhash.blockhash,
858
+ recentBlockhash: blockhash,
575
859
  instructions,
576
860
  }).compileToV0Message([alt]);
577
861
 
@@ -729,57 +1013,4 @@ export class FutarchyClient {
729
1013
 
730
1014
  return { builder, daoPda, moderatorPda };
731
1015
  }
732
-
733
- /* High-Level Proposal Creator */
734
-
735
- async createProposal(
736
- creator: PublicKey,
737
- moderatorPda: PublicKey,
738
- proposalParams: ProposalParams,
739
- baseAmount: BN | number,
740
- quoteAmount: BN | number,
741
- numOptions: number = 2,
742
- metadata?: string,
743
- options?: TxOptions
744
- ) {
745
- if (numOptions < 2) {
746
- throw new Error("Minimum 2 options required");
747
- }
748
-
749
- // 1. Create ALT (sends txs internally)
750
- const { altAddress } = await this.createProposalALT(creator, moderatorPda, numOptions);
751
-
752
- // 2. Build initializeProposal
753
- const initResult = await this.initializeProposal(creator, moderatorPda, proposalParams, metadata, options);
754
-
755
- // 3. Build addOption for each additional option (beyond initial 2)
756
- const addOptionBuilders: Awaited<ReturnType<typeof this.addOption>>["builder"][] = [];
757
- const additionalPools: PublicKey[] = [];
758
- const additionalCondBaseMints: PublicKey[] = [];
759
- const additionalCondQuoteMints: PublicKey[] = [];
760
-
761
- for (let i = 2; i < numOptions; i++) {
762
- const addResult = await this.addOption(creator, initResult.proposalPda, options);
763
- addOptionBuilders.push(addResult.builder);
764
- additionalPools.push(addResult.pool);
765
- additionalCondBaseMints.push(addResult.condBaseMint);
766
- additionalCondQuoteMints.push(addResult.condQuoteMint);
767
- }
768
-
769
- // 4. Build launchProposal
770
- const launchResult = await this.launchProposal(creator, initResult.proposalPda, baseAmount, quoteAmount, options);
771
-
772
- return {
773
- altAddress,
774
- proposalPda: initResult.proposalPda,
775
- proposalId: initResult.proposalId,
776
- vaultPda: initResult.vaultPda,
777
- pools: [...initResult.pools, ...additionalPools],
778
- condBaseMints: [...initResult.condBaseMints, ...additionalCondBaseMints],
779
- condQuoteMints: [...initResult.condQuoteMints, ...additionalCondQuoteMints],
780
- initializeBuilder: initResult.builder,
781
- addOptionBuilders,
782
- launchBuilder: launchResult.builder,
783
- };
784
- }
785
1016
  }
package/src/utils.ts CHANGED
@@ -8,6 +8,12 @@
8
8
  export interface TxOptions {
9
9
  includeCuBudget?: boolean; // Include compute budget instruction (default: true)
10
10
  computeUnits?: number; // Override default compute units
11
+ /**
12
+ * Pre-create conditional token ATAs before launch to avoid exceeding
13
+ * Solana's 64 instruction trace limit. Default: true for 3+ options.
14
+ * Set to false if you've already created ATAs or want to manage them manually.
15
+ */
16
+ ensureATAs?: boolean;
11
17
  }
12
18
 
13
19
  /*