@zcomb/programs-sdk 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.3.0",
4
4
  "description": "SDK for ZCombinator Programs",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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
  /*