@forgekit-labs/launchpad 0.1.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/LICENSE +21 -0
- package/README.md +66 -0
- package/package.json +40 -0
- package/src/fees.js +62 -0
- package/src/index.js +9 -0
- package/src/launch.js +283 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ForgeKit Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @forgekit-labs/launchpad
|
|
2
|
+
|
|
3
|
+
Create Raydium CPMM liquidity pools in one call. Validation, idempotency, and fee config included.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @forgekit-labs/launchpad
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { launch } from '@forgekit-labs/launchpad';
|
|
15
|
+
|
|
16
|
+
const { poolId, lpMint, signature } = await launch('my-pool')
|
|
17
|
+
.mint('J1rNqz1...')
|
|
18
|
+
.decimals(6)
|
|
19
|
+
.tokens(500_000_000)
|
|
20
|
+
.seed(0.65)
|
|
21
|
+
.feeTier(1)
|
|
22
|
+
.wallet(keypair.secretKey)
|
|
23
|
+
.rpc('https://api.mainnet-beta.solana.com')
|
|
24
|
+
.send();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Mainnet and devnet are auto-detected from the RPC URL. Program IDs and fee configs switch automatically.
|
|
28
|
+
|
|
29
|
+
`seed()` enforces the 0.65 SOL minimum: 0.15 SOL is consumed by Raydium as the pool creation fee, leaving 0.50 SOL as the minimum pool liquidity. Anything below this is rejected by Raydium on-chain with no useful error message, so it is caught synchronously before the network call.
|
|
30
|
+
|
|
31
|
+
## Idempotency
|
|
32
|
+
|
|
33
|
+
The Raydium pool PDA is deterministic. If a pool for this token already exists, `.send()` throws `ForgePoolExistsError` with the existing `poolId` attached:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
import { launch, ForgePoolExistsError } from '@forgekit-labs/launchpad';
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await launch('my-pool').mint(mint).decimals(6).tokens(500_000_000).seed(0.65).wallet(secretKey).send();
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (err instanceof ForgePoolExistsError) {
|
|
42
|
+
console.log('Pool already exists:', err.poolId);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Error Handling
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
import { launch, ForgeTxError, ForgeRpcError } from '@forgekit-labs/launchpad';
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
await launch('my-pool')./* ... */.send();
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (err instanceof ForgeTxError) {
|
|
56
|
+
console.error(err.message, err.cause);
|
|
57
|
+
}
|
|
58
|
+
if (err.retry) {
|
|
59
|
+
// safe to retry
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forgekit-labs/launchpad",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create Raydium CPMM liquidity pools in one call. Validation, idempotency, and fee config included.",
|
|
5
|
+
"author": "Res <support@crxcible.io> (https://github.com/solDEv0)",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./src/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.js"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18.0.0"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/solDEv0/forgekit.git",
|
|
17
|
+
"directory": "packages/launchpad"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/solDEv0/forgekit/tree/main/packages/launchpad#readme",
|
|
20
|
+
"bugs": "https://github.com/solDEv0/forgekit/issues",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"solana",
|
|
23
|
+
"raydium",
|
|
24
|
+
"cpmm",
|
|
25
|
+
"launchpad",
|
|
26
|
+
"liquidity",
|
|
27
|
+
"pool"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@forgekit-labs/errors": "^0.1.0",
|
|
35
|
+
"@solana/web3.js": "^1.98.0",
|
|
36
|
+
"@solana/spl-token": "^0.4.9",
|
|
37
|
+
"@raydium-io/raydium-sdk-v2": "0.2.44-alpha",
|
|
38
|
+
"bn.js": "^5.2.1"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/fees.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Raydium CPMM fee config accounts. Stable on-chain addresses.
|
|
2
|
+
// Hardcoded to avoid an API round-trip on every launch and remove
|
|
3
|
+
// an external dependency from the critical path.
|
|
4
|
+
//
|
|
5
|
+
// tradeFeeRate denominator: 1,000,000
|
|
6
|
+
// 10_000 = 1%
|
|
7
|
+
// 20_000 = 2%
|
|
8
|
+
//
|
|
9
|
+
// createPoolFee: 150_000_000 lamports = 0.15 SOL (paid to Raydium at creation)
|
|
10
|
+
//
|
|
11
|
+
// Devnet has no 2% config. Both tiers fall back to 1% (index 3).
|
|
12
|
+
|
|
13
|
+
export const CPMM_FEE_CONFIGS = {
|
|
14
|
+
mainnet: {
|
|
15
|
+
1: {
|
|
16
|
+
id: 'G95xxie3XbkCqtE39GgQ9Ggc7xBC8Uceve7HFDEFApkc',
|
|
17
|
+
index: 1,
|
|
18
|
+
protocolFeeRate: 120_000,
|
|
19
|
+
tradeFeeRate: 10_000,
|
|
20
|
+
fundFeeRate: 40_000,
|
|
21
|
+
createPoolFee: '150000000',
|
|
22
|
+
creatorFeeRate: 500,
|
|
23
|
+
},
|
|
24
|
+
2: {
|
|
25
|
+
id: '2fGXL8uhqxJ4tpgtosHZXT4zcQap6j62z3bMDxdkMvy5',
|
|
26
|
+
index: 2,
|
|
27
|
+
protocolFeeRate: 120_000,
|
|
28
|
+
tradeFeeRate: 20_000,
|
|
29
|
+
fundFeeRate: 40_000,
|
|
30
|
+
createPoolFee: '150000000',
|
|
31
|
+
creatorFeeRate: 500,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
devnet: {
|
|
35
|
+
1: {
|
|
36
|
+
id: 'EsTevfacYXpuho5VBuzBjDZi8dtWidGnXoSYAr8krTvz',
|
|
37
|
+
index: 3,
|
|
38
|
+
protocolFeeRate: 120_000,
|
|
39
|
+
tradeFeeRate: 10_000,
|
|
40
|
+
fundFeeRate: 40_000,
|
|
41
|
+
createPoolFee: '150000000',
|
|
42
|
+
creatorFeeRate: 2_500,
|
|
43
|
+
},
|
|
44
|
+
2: {
|
|
45
|
+
id: 'EsTevfacYXpuho5VBuzBjDZi8dtWidGnXoSYAr8krTvz',
|
|
46
|
+
index: 3,
|
|
47
|
+
protocolFeeRate: 120_000,
|
|
48
|
+
tradeFeeRate: 10_000,
|
|
49
|
+
fundFeeRate: 40_000,
|
|
50
|
+
createPoolFee: '150000000',
|
|
51
|
+
creatorFeeRate: 2_500,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const VALID_FEE_TIERS = new Set([1, 2]);
|
|
57
|
+
|
|
58
|
+
// 0.15 SOL creation fee + 0.50 SOL minimum pool liquidity
|
|
59
|
+
export const MIN_SEED_SOL = 0.65;
|
|
60
|
+
export const MIN_SEED_LAMPORTS = BigInt(650_000_000);
|
|
61
|
+
|
|
62
|
+
export const WSOL_MINT = 'So11111111111111111111111111111111111111112';
|
package/src/index.js
ADDED
package/src/launch.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
|
|
2
|
+
import {
|
|
3
|
+
Raydium,
|
|
4
|
+
TxVersion,
|
|
5
|
+
CREATE_CPMM_POOL_PROGRAM,
|
|
6
|
+
CREATE_CPMM_POOL_FEE_ACC,
|
|
7
|
+
DEVNET_PROGRAM_ID,
|
|
8
|
+
DEV_API_URLS,
|
|
9
|
+
API_URLS,
|
|
10
|
+
} from '@raydium-io/raydium-sdk-v2';
|
|
11
|
+
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
|
12
|
+
import BN from 'bn.js';
|
|
13
|
+
import {
|
|
14
|
+
CPMM_FEE_CONFIGS,
|
|
15
|
+
VALID_FEE_TIERS,
|
|
16
|
+
MIN_SEED_SOL,
|
|
17
|
+
WSOL_MINT,
|
|
18
|
+
} from './fees.js';
|
|
19
|
+
import {
|
|
20
|
+
ForgeValidationError,
|
|
21
|
+
ForgeTxError,
|
|
22
|
+
ForgeRpcError,
|
|
23
|
+
ForgePoolExistsError,
|
|
24
|
+
} from '@forgekit-labs/errors';
|
|
25
|
+
|
|
26
|
+
const LAMPORTS_PER_SOL = 1_000_000_000n;
|
|
27
|
+
|
|
28
|
+
class LaunchBuilder {
|
|
29
|
+
#name;
|
|
30
|
+
#mintAddress = null;
|
|
31
|
+
#mintDecimals = null;
|
|
32
|
+
#tokenAmount = null;
|
|
33
|
+
#seedSol = null;
|
|
34
|
+
#feeTier = 1;
|
|
35
|
+
#secretKey = null;
|
|
36
|
+
#rpcUrl = 'https://api.mainnet-beta.solana.com';
|
|
37
|
+
|
|
38
|
+
constructor(name) {
|
|
39
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
40
|
+
throw new ForgeValidationError(
|
|
41
|
+
'launch() requires a name string.',
|
|
42
|
+
'Pass a unique identifier, e.g. launch("my-pool").',
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
this.#name = name;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Token mint address to pair with SOL
|
|
49
|
+
mint(address) {
|
|
50
|
+
if (typeof address !== 'string') {
|
|
51
|
+
throw new ForgeValidationError(
|
|
52
|
+
'mint() requires a string address.',
|
|
53
|
+
'Pass the token mint address as a base-58 Solana public key.',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
try { new PublicKey(address); } catch {
|
|
57
|
+
throw new ForgeValidationError(
|
|
58
|
+
`mint() received an invalid address: "${address}"`,
|
|
59
|
+
'The mint address must be a valid base-58 Solana public key.',
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
this.#mintAddress = address;
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Token decimals, required to calculate raw amounts correctly
|
|
67
|
+
decimals(d) {
|
|
68
|
+
if (typeof d !== 'number' || d < 0 || d > 9 || !Number.isInteger(d)) {
|
|
69
|
+
throw new ForgeValidationError(
|
|
70
|
+
'decimals() requires an integer between 0 and 9.',
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
this.#mintDecimals = d;
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Number of tokens (in human units) to seed into the pool
|
|
78
|
+
tokens(amount) {
|
|
79
|
+
if (typeof amount !== 'number' || amount <= 0 || !Number.isFinite(amount)) {
|
|
80
|
+
throw new ForgeValidationError(
|
|
81
|
+
'tokens() requires a positive number.',
|
|
82
|
+
'e.g. .tokens(500_000_000) to seed 500M tokens into the pool.',
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
this.#tokenAmount = amount;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// SOL to seed the pool. Minimum 0.65 (0.15 creation fee + 0.50 liquidity).
|
|
90
|
+
seed(sol) {
|
|
91
|
+
if (typeof sol !== 'number' || !Number.isFinite(sol)) {
|
|
92
|
+
throw new ForgeValidationError('seed() requires a number.');
|
|
93
|
+
}
|
|
94
|
+
if (sol < MIN_SEED_SOL) {
|
|
95
|
+
throw new ForgeValidationError(
|
|
96
|
+
`seed() received ${sol} SOL. Below the minimum of ${MIN_SEED_SOL} SOL.`,
|
|
97
|
+
`Raydium CPMM requires at least ${MIN_SEED_SOL} SOL: 0.15 SOL is consumed as ` +
|
|
98
|
+
`the pool creation fee, leaving 0.50 SOL as the minimum pool liquidity. ` +
|
|
99
|
+
`Anything below this will be rejected on-chain.`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
this.#seedSol = sol;
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Swap fee tier: 1 (1%) or 2 (2%). Devnet only supports 1%.
|
|
107
|
+
feeTier(pct) {
|
|
108
|
+
if (!VALID_FEE_TIERS.has(pct)) {
|
|
109
|
+
throw new ForgeValidationError(
|
|
110
|
+
`feeTier() received ${pct}. Only 1 and 2 are valid Raydium CPMM fee tiers.`,
|
|
111
|
+
'Use .feeTier(1) for 1% swap fee or .feeTier(2) for 2% swap fee.',
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
this.#feeTier = pct;
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
wallet(secretKey) {
|
|
119
|
+
if (!(secretKey instanceof Uint8Array)) {
|
|
120
|
+
throw new ForgeValidationError(
|
|
121
|
+
'wallet() expects a Uint8Array (your keypair.secretKey).',
|
|
122
|
+
'e.g. .wallet(keypair.secretKey)',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
this.#secretKey = secretKey;
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
rpc(url) {
|
|
130
|
+
if (typeof url !== 'string' || (!url.startsWith('http://') && !url.startsWith('https://'))) {
|
|
131
|
+
throw new ForgeValidationError(
|
|
132
|
+
'rpc() requires a valid HTTP or HTTPS URL.',
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
this.#rpcUrl = url;
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async send() {
|
|
140
|
+
if (!this.#mintAddress) {
|
|
141
|
+
throw new ForgeValidationError(
|
|
142
|
+
'No mint address provided.',
|
|
143
|
+
'Call .mint("your-token-mint-address") before .send().',
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (this.#mintDecimals === null) {
|
|
147
|
+
throw new ForgeValidationError(
|
|
148
|
+
'No decimals provided.',
|
|
149
|
+
'Call .decimals(6) before .send().',
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (this.#tokenAmount === null) {
|
|
153
|
+
throw new ForgeValidationError(
|
|
154
|
+
'No token amount provided.',
|
|
155
|
+
'Call .tokens(500_000_000) before .send().',
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (this.#seedSol === null) {
|
|
159
|
+
throw new ForgeValidationError(
|
|
160
|
+
'No SOL seed amount provided.',
|
|
161
|
+
`Call .seed(${MIN_SEED_SOL}) before .send().`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (!this.#secretKey) {
|
|
165
|
+
throw new ForgeValidationError(
|
|
166
|
+
'No wallet provided.',
|
|
167
|
+
'Call .wallet(keypair.secretKey) before .send().',
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const isDevnet = this.#rpcUrl.toLowerCase().includes('devnet');
|
|
172
|
+
const network = isDevnet ? 'devnet' : 'mainnet';
|
|
173
|
+
const feeConfig = CPMM_FEE_CONFIGS[network][this.#feeTier];
|
|
174
|
+
|
|
175
|
+
const owner = Keypair.fromSecretKey(Buffer.from(this.#secretKey));
|
|
176
|
+
const connection = new Connection(this.#rpcUrl, 'confirmed');
|
|
177
|
+
|
|
178
|
+
// Load Raydium SDK
|
|
179
|
+
let raydium;
|
|
180
|
+
try {
|
|
181
|
+
raydium = await Raydium.load({
|
|
182
|
+
connection,
|
|
183
|
+
owner,
|
|
184
|
+
cluster: isDevnet ? 'devnet' : 'mainnet',
|
|
185
|
+
disableFeatureCheck: true,
|
|
186
|
+
disableLoadToken: true,
|
|
187
|
+
blockhashCommitment: 'confirmed',
|
|
188
|
+
urlConfigs: isDevnet ? DEV_API_URLS : API_URLS,
|
|
189
|
+
});
|
|
190
|
+
await raydium.account.fetchWalletTokenAccounts();
|
|
191
|
+
} catch (err) {
|
|
192
|
+
throw new ForgeRpcError(
|
|
193
|
+
`Failed to initialise Raydium SDK: ${err?.message ?? String(err)}`,
|
|
194
|
+
err,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const programIds = isDevnet
|
|
199
|
+
? {
|
|
200
|
+
programId: new PublicKey(DEVNET_PROGRAM_ID.CREATE_CPMM_POOL_PROGRAM),
|
|
201
|
+
poolFeeAccount: new PublicKey(DEVNET_PROGRAM_ID.CREATE_CPMM_POOL_FEE_ACC),
|
|
202
|
+
}
|
|
203
|
+
: {
|
|
204
|
+
programId: CREATE_CPMM_POOL_PROGRAM,
|
|
205
|
+
poolFeeAccount: CREATE_CPMM_POOL_FEE_ACC,
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const rawTokenAmount = BigInt(Math.round(this.#tokenAmount * (10 ** this.#mintDecimals)));
|
|
209
|
+
const rawSolLamports = BigInt(Math.round(this.#seedSol * Number(LAMPORTS_PER_SOL)));
|
|
210
|
+
|
|
211
|
+
const mintA = {
|
|
212
|
+
address: this.#mintAddress,
|
|
213
|
+
decimals: this.#mintDecimals,
|
|
214
|
+
programId: TOKEN_PROGRAM_ID.toBase58(),
|
|
215
|
+
};
|
|
216
|
+
const mintB = {
|
|
217
|
+
address: WSOL_MINT,
|
|
218
|
+
decimals: 9,
|
|
219
|
+
programId: TOKEN_PROGRAM_ID.toBase58(),
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Build the pool creation transaction
|
|
223
|
+
let txData;
|
|
224
|
+
try {
|
|
225
|
+
txData = await raydium.cpmm.createPool({
|
|
226
|
+
programId: programIds.programId,
|
|
227
|
+
poolFeeAccount: programIds.poolFeeAccount,
|
|
228
|
+
mintA,
|
|
229
|
+
mintB,
|
|
230
|
+
mintAAmount: new BN(rawTokenAmount.toString()),
|
|
231
|
+
mintBAmount: new BN(rawSolLamports.toString()),
|
|
232
|
+
startTime: new BN(0),
|
|
233
|
+
feeConfig,
|
|
234
|
+
associatedOnly: false,
|
|
235
|
+
checkCreateATAOwner: false,
|
|
236
|
+
ownerInfo: { useSOLBalance: true },
|
|
237
|
+
txVersion: TxVersion.V0,
|
|
238
|
+
});
|
|
239
|
+
} catch (err) {
|
|
240
|
+
throw new ForgeTxError(
|
|
241
|
+
`Failed to build pool transaction: ${err?.message ?? String(err)}`,
|
|
242
|
+
err,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const { execute, extInfo } = txData;
|
|
247
|
+
const poolId = extInfo.address.poolId.toBase58();
|
|
248
|
+
const lpMint = extInfo.address.lpMint.toBase58();
|
|
249
|
+
|
|
250
|
+
// Idempotency. Pool PDA is deterministic; if it exists we surface it clearly.
|
|
251
|
+
let existingPool;
|
|
252
|
+
try {
|
|
253
|
+
existingPool = await connection.getAccountInfo(extInfo.address.poolId);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
throw new ForgeRpcError(
|
|
256
|
+
`Failed to check for existing pool: ${err?.message ?? String(err)}`,
|
|
257
|
+
err,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (existingPool !== null) {
|
|
262
|
+
throw new ForgePoolExistsError(poolId);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Submit
|
|
266
|
+
let txId;
|
|
267
|
+
try {
|
|
268
|
+
const result = await execute({ sendAndConfirm: true });
|
|
269
|
+
txId = result.txId;
|
|
270
|
+
} catch (err) {
|
|
271
|
+
throw new ForgeTxError(
|
|
272
|
+
`Pool creation transaction failed: ${err?.message ?? String(err)}`,
|
|
273
|
+
err,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return { poolId, lpMint, signature: txId };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function launch(name) {
|
|
282
|
+
return new LaunchBuilder(name);
|
|
283
|
+
}
|