Skip to content

Commit f63d53a

Browse files
committed
add extend_launch instruction for admin to extend live launch duration
1 parent c7de3bb commit f63d53a

11 files changed

Lines changed: 528 additions & 0 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+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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!(
28+
self.admin.key(),
29+
metadao_multisig_vault::ID,
30+
LaunchpadError::InvalidLaunchState
31+
);
32+
33+
require!(
34+
self.launch.state == LaunchState::Live,
35+
LaunchpadError::InvalidLaunchState
36+
);
37+
38+
require_gt!(args.duration_seconds, 0, LaunchpadError::InvalidAmount);
39+
40+
require!(
41+
self.launch
42+
.seconds_for_launch
43+
.checked_add(args.duration_seconds)
44+
.is_some(),
45+
LaunchpadError::ExtendDurationExceedsMax
46+
);
47+
48+
Ok(())
49+
}
50+
51+
pub fn handle(ctx: Context<Self>, args: ExtendLaunchArgs) -> Result<()> {
52+
let launch = &mut ctx.accounts.launch;
53+
let clock = Clock::get()?;
54+
55+
let old_seconds_for_launch = launch.seconds_for_launch;
56+
57+
launch.seconds_for_launch = launch
58+
.seconds_for_launch
59+
.checked_add(args.duration_seconds)
60+
.unwrap();
61+
62+
launch.seq_num += 1;
63+
64+
emit_cpi!(LaunchExtendedEvent {
65+
common: CommonFields::new(&clock, launch.seq_num),
66+
launch: launch.key(),
67+
old_seconds_for_launch,
68+
new_seconds_for_launch: launch.seconds_for_launch,
69+
});
70+
71+
Ok(())
72+
}
73+
}

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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ pub mod fee_recipient {
5858
declare_id!("6awyHMshBGVjJ3ozdSJdyyDE1CTAXUwrpNMaRGMsb4sf");
5959
}
6060

61+
pub mod metadao_multisig_vault {
62+
use anchor_lang::prelude::declare_id;
63+
64+
// MetaDAO operations multisig vault
65+
declare_id!("6awyHMshBGVjJ3ozdSJdyyDE1CTAXUwrpNMaRGMsb4sf");
66+
}
67+
6168
#[program]
6269
pub mod launchpad_v7 {
6370
use super::*;
@@ -129,4 +136,9 @@ pub mod launchpad_v7 {
129136
pub fn resize_launch(ctx: Context<ResizeLaunch>) -> Result<()> {
130137
ResizeLaunch::handle(ctx)
131138
}
139+
140+
#[access_control(ctx.accounts.validate(&args))]
141+
pub fn extend_launch(ctx: Context<ExtendLaunch>, args: ExtendLaunchArgs) -> Result<()> {
142+
ExtendLaunch::handle(ctx, args)
143+
}
132144
}

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)