Skip to content

Commit faa929b

Browse files
authored
Merge pull request #269 from metaDAOproject/pileks/launch-scripts
Launchpad scripts
2 parents 23b7ccc + 698639c commit faa929b

File tree

12 files changed

+444
-10
lines changed

12 files changed

+444
-10
lines changed

.env.example

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Settings for launchpad initialization and starting a launch
2+
NETWORK="devnet"
3+
RPC_URL="https://api.devnet.solana.com"
4+
WALLET_PATH=".config/solana/id.json"
5+
LAUNCH_AUTHORITY_KEYPAIR_PATH=".config/solana/launch_authority.json"
6+
LAUNCH_TOKEN_NAME="Token Name"
7+
LAUNCH_TOKEN_SYMBOL="TKN"
8+
LAUNCH_TOKEN_URI="https://www.example.com/"
9+
MINIMUM_RAISE_AMOUNT="10"
10+
SECONDS_FOR_LAUNCH="10"
11+
LAUNCH_ADDRESS="" #used for starting and completing a launch

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
"scripts": {
55
"lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
66
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check",
7-
"setup-metric-market": "tsx --tsconfig tsconfig.json scripts/setupMetricMarket.ts"
7+
"setup-metric-market": "tsx --tsconfig tsconfig.json scripts/setupMetricMarket.ts",
8+
"launch-init": "NODE_OPTIONS=\"--no-deprecation\" tsx --tsconfig tsconfig.json scripts/launchInit.ts",
9+
"launch-start": "NODE_OPTIONS=\"--no-deprecation\" tsx --tsconfig tsconfig.json scripts/launchStart.ts",
10+
"launch-complete": "NODE_OPTIONS=\"--no-deprecation\" tsx --tsconfig tsconfig.json scripts/launchComplete.ts"
811
},
912
"dependencies": {
1013
"@coral-xyz/anchor": "0.29.0",
@@ -34,6 +37,7 @@
3437
"@types/mocha": "^10.0.7",
3538
"@types/node": "^20.8.6",
3639
"chai": "^4.3.4",
40+
"dotenv": "^16.4.7",
3741
"mocha": "^9.0.3",
3842
"prettier": "^2.6.2",
3943
"ts-mocha": "^10.0.0",

scripts/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Scripts
2+
3+
A collection of scripts to interact with various parts of the futarchy protocol
4+
5+
## Launchpad
6+
7+
In the root directory of this repo, you will find a `.env.example` file with settings for launches. Move into `.env`, change everything, and then simply hold enter throughout the script. If you don't, you can always enter everything manually.
8+
9+
To initialize a launch, run:
10+
```sh
11+
yarn launch-init
12+
```
13+
14+
To start a launch, run:
15+
```sh
16+
yarn launch-start
17+
```
18+
19+
To complete a launch, run:
20+
```sh
21+
yarn launch-complete
22+
```

scripts/launchComplete.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {
2+
ComputeBudgetProgram,
3+
Keypair,
4+
Transaction,
5+
PublicKey,
6+
} from "@solana/web3.js";
7+
import * as anchor from "@coral-xyz/anchor";
8+
import { LaunchpadClient } from "@metadaoproject/futarchy/v0.4";
9+
import { homedir } from "os";
10+
import { join } from "path";
11+
import { input } from "@inquirer/prompts";
12+
13+
import dotenv from "dotenv";
14+
15+
dotenv.config();
16+
17+
const rpcUrl = await input({
18+
message: "Enter your RPC URL:",
19+
default: process.env.RPC_URL,
20+
});
21+
22+
const walletPath = await input({
23+
message: "Enter the path (relative to home directory) to your wallet file",
24+
default: join(homedir(), process.env.WALLET_PATH),
25+
});
26+
process.env.ANCHOR_WALLET = walletPath;
27+
const provider = anchor.AnchorProvider.local(rpcUrl, {
28+
commitment: "confirmed",
29+
});
30+
const payer = provider.wallet["payer"];
31+
32+
const launchAddr = new PublicKey(
33+
await input({
34+
message: "Enter the launch address",
35+
default: process.env.LAUNCH_ADDRESS,
36+
})
37+
);
38+
39+
const launchpad: LaunchpadClient = LaunchpadClient.createClient({ provider });
40+
41+
async function main() {
42+
const launch = await launchpad.getLaunch(launchAddr);
43+
44+
const tx = await launchpad
45+
.completeLaunchIx(launchAddr, launch.tokenMint, true)
46+
.transaction();
47+
48+
await sendAndConfirmTransaction(tx, "Complete launch");
49+
50+
console.log("Launch completed!");
51+
}
52+
53+
// Make sure the promise rejection is handled
54+
main().catch((error) => {
55+
console.error("Fatal error:", error);
56+
process.exit(1);
57+
});
58+
59+
async function sendAndConfirmTransaction(
60+
tx: Transaction,
61+
label: string,
62+
signers: Keypair[] = []
63+
) {
64+
tx.feePayer = payer.publicKey;
65+
tx.recentBlockhash = (
66+
await provider.connection.getLatestBlockhash()
67+
).blockhash;
68+
tx.partialSign(payer, ...signers);
69+
const txHash = await provider.connection.sendRawTransaction(tx.serialize());
70+
console.log(`${label} transaction sent:`, txHash);
71+
72+
await provider.connection.confirmTransaction(txHash, "confirmed");
73+
const txStatus = await provider.connection.getTransaction(txHash, {
74+
maxSupportedTransactionVersion: 0,
75+
commitment: "confirmed",
76+
});
77+
if (txStatus?.meta?.err) {
78+
throw new Error(
79+
`Transaction failed: ${txHash}\nError: ${JSON.stringify(
80+
txStatus?.meta?.err
81+
)}\n\n${txStatus?.meta?.logMessages?.join("\n")}`
82+
);
83+
}
84+
console.log(`${label} transaction confirmed`);
85+
return txHash;
86+
}

scripts/launchInit.ts

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import * as token from "@solana/spl-token";
2+
import { ComputeBudgetProgram, Keypair, Transaction } from "@solana/web3.js";
3+
import * as anchor from "@coral-xyz/anchor";
4+
import {
5+
getLaunchAddr,
6+
getLaunchSignerAddr,
7+
LaunchpadClient,
8+
} from "@metadaoproject/futarchy/v0.4";
9+
import { BN } from "bn.js";
10+
import { homedir } from "os";
11+
import { join } from "path";
12+
import fs from "fs";
13+
import { input, select } from "@inquirer/prompts";
14+
15+
import dotenv from "dotenv";
16+
17+
dotenv.config();
18+
19+
const choices = [
20+
{ value: "devnet", name: "devnet" },
21+
{ value: "mainnet", name: "mainnet" },
22+
];
23+
24+
// Place mainnet as first choice if env says mainnet
25+
if (process.env.NETWORK === "mainnet") {
26+
choices.reverse();
27+
}
28+
29+
const network = await select({
30+
message: "Which network is this launch on?",
31+
choices: choices,
32+
});
33+
34+
const rpcUrl = await input({
35+
message: "Enter your RPC URL:",
36+
default: process.env.RPC_URL,
37+
});
38+
39+
const walletPath = await input({
40+
message: "Enter the path (relative to home directory) to your wallet file",
41+
default: join(homedir(), process.env.WALLET_PATH),
42+
});
43+
process.env.ANCHOR_WALLET = walletPath;
44+
const provider = anchor.AnchorProvider.local(rpcUrl, {
45+
commitment: "confirmed",
46+
});
47+
const payer = provider.wallet["payer"];
48+
49+
const launchAuthorityKeypairPath = await input({
50+
message:
51+
"Enter the path (relative to home directory) to your launch authority keypair file",
52+
default: join(homedir(), process.env.LAUNCH_AUTHORITY_KEYPAIR_PATH),
53+
});
54+
55+
const isDevnet = network === "devnet";
56+
57+
const launchpad: LaunchpadClient = LaunchpadClient.createClient({ provider });
58+
59+
const tokenName = await input({
60+
message: "Enter the token name",
61+
default: process.env.LAUNCH_TOKEN_NAME,
62+
});
63+
64+
const tokenSymbol = await input({
65+
message: "Enter the token symbol",
66+
default: process.env.LAUNCH_TOKEN_SYMBOL,
67+
});
68+
69+
const tokenUri = await input({
70+
message: "Enter the token URI",
71+
default: process.env.LAUNCH_TOKEN_URI,
72+
});
73+
74+
const minimumRaiseAmount = new BN(
75+
await input({
76+
message: "Enter the minimum raise amount",
77+
default: process.env.MINIMUM_RAISE_AMOUNT,
78+
})
79+
);
80+
81+
const secondsForLaunch = Number.parseInt(
82+
await input({
83+
message: "Enter the seconds for launch",
84+
default: process.env.SECONDS_FOR_LAUNCH,
85+
})
86+
);
87+
88+
async function main() {
89+
const launchAuthorityFile = fs.readFileSync(launchAuthorityKeypairPath);
90+
const launchAuthorityKeypair = Keypair.fromSecretKey(
91+
Buffer.from(JSON.parse(launchAuthorityFile.toString()) as Uint8Array)
92+
);
93+
94+
if (!launchAuthorityKeypair) {
95+
throw new Error("Could not read launch authority keypair.");
96+
}
97+
98+
if (!tokenName.length) {
99+
throw new Error("Token name is required.");
100+
}
101+
102+
if (!tokenSymbol.length) {
103+
throw new Error("Token symbol is required.");
104+
}
105+
106+
if (!tokenUri.length) {
107+
throw new Error("Token URI is required.");
108+
}
109+
110+
console.log(
111+
"Launch authority public key:",
112+
launchAuthorityKeypair.publicKey.toBase58()
113+
);
114+
115+
const mintKeypair = Keypair.generate();
116+
117+
const [launchAddr] = getLaunchAddr(
118+
launchpad.getProgramId(),
119+
mintKeypair.publicKey
120+
);
121+
const [launchSigner] = getLaunchSignerAddr(
122+
launchpad.getProgramId(),
123+
launchAddr
124+
);
125+
126+
console.log("Creating mint...");
127+
128+
const mint = await token.createMint(
129+
provider.connection,
130+
payer,
131+
launchSigner,
132+
null,
133+
6,
134+
mintKeypair,
135+
{
136+
// Let's be 100% sure the mint is created
137+
commitment: "finalized",
138+
}
139+
);
140+
141+
console.log("Mint created:", mint.toBase58());
142+
143+
console.log("Launching...");
144+
145+
const tx = await launchpad
146+
.initializeLaunchIx(
147+
tokenName,
148+
tokenSymbol,
149+
tokenUri,
150+
minimumRaiseAmount,
151+
secondsForLaunch,
152+
mint,
153+
launchAuthorityKeypair.publicKey,
154+
isDevnet,
155+
payer.publicKey
156+
)
157+
.preInstructions([
158+
ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }),
159+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 5 }),
160+
])
161+
.transaction();
162+
await sendAndConfirmTransaction(tx, "Initialize launch");
163+
164+
console.log("Launch initialized!");
165+
console.log("Launch address:", launchAddr.toBase58());
166+
}
167+
168+
// Make sure the promise rejection is handled
169+
main().catch((error) => {
170+
console.error("Fatal error:", error);
171+
process.exit(1);
172+
});
173+
174+
async function sendAndConfirmTransaction(
175+
tx: Transaction,
176+
label: string,
177+
signers: Keypair[] = []
178+
) {
179+
tx.feePayer = payer.publicKey;
180+
tx.recentBlockhash = (
181+
await provider.connection.getLatestBlockhash()
182+
).blockhash;
183+
tx.partialSign(payer, ...signers);
184+
const txHash = await provider.connection.sendRawTransaction(tx.serialize());
185+
console.log(`${label} transaction sent:`, txHash);
186+
187+
await provider.connection.confirmTransaction(txHash, "confirmed");
188+
const txStatus = await provider.connection.getTransaction(txHash, {
189+
maxSupportedTransactionVersion: 0,
190+
commitment: "confirmed",
191+
});
192+
if (txStatus?.meta?.err) {
193+
throw new Error(
194+
`Transaction failed: ${txHash}\nError: ${JSON.stringify(
195+
txStatus?.meta?.err
196+
)}\n\n${txStatus?.meta?.logMessages?.join("\n")}`
197+
);
198+
}
199+
console.log(`${label} transaction confirmed`);
200+
return txHash;
201+
}

0 commit comments

Comments
 (0)