Skip to content

Commit a41b41b

Browse files
authored
add extend_launch instruction for admin to extend live launch duration (#445)
* add extend_launch instruction for admin to extend live launch duration * unify fee recipient into metadao operational multisig vault * remove incorrect error message
1 parent c7de3bb commit a41b41b

12 files changed

Lines changed: 523 additions & 6 deletions

File tree

Anchor.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ v07-dump-launches-funding-records = "yarn run tsx scripts/v0.7/dumpLaunchesAndFu
6969
v07-resize-launches-funding-records = "yarn run tsx scripts/v0.7/resizeLaunchesAndFundingRecords.ts"
7070
v07-dump-launches = "yarn run tsx scripts/v0.7/dumpLaunches.ts"
7171
v07-resize-launches = "yarn run tsx scripts/v0.7/resizeLaunches.ts"
72+
v07-extend-launch = "yarn run tsx scripts/v0.7/extendLaunch.ts"
7273

7374
[test]
7475
startup_wait = 5000

programs/v07_launchpad/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,6 @@ pub enum LaunchpadError {
6464
InvalidDao,
6565
#[msg("Accumulator activation delay must be less than the launch duration")]
6666
InvalidAccumulatorActivationDelaySeconds,
67+
#[msg("The extend duration would exceed the maximum allowed launch duration")]
68+
ExtendDurationExceedsMax,
6769
}

programs/v07_launchpad/src/events.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,11 @@ pub struct LaunchPerformancePackageInitializedEvent {
125125
pub launch: Pubkey,
126126
pub performance_package: Pubkey,
127127
}
128+
129+
#[event]
130+
pub struct LaunchExtendedEvent {
131+
pub common: CommonFields,
132+
pub launch: Pubkey,
133+
pub old_seconds_for_launch: u32,
134+
pub new_seconds_for_launch: u32,
135+
}

programs/v07_launchpad/src/instructions/complete_launch.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ use crate::error::LaunchpadError;
1717
use crate::events::{CommonFields, LaunchCloseEvent, LaunchCompletedEvent};
1818
use crate::state::{Launch, LaunchState};
1919
use crate::{
20-
fee_recipient, PRICE_SCALE, PROPOSAL_MIN_STAKE_TOKENS, TOKENS_TO_DAMM_V2_LIQUIDITY_UNSCALED,
21-
TOKENS_TO_FUTARCHY_LIQUIDITY, TOKENS_TO_PARTICIPANTS, TOKEN_SCALE,
20+
metadao_multisig_vault, PRICE_SCALE, PROPOSAL_MIN_STAKE_TOKENS,
21+
TOKENS_TO_DAMM_V2_LIQUIDITY_UNSCALED, TOKENS_TO_FUTARCHY_LIQUIDITY, TOKENS_TO_PARTICIPANTS,
22+
TOKEN_SCALE,
2223
};
2324
use anchor_spl::metadata::{
2425
mpl_token_metadata::ID as MPL_TOKEN_METADATA_PROGRAM_ID, update_metadata_accounts_v2, Metadata,
@@ -213,7 +214,7 @@ pub struct CompleteLaunch<'info> {
213214
pub bid_wall_quote_token_account: UncheckedAccount<'info>,
214215

215216
/// CHECK: The fee recipient of bid wall fees, a fixed address
216-
#[account(address = fee_recipient::id())]
217+
#[account(address = metadao_multisig_vault::id())]
217218
pub fee_recipient: AccountInfo<'info>,
218219

219220
pub system_program: Program<'info, System>,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use anchor_lang::prelude::*;
2+
3+
use crate::error::LaunchpadError;
4+
use crate::events::{CommonFields, LaunchExtendedEvent};
5+
use crate::state::{Launch, LaunchState};
6+
7+
#[cfg(feature = "production")]
8+
use crate::metadao_multisig_vault;
9+
10+
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
11+
pub struct ExtendLaunchArgs {
12+
pub duration_seconds: u32,
13+
}
14+
15+
#[event_cpi]
16+
#[derive(Accounts)]
17+
pub struct ExtendLaunch<'info> {
18+
#[account(mut)]
19+
pub launch: Account<'info, Launch>,
20+
21+
pub admin: Signer<'info>,
22+
}
23+
24+
impl ExtendLaunch<'_> {
25+
pub fn validate(&self, args: &ExtendLaunchArgs) -> Result<()> {
26+
#[cfg(feature = "production")]
27+
require_keys_eq!(self.admin.key(), metadao_multisig_vault::ID);
28+
29+
require!(
30+
self.launch.state == LaunchState::Live,
31+
LaunchpadError::InvalidLaunchState
32+
);
33+
34+
require_gt!(args.duration_seconds, 0, LaunchpadError::InvalidAmount);
35+
36+
require!(
37+
self.launch
38+
.seconds_for_launch
39+
.checked_add(args.duration_seconds)
40+
.is_some(),
41+
LaunchpadError::ExtendDurationExceedsMax
42+
);
43+
44+
Ok(())
45+
}
46+
47+
pub fn handle(ctx: Context<Self>, args: ExtendLaunchArgs) -> Result<()> {
48+
let launch = &mut ctx.accounts.launch;
49+
let clock = Clock::get()?;
50+
51+
let old_seconds_for_launch = launch.seconds_for_launch;
52+
53+
launch.seconds_for_launch = launch
54+
.seconds_for_launch
55+
.checked_add(args.duration_seconds)
56+
.unwrap();
57+
58+
launch.seq_num += 1;
59+
60+
emit_cpi!(LaunchExtendedEvent {
61+
common: CommonFields::new(&clock, launch.seq_num),
62+
launch: launch.key(),
63+
old_seconds_for_launch,
64+
new_seconds_for_launch: launch.seconds_for_launch,
65+
});
66+
67+
Ok(())
68+
}
69+
}

programs/v07_launchpad/src/instructions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod claim;
22
pub mod claim_additional_token_allocation;
33
pub mod close_launch;
44
pub mod complete_launch;
5+
pub mod extend_launch;
56
pub mod fund;
67
pub mod initialize_launch;
78
pub mod initialize_performance_package;
@@ -15,6 +16,7 @@ pub use claim::*;
1516
pub use claim_additional_token_allocation::*;
1617
pub use close_launch::*;
1718
pub use complete_launch::*;
19+
pub use extend_launch::*;
1820
pub use fund::*;
1921
pub use initialize_launch::*;
2022
pub use initialize_performance_package::*;

programs/v07_launchpad/src/lib.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,10 @@ pub mod usdc_mint {
5050
declare_id!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
5151
}
5252

53-
// TODO - Pileks: Set this to the correct fee recipient address
54-
pub mod fee_recipient {
53+
pub mod metadao_multisig_vault {
5554
use anchor_lang::prelude::declare_id;
5655

57-
// MetaDAO multisig vault
56+
// MetaDAO operations multisig vault
5857
declare_id!("6awyHMshBGVjJ3ozdSJdyyDE1CTAXUwrpNMaRGMsb4sf");
5958
}
6059

@@ -129,4 +128,9 @@ pub mod launchpad_v7 {
129128
pub fn resize_launch(ctx: Context<ResizeLaunch>) -> Result<()> {
130129
ResizeLaunch::handle(ctx)
131130
}
131+
132+
#[access_control(ctx.accounts.validate(&args))]
133+
pub fn extend_launch(ctx: Context<ExtendLaunch>, args: ExtendLaunchArgs) -> Result<()> {
134+
ExtendLaunch::handle(ctx, args)
135+
}
132136
}

scripts/v0.7/extendLaunch.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import * as anchor from "@coral-xyz/anchor";
2+
import * as multisig from "@sqds/multisig";
3+
import {
4+
LaunchpadClient,
5+
METADAO_MULTISIG_VAULT,
6+
} from "@metadaoproject/futarchy/v0.7";
7+
import { PublicKey, Transaction, TransactionMessage } from "@solana/web3.js";
8+
9+
// Set the launch address before running the script
10+
const launch = new PublicKey("");
11+
12+
// Set the number of seconds to extend the launch by
13+
const durationSeconds = 60 * 60 * 24; // 1 day
14+
15+
const provider = anchor.AnchorProvider.env();
16+
17+
// Payer MUST be a signer with permissions to propose transactions on MetaDAO's multisig
18+
const payer = provider.wallet["payer"];
19+
20+
const launchpad: LaunchpadClient = LaunchpadClient.createClient({ provider });
21+
22+
// MetaDAO Squads multisig and vault addresses
23+
const metadaoSquadsMultisig = new PublicKey(
24+
"8N3Tvc6B1wEVKVC6iD4s6eyaCNqX2ovj2xze2q3Q9DWH",
25+
);
26+
const metadaoSquadsMultisigVault = METADAO_MULTISIG_VAULT;
27+
28+
export const extendLaunch = async () => {
29+
const launchAccount = await launchpad.getLaunch(launch);
30+
31+
console.log(`Extending launch: ${launch.toBase58()}`);
32+
console.log(`Current seconds_for_launch: ${launchAccount.secondsForLaunch}`);
33+
console.log(`Extension: ${durationSeconds} seconds`);
34+
console.log(
35+
`New seconds_for_launch: ${launchAccount.secondsForLaunch + durationSeconds}`,
36+
);
37+
38+
// Build the extend_launch instruction
39+
const extendLaunchIx = await launchpad
40+
.extendLaunchIx({
41+
launch,
42+
durationSeconds,
43+
admin: metadaoSquadsMultisigVault,
44+
})
45+
.instruction();
46+
47+
// Build the transaction message with the multisig vault as payer
48+
const transactionMessage = new TransactionMessage({
49+
instructions: [extendLaunchIx],
50+
payerKey: metadaoSquadsMultisigVault,
51+
recentBlockhash: (await provider.connection.getLatestBlockhash()).blockhash,
52+
});
53+
54+
// Log the transaction message as base64
55+
const compiledMessage = transactionMessage.compileToLegacyMessage();
56+
const base64Message = Buffer.from(compiledMessage.serialize()).toString(
57+
"base64",
58+
);
59+
console.log("\nTransaction message (base64):");
60+
console.log(base64Message);
61+
62+
// TODO: Uncomment this when ready to extend the launch
63+
return;
64+
65+
// Fetch the current multisig state to get the next transaction index
66+
const metaDaoSquadsMultisigAccount =
67+
await multisig.accounts.Multisig.fromAccountAddress(
68+
provider.connection,
69+
metadaoSquadsMultisig,
70+
);
71+
72+
const transactionIndex =
73+
BigInt(metaDaoSquadsMultisigAccount.transactionIndex.toString()) + 1n;
74+
75+
// Create vault transaction instruction
76+
const vaultTxCreateIx = multisig.instructions.vaultTransactionCreate({
77+
multisigPda: metadaoSquadsMultisig,
78+
transactionIndex,
79+
creator: payer.publicKey,
80+
rentPayer: payer.publicKey,
81+
vaultIndex: 0,
82+
ephemeralSigners: 0,
83+
transactionMessage,
84+
});
85+
86+
// Create proposal instruction
87+
const proposalCreateIx = multisig.instructions.proposalCreate({
88+
multisigPda: metadaoSquadsMultisig,
89+
transactionIndex,
90+
creator: payer.publicKey,
91+
rentPayer: payer.publicKey,
92+
isDraft: false,
93+
});
94+
95+
// Build, sign, and send the transaction
96+
const tx = new Transaction().add(vaultTxCreateIx, proposalCreateIx);
97+
tx.recentBlockhash = (
98+
await provider.connection.getLatestBlockhash()
99+
).blockhash;
100+
tx.feePayer = payer.publicKey;
101+
tx.sign(payer);
102+
103+
const txHash = await provider.connection.sendRawTransaction(tx.serialize());
104+
await provider.connection.confirmTransaction(txHash, "confirmed");
105+
106+
const [proposalPda] = multisig.getProposalPda({
107+
multisigPda: metadaoSquadsMultisig,
108+
transactionIndex,
109+
});
110+
111+
console.log("\nVault transaction + proposal created successfully!");
112+
console.log("Transaction signature:", txHash);
113+
console.log("Proposal index:", transactionIndex.toString());
114+
console.log("Proposal PDA:", proposalPda.toBase58());
115+
console.log("Go ahead and approve/execute the transaction through Squads.");
116+
};
117+
118+
extendLaunch().catch(console.error);

sdk/src/v0.7/LaunchpadClient.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,21 @@ export class LaunchpadClient {
715715
});
716716
}
717717

718+
extendLaunchIx({
719+
launch,
720+
durationSeconds,
721+
admin = METADAO_MULTISIG_VAULT,
722+
}: {
723+
launch: PublicKey;
724+
durationSeconds: number;
725+
admin?: PublicKey;
726+
}) {
727+
return this.launchpad.methods.extendLaunch({ durationSeconds }).accounts({
728+
launch,
729+
admin,
730+
});
731+
}
732+
718733
getLaunchAddress({ baseMint }: { baseMint: PublicKey }): PublicKey {
719734
return getLaunchAddr(this.launchpad.programId, baseMint)[0];
720735
}

0 commit comments

Comments
 (0)