nara-sdk 1.0.44 → 1.0.48
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/index.ts +3 -3
- package/package.json +1 -1
- package/src/idls/nara_quest.json +86 -25
- package/src/idls/nara_quest.ts +86 -25
- package/src/quest.ts +85 -16
- package/src/tx.ts +78 -12
package/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ export {
|
|
|
18
18
|
} from "./src/constants";
|
|
19
19
|
|
|
20
20
|
// Export transaction helper
|
|
21
|
-
export { sendTx, setAltAddress, getAltAddress } from "./src/tx";
|
|
21
|
+
export { sendTx, setAltAddress, getAltAddress, getRecentPriorityFee } from "./src/tx";
|
|
22
22
|
|
|
23
23
|
// Export quest functions and types
|
|
24
24
|
export {
|
|
@@ -34,8 +34,8 @@ export {
|
|
|
34
34
|
unstake,
|
|
35
35
|
getStakeInfo,
|
|
36
36
|
initializeQuest,
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
setRewardConfig,
|
|
38
|
+
setStakeConfig,
|
|
39
39
|
transferQuestAuthority,
|
|
40
40
|
getQuestConfig,
|
|
41
41
|
type QuestInfo,
|
package/package.json
CHANGED
package/src/idls/nara_quest.json
CHANGED
|
@@ -204,16 +204,16 @@
|
|
|
204
204
|
"args": []
|
|
205
205
|
},
|
|
206
206
|
{
|
|
207
|
-
"name": "
|
|
207
|
+
"name": "set_reward_config",
|
|
208
208
|
"discriminator": [
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
209
|
+
163,
|
|
210
|
+
34,
|
|
211
|
+
211,
|
|
212
|
+
14,
|
|
213
|
+
25,
|
|
214
|
+
118,
|
|
215
|
+
181,
|
|
216
|
+
233
|
|
217
217
|
],
|
|
218
218
|
"accounts": [
|
|
219
219
|
{
|
|
@@ -247,6 +247,10 @@
|
|
|
247
247
|
}
|
|
248
248
|
],
|
|
249
249
|
"args": [
|
|
250
|
+
{
|
|
251
|
+
"name": "min_reward_count",
|
|
252
|
+
"type": "u32"
|
|
253
|
+
},
|
|
250
254
|
{
|
|
251
255
|
"name": "max_reward_count",
|
|
252
256
|
"type": "u32"
|
|
@@ -254,16 +258,16 @@
|
|
|
254
258
|
]
|
|
255
259
|
},
|
|
256
260
|
{
|
|
257
|
-
"name": "
|
|
261
|
+
"name": "set_stake_config",
|
|
258
262
|
"discriminator": [
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
263
|
+
84,
|
|
264
|
+
37,
|
|
265
|
+
76,
|
|
266
|
+
39,
|
|
267
|
+
236,
|
|
268
|
+
111,
|
|
269
|
+
214,
|
|
270
|
+
191
|
|
267
271
|
],
|
|
268
272
|
"accounts": [
|
|
269
273
|
{
|
|
@@ -298,8 +302,16 @@
|
|
|
298
302
|
],
|
|
299
303
|
"args": [
|
|
300
304
|
{
|
|
301
|
-
"name": "
|
|
302
|
-
"type": "
|
|
305
|
+
"name": "bps_high",
|
|
306
|
+
"type": "u64"
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
"name": "bps_low",
|
|
310
|
+
"type": "u64"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"name": "decay_ms",
|
|
314
|
+
"type": "i64"
|
|
303
315
|
}
|
|
304
316
|
]
|
|
305
317
|
},
|
|
@@ -498,6 +510,30 @@
|
|
|
498
510
|
48
|
|
499
511
|
],
|
|
500
512
|
"accounts": [
|
|
513
|
+
{
|
|
514
|
+
"name": "game_config",
|
|
515
|
+
"pda": {
|
|
516
|
+
"seeds": [
|
|
517
|
+
{
|
|
518
|
+
"kind": "const",
|
|
519
|
+
"value": [
|
|
520
|
+
113,
|
|
521
|
+
117,
|
|
522
|
+
101,
|
|
523
|
+
115,
|
|
524
|
+
116,
|
|
525
|
+
95,
|
|
526
|
+
99,
|
|
527
|
+
111,
|
|
528
|
+
110,
|
|
529
|
+
102,
|
|
530
|
+
105,
|
|
531
|
+
103
|
|
532
|
+
]
|
|
533
|
+
}
|
|
534
|
+
]
|
|
535
|
+
}
|
|
536
|
+
},
|
|
501
537
|
{
|
|
502
538
|
"name": "pool",
|
|
503
539
|
"writable": true,
|
|
@@ -1192,12 +1228,12 @@
|
|
|
1192
1228
|
{
|
|
1193
1229
|
"code": 6008,
|
|
1194
1230
|
"name": "InvalidMinRewardCount",
|
|
1195
|
-
"msg": "
|
|
1231
|
+
"msg": "Invalid reward config: need 0 < min <= max"
|
|
1196
1232
|
},
|
|
1197
1233
|
{
|
|
1198
1234
|
"code": 6009,
|
|
1199
|
-
"name": "
|
|
1200
|
-
"msg": "
|
|
1235
|
+
"name": "InvalidStakeConfig",
|
|
1236
|
+
"msg": "Stake config values must be > 0"
|
|
1201
1237
|
},
|
|
1202
1238
|
{
|
|
1203
1239
|
"code": 6010,
|
|
@@ -1208,6 +1244,11 @@
|
|
|
1208
1244
|
"code": 6011,
|
|
1209
1245
|
"name": "InsufficientStakeBalance",
|
|
1210
1246
|
"msg": "Unstake amount exceeds staked balance"
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
"code": 6012,
|
|
1250
|
+
"name": "InsufficientStake",
|
|
1251
|
+
"msg": "Stake does not meet dynamic requirement"
|
|
1211
1252
|
}
|
|
1212
1253
|
],
|
|
1213
1254
|
"types": [
|
|
@@ -1260,6 +1301,18 @@
|
|
|
1260
1301
|
"name": "max_reward_count",
|
|
1261
1302
|
"type": "u32"
|
|
1262
1303
|
},
|
|
1304
|
+
{
|
|
1305
|
+
"name": "stake_bps_high",
|
|
1306
|
+
"type": "u64"
|
|
1307
|
+
},
|
|
1308
|
+
{
|
|
1309
|
+
"name": "stake_bps_low",
|
|
1310
|
+
"type": "u64"
|
|
1311
|
+
},
|
|
1312
|
+
{
|
|
1313
|
+
"name": "decay_ms",
|
|
1314
|
+
"type": "i64"
|
|
1315
|
+
},
|
|
1263
1316
|
{
|
|
1264
1317
|
"name": "_padding",
|
|
1265
1318
|
"type": {
|
|
@@ -1319,11 +1372,19 @@
|
|
|
1319
1372
|
"type": "u32"
|
|
1320
1373
|
},
|
|
1321
1374
|
{
|
|
1322
|
-
"name": "
|
|
1375
|
+
"name": "created_at",
|
|
1376
|
+
"type": "i64"
|
|
1377
|
+
},
|
|
1378
|
+
{
|
|
1379
|
+
"name": "stake_high",
|
|
1380
|
+
"type": "u64"
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
"name": "stake_low",
|
|
1323
1384
|
"type": "u64"
|
|
1324
1385
|
},
|
|
1325
1386
|
{
|
|
1326
|
-
"name": "
|
|
1387
|
+
"name": "avg_participant_stake",
|
|
1327
1388
|
"type": "u64"
|
|
1328
1389
|
},
|
|
1329
1390
|
{
|
package/src/idls/nara_quest.ts
CHANGED
|
@@ -210,16 +210,16 @@ export type NaraQuest = {
|
|
|
210
210
|
"args": []
|
|
211
211
|
},
|
|
212
212
|
{
|
|
213
|
-
"name": "
|
|
213
|
+
"name": "setRewardConfig",
|
|
214
214
|
"discriminator": [
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
215
|
+
163,
|
|
216
|
+
34,
|
|
217
|
+
211,
|
|
218
|
+
14,
|
|
219
|
+
25,
|
|
220
|
+
118,
|
|
221
|
+
181,
|
|
222
|
+
233
|
|
223
223
|
],
|
|
224
224
|
"accounts": [
|
|
225
225
|
{
|
|
@@ -253,6 +253,10 @@ export type NaraQuest = {
|
|
|
253
253
|
}
|
|
254
254
|
],
|
|
255
255
|
"args": [
|
|
256
|
+
{
|
|
257
|
+
"name": "minRewardCount",
|
|
258
|
+
"type": "u32"
|
|
259
|
+
},
|
|
256
260
|
{
|
|
257
261
|
"name": "maxRewardCount",
|
|
258
262
|
"type": "u32"
|
|
@@ -260,16 +264,16 @@ export type NaraQuest = {
|
|
|
260
264
|
]
|
|
261
265
|
},
|
|
262
266
|
{
|
|
263
|
-
"name": "
|
|
267
|
+
"name": "setStakeConfig",
|
|
264
268
|
"discriminator": [
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
84,
|
|
270
|
+
37,
|
|
271
|
+
76,
|
|
272
|
+
39,
|
|
273
|
+
236,
|
|
274
|
+
111,
|
|
275
|
+
214,
|
|
276
|
+
191
|
|
273
277
|
],
|
|
274
278
|
"accounts": [
|
|
275
279
|
{
|
|
@@ -304,8 +308,16 @@ export type NaraQuest = {
|
|
|
304
308
|
],
|
|
305
309
|
"args": [
|
|
306
310
|
{
|
|
307
|
-
"name": "
|
|
308
|
-
"type": "
|
|
311
|
+
"name": "bpsHigh",
|
|
312
|
+
"type": "u64"
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"name": "bpsLow",
|
|
316
|
+
"type": "u64"
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"name": "decayMs",
|
|
320
|
+
"type": "i64"
|
|
309
321
|
}
|
|
310
322
|
]
|
|
311
323
|
},
|
|
@@ -504,6 +516,30 @@ export type NaraQuest = {
|
|
|
504
516
|
48
|
|
505
517
|
],
|
|
506
518
|
"accounts": [
|
|
519
|
+
{
|
|
520
|
+
"name": "gameConfig",
|
|
521
|
+
"pda": {
|
|
522
|
+
"seeds": [
|
|
523
|
+
{
|
|
524
|
+
"kind": "const",
|
|
525
|
+
"value": [
|
|
526
|
+
113,
|
|
527
|
+
117,
|
|
528
|
+
101,
|
|
529
|
+
115,
|
|
530
|
+
116,
|
|
531
|
+
95,
|
|
532
|
+
99,
|
|
533
|
+
111,
|
|
534
|
+
110,
|
|
535
|
+
102,
|
|
536
|
+
105,
|
|
537
|
+
103
|
|
538
|
+
]
|
|
539
|
+
}
|
|
540
|
+
]
|
|
541
|
+
}
|
|
542
|
+
},
|
|
507
543
|
{
|
|
508
544
|
"name": "pool",
|
|
509
545
|
"writable": true,
|
|
@@ -1198,12 +1234,12 @@ export type NaraQuest = {
|
|
|
1198
1234
|
{
|
|
1199
1235
|
"code": 6008,
|
|
1200
1236
|
"name": "invalidMinRewardCount",
|
|
1201
|
-
"msg": "
|
|
1237
|
+
"msg": "Invalid reward config: need 0 < min <= max"
|
|
1202
1238
|
},
|
|
1203
1239
|
{
|
|
1204
1240
|
"code": 6009,
|
|
1205
|
-
"name": "
|
|
1206
|
-
"msg": "
|
|
1241
|
+
"name": "invalidStakeConfig",
|
|
1242
|
+
"msg": "Stake config values must be > 0"
|
|
1207
1243
|
},
|
|
1208
1244
|
{
|
|
1209
1245
|
"code": 6010,
|
|
@@ -1214,6 +1250,11 @@ export type NaraQuest = {
|
|
|
1214
1250
|
"code": 6011,
|
|
1215
1251
|
"name": "insufficientStakeBalance",
|
|
1216
1252
|
"msg": "Unstake amount exceeds staked balance"
|
|
1253
|
+
},
|
|
1254
|
+
{
|
|
1255
|
+
"code": 6012,
|
|
1256
|
+
"name": "insufficientStake",
|
|
1257
|
+
"msg": "Stake does not meet dynamic requirement"
|
|
1217
1258
|
}
|
|
1218
1259
|
],
|
|
1219
1260
|
"types": [
|
|
@@ -1266,6 +1307,18 @@ export type NaraQuest = {
|
|
|
1266
1307
|
"name": "maxRewardCount",
|
|
1267
1308
|
"type": "u32"
|
|
1268
1309
|
},
|
|
1310
|
+
{
|
|
1311
|
+
"name": "stakeBpsHigh",
|
|
1312
|
+
"type": "u64"
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
"name": "stakeBpsLow",
|
|
1316
|
+
"type": "u64"
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
"name": "decayMs",
|
|
1320
|
+
"type": "i64"
|
|
1321
|
+
},
|
|
1269
1322
|
{
|
|
1270
1323
|
"name": "padding",
|
|
1271
1324
|
"type": {
|
|
@@ -1325,11 +1378,19 @@ export type NaraQuest = {
|
|
|
1325
1378
|
"type": "u32"
|
|
1326
1379
|
},
|
|
1327
1380
|
{
|
|
1328
|
-
"name": "
|
|
1381
|
+
"name": "createdAt",
|
|
1382
|
+
"type": "i64"
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
"name": "stakeHigh",
|
|
1386
|
+
"type": "u64"
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
"name": "stakeLow",
|
|
1329
1390
|
"type": "u64"
|
|
1330
1391
|
},
|
|
1331
1392
|
{
|
|
1332
|
-
"name": "
|
|
1393
|
+
"name": "avgParticipantStake",
|
|
1333
1394
|
"type": "u64"
|
|
1334
1395
|
},
|
|
1335
1396
|
{
|
package/src/quest.ts
CHANGED
|
@@ -50,10 +50,16 @@ export interface QuestInfo {
|
|
|
50
50
|
deadline: number;
|
|
51
51
|
timeRemaining: number;
|
|
52
52
|
expired: boolean;
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
|
|
53
|
+
/** High stake requirement for the current round (in NARA, decays over time) */
|
|
54
|
+
stakeHigh: number;
|
|
55
|
+
/** Low stake requirement for the current round (in NARA, floor after decay) */
|
|
56
|
+
stakeLow: number;
|
|
57
|
+
/** Running average participant stake for the current round (in NARA) */
|
|
58
|
+
avgParticipantStake: number;
|
|
59
|
+
/** Unix timestamp when the current question was created */
|
|
60
|
+
createdAt: number;
|
|
61
|
+
/** Current effective stake requirement after parabolic decay (in NARA) */
|
|
62
|
+
effectiveStakeRequirement: number;
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
export interface StakeInfo {
|
|
@@ -242,6 +248,26 @@ function getStakeTokenAccount(stakeRecordPda: PublicKey): PublicKey {
|
|
|
242
248
|
return getAssociatedTokenAddressSync(WSOL_MINT, stakeRecordPda, true);
|
|
243
249
|
}
|
|
244
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Compute the effective stake requirement using the parabolic decay formula.
|
|
253
|
+
* effective = stakeHigh - (stakeHigh - stakeLow) * (elapsed / decay)^2
|
|
254
|
+
* All amounts in NARA (not lamports). Times in milliseconds.
|
|
255
|
+
*/
|
|
256
|
+
function computeEffectiveStake(
|
|
257
|
+
stakeHigh: number,
|
|
258
|
+
stakeLow: number,
|
|
259
|
+
createdAt: number,
|
|
260
|
+
decayMs: number,
|
|
261
|
+
nowMs: number
|
|
262
|
+
): number {
|
|
263
|
+
if (decayMs <= 0) return stakeLow;
|
|
264
|
+
const elapsedMs = nowMs - createdAt;
|
|
265
|
+
if (elapsedMs >= decayMs) return stakeLow;
|
|
266
|
+
const range = stakeHigh - stakeLow;
|
|
267
|
+
const ratio = elapsedMs / decayMs;
|
|
268
|
+
return stakeHigh - range * ratio * ratio;
|
|
269
|
+
}
|
|
270
|
+
|
|
245
271
|
// ─── SDK functions ───────────────────────────────────────────────
|
|
246
272
|
|
|
247
273
|
/**
|
|
@@ -263,6 +289,26 @@ export async function getQuestInfo(
|
|
|
263
289
|
|
|
264
290
|
const active = pool.question.length > 0 && secsLeft > 0;
|
|
265
291
|
|
|
292
|
+
const stakeHigh = Number(pool.stakeHigh.toString()) / LAMPORTS_PER_SOL;
|
|
293
|
+
const stakeLow = Number(pool.stakeLow.toString()) / LAMPORTS_PER_SOL;
|
|
294
|
+
const createdAt = pool.createdAt.toNumber();
|
|
295
|
+
|
|
296
|
+
// Fetch decayMs from GameConfig for effective calculation
|
|
297
|
+
const programId = new PublicKey(options?.programId ?? DEFAULT_QUEST_PROGRAM_ID);
|
|
298
|
+
const [configPda] = PublicKey.findProgramAddressSync(
|
|
299
|
+
[new TextEncoder().encode("quest_config")],
|
|
300
|
+
programId
|
|
301
|
+
);
|
|
302
|
+
const config = await program.account.gameConfig.fetch(configPda);
|
|
303
|
+
const decayMs = Number(config.decayMs.toString());
|
|
304
|
+
|
|
305
|
+
// createdAt is unix timestamp (seconds), convert to ms for decay calculation
|
|
306
|
+
const nowMs = Date.now();
|
|
307
|
+
const createdAtMs = createdAt * 1000;
|
|
308
|
+
const effectiveStakeRequirement = computeEffectiveStake(
|
|
309
|
+
stakeHigh, stakeLow, createdAtMs, decayMs, nowMs
|
|
310
|
+
);
|
|
311
|
+
|
|
266
312
|
return {
|
|
267
313
|
active,
|
|
268
314
|
round: pool.round.toString(),
|
|
@@ -277,8 +323,11 @@ export async function getQuestInfo(
|
|
|
277
323
|
deadline,
|
|
278
324
|
timeRemaining: secsLeft,
|
|
279
325
|
expired: secsLeft <= 0,
|
|
280
|
-
|
|
281
|
-
|
|
326
|
+
stakeHigh,
|
|
327
|
+
stakeLow,
|
|
328
|
+
avgParticipantStake: Number(pool.avgParticipantStake.toString()) / LAMPORTS_PER_SOL,
|
|
329
|
+
createdAt,
|
|
330
|
+
effectiveStakeRequirement,
|
|
282
331
|
};
|
|
283
332
|
}
|
|
284
333
|
|
|
@@ -372,7 +421,7 @@ export async function submitAnswer(
|
|
|
372
421
|
if (options.stake === "auto") {
|
|
373
422
|
const quest = await getQuestInfo(connection, wallet, options);
|
|
374
423
|
const stakeInfo = await getStakeInfo(connection, wallet.publicKey, options);
|
|
375
|
-
const required = quest.
|
|
424
|
+
const required = quest.effectiveStakeRequirement;
|
|
376
425
|
const current = stakeInfo?.amount ?? 0;
|
|
377
426
|
const deficit = required - current;
|
|
378
427
|
if (deficit > 0) {
|
|
@@ -423,7 +472,11 @@ export async function submitAnswer(
|
|
|
423
472
|
ixs.push(logIx);
|
|
424
473
|
}
|
|
425
474
|
|
|
426
|
-
const signature = await sendTx(connection, wallet, ixs, [], {
|
|
475
|
+
const signature = await sendTx(connection, wallet, ixs, [], {
|
|
476
|
+
skipPreflight: true,
|
|
477
|
+
computeUnitLimit: 500_000,
|
|
478
|
+
computeUnitPrice: "auto",
|
|
479
|
+
});
|
|
427
480
|
return { signature };
|
|
428
481
|
}
|
|
429
482
|
|
|
@@ -645,34 +698,40 @@ export async function initializeQuest(
|
|
|
645
698
|
}
|
|
646
699
|
|
|
647
700
|
/**
|
|
648
|
-
* Set the
|
|
701
|
+
* Set the reward config (authority only).
|
|
649
702
|
*/
|
|
650
|
-
export async function
|
|
703
|
+
export async function setRewardConfig(
|
|
651
704
|
connection: Connection,
|
|
652
705
|
wallet: Keypair,
|
|
706
|
+
minRewardCount: number,
|
|
653
707
|
maxRewardCount: number,
|
|
654
708
|
options?: QuestOptions
|
|
655
709
|
): Promise<string> {
|
|
656
710
|
const program = createProgram(connection, wallet, options?.programId);
|
|
657
711
|
const ix = await program.methods
|
|
658
|
-
.
|
|
712
|
+
.setRewardConfig(minRewardCount, maxRewardCount)
|
|
659
713
|
.accounts({ authority: wallet.publicKey } as any)
|
|
660
714
|
.instruction();
|
|
661
715
|
return sendTx(connection, wallet, [ix]);
|
|
662
716
|
}
|
|
663
717
|
|
|
664
718
|
/**
|
|
665
|
-
* Set the
|
|
719
|
+
* Set the stake config (authority only).
|
|
720
|
+
* @param bpsHigh - Upper bound multiplier in basis points (e.g. 100000 = 10x average)
|
|
721
|
+
* @param bpsLow - Lower bound multiplier in basis points (e.g. 1000 = 0.1x average)
|
|
722
|
+
* @param decayMs - Time window in milliseconds for parabolic decay from high to low
|
|
666
723
|
*/
|
|
667
|
-
export async function
|
|
724
|
+
export async function setStakeConfig(
|
|
668
725
|
connection: Connection,
|
|
669
726
|
wallet: Keypair,
|
|
670
|
-
|
|
727
|
+
bpsHigh: number,
|
|
728
|
+
bpsLow: number,
|
|
729
|
+
decayMs: number,
|
|
671
730
|
options?: QuestOptions
|
|
672
731
|
): Promise<string> {
|
|
673
732
|
const program = createProgram(connection, wallet, options?.programId);
|
|
674
733
|
const ix = await program.methods
|
|
675
|
-
.
|
|
734
|
+
.setStakeConfig(new BN(bpsHigh), new BN(bpsLow), new BN(decayMs))
|
|
676
735
|
.accounts({ authority: wallet.publicKey } as any)
|
|
677
736
|
.instruction();
|
|
678
737
|
return sendTx(connection, wallet, [ix]);
|
|
@@ -701,7 +760,14 @@ export async function transferQuestAuthority(
|
|
|
701
760
|
export async function getQuestConfig(
|
|
702
761
|
connection: Connection,
|
|
703
762
|
options?: QuestOptions
|
|
704
|
-
): Promise<{
|
|
763
|
+
): Promise<{
|
|
764
|
+
authority: PublicKey;
|
|
765
|
+
minRewardCount: number;
|
|
766
|
+
maxRewardCount: number;
|
|
767
|
+
stakeBpsHigh: number;
|
|
768
|
+
stakeBpsLow: number;
|
|
769
|
+
decayMs: number;
|
|
770
|
+
}> {
|
|
705
771
|
const kp = Keypair.generate();
|
|
706
772
|
const program = createProgram(connection, kp, options?.programId);
|
|
707
773
|
const programId = new PublicKey(options?.programId ?? DEFAULT_QUEST_PROGRAM_ID);
|
|
@@ -714,5 +780,8 @@ export async function getQuestConfig(
|
|
|
714
780
|
authority: config.authority,
|
|
715
781
|
minRewardCount: config.minRewardCount,
|
|
716
782
|
maxRewardCount: config.maxRewardCount,
|
|
783
|
+
stakeBpsHigh: Number(config.stakeBpsHigh.toString()),
|
|
784
|
+
stakeBpsLow: Number(config.stakeBpsLow.toString()),
|
|
785
|
+
decayMs: Number(config.decayMs.toString()),
|
|
717
786
|
};
|
|
718
787
|
}
|
package/src/tx.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
AddressLookupTableAccount,
|
|
10
|
+
ComputeBudgetProgram,
|
|
10
11
|
Connection,
|
|
11
12
|
Keypair,
|
|
12
13
|
PublicKey,
|
|
@@ -57,11 +58,30 @@ async function loadAlt(
|
|
|
57
58
|
return _cachedAlt;
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Get the recent average priority fee (in micro-lamports per CU).
|
|
63
|
+
* Samples the last few slots via getRecentPrioritizationFees.
|
|
64
|
+
*/
|
|
65
|
+
export async function getRecentPriorityFee(
|
|
66
|
+
connection: Connection,
|
|
67
|
+
): Promise<number> {
|
|
68
|
+
const fees = await connection.getRecentPrioritizationFees();
|
|
69
|
+
if (!fees.length) return 0;
|
|
70
|
+
const nonZero = fees.filter((f) => f.prioritizationFee > 0);
|
|
71
|
+
if (!nonZero.length) return 0;
|
|
72
|
+
const avg = nonZero.reduce((s, f) => s + f.prioritizationFee, 0) / nonZero.length;
|
|
73
|
+
return Math.ceil(avg);
|
|
74
|
+
}
|
|
75
|
+
|
|
60
76
|
/**
|
|
61
77
|
* Send a transaction with optional ALT support.
|
|
62
78
|
* If DEFAULT_ALT_ADDRESS is configured, uses VersionedTransaction.
|
|
63
79
|
* Otherwise, uses legacy Transaction.
|
|
64
80
|
*
|
|
81
|
+
* opts.computeUnitLimit - set CU limit (ComputeBudgetProgram.setComputeUnitLimit)
|
|
82
|
+
* opts.computeUnitPrice - set CU price in micro-lamports (ComputeBudgetProgram.setComputeUnitPrice)
|
|
83
|
+
* opts.computeUnitPrice = "auto" - auto-fetch recent average priority fee
|
|
84
|
+
*
|
|
65
85
|
* @returns transaction signature
|
|
66
86
|
*/
|
|
67
87
|
export async function sendTx(
|
|
@@ -69,8 +89,30 @@ export async function sendTx(
|
|
|
69
89
|
payer: Keypair,
|
|
70
90
|
instructions: TransactionInstruction[],
|
|
71
91
|
signers?: Keypair[],
|
|
72
|
-
opts?: { skipPreflight?: boolean }
|
|
92
|
+
opts?: { skipPreflight?: boolean; computeUnitLimit?: number; computeUnitPrice?: number | "auto" }
|
|
73
93
|
): Promise<string> {
|
|
94
|
+
// Prepend compute budget instructions
|
|
95
|
+
const budgetIxs: TransactionInstruction[] = [];
|
|
96
|
+
if (opts?.computeUnitLimit) {
|
|
97
|
+
budgetIxs.push(
|
|
98
|
+
ComputeBudgetProgram.setComputeUnitLimit({ units: opts.computeUnitLimit })
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (opts?.computeUnitPrice !== undefined) {
|
|
102
|
+
let price: number;
|
|
103
|
+
if (opts.computeUnitPrice === "auto") {
|
|
104
|
+
price = await getRecentPriorityFee(connection);
|
|
105
|
+
} else {
|
|
106
|
+
price = opts.computeUnitPrice;
|
|
107
|
+
}
|
|
108
|
+
if (price > 0) {
|
|
109
|
+
budgetIxs.push(
|
|
110
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: price })
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const allInstructions = [...budgetIxs, ...instructions];
|
|
115
|
+
|
|
74
116
|
const alt = await loadAlt(connection);
|
|
75
117
|
const { blockhash, lastValidBlockHeight } =
|
|
76
118
|
await connection.getLatestBlockhash("confirmed");
|
|
@@ -81,7 +123,7 @@ export async function sendTx(
|
|
|
81
123
|
const message = new TransactionMessage({
|
|
82
124
|
payerKey: payer.publicKey,
|
|
83
125
|
recentBlockhash: blockhash,
|
|
84
|
-
instructions,
|
|
126
|
+
instructions: allInstructions,
|
|
85
127
|
}).compileToV0Message([alt]);
|
|
86
128
|
|
|
87
129
|
const tx = new VersionedTransaction(message);
|
|
@@ -103,7 +145,7 @@ export async function sendTx(
|
|
|
103
145
|
tx.recentBlockhash = blockhash;
|
|
104
146
|
tx.lastValidBlockHeight = lastValidBlockHeight;
|
|
105
147
|
tx.feePayer = payer.publicKey;
|
|
106
|
-
for (const ix of
|
|
148
|
+
for (const ix of allInstructions) tx.add(ix);
|
|
107
149
|
const allSigners = [payer, ...(signers ?? [])];
|
|
108
150
|
const seen = new Set<string>();
|
|
109
151
|
const uniqueSigners = allSigners.filter((s) => {
|
|
@@ -118,14 +160,38 @@ export async function sendTx(
|
|
|
118
160
|
});
|
|
119
161
|
}
|
|
120
162
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
163
|
+
// Poll for confirmation (avoid confirmTransaction which uses WebSocket)
|
|
164
|
+
const startTime = Date.now();
|
|
165
|
+
const TIMEOUT_MS = 20_000;
|
|
166
|
+
const POLL_INTERVAL_MS = 1_000;
|
|
167
|
+
|
|
168
|
+
while (Date.now() - startTime < TIMEOUT_MS) {
|
|
169
|
+
const currentBlockHeight = await connection.getBlockHeight("confirmed");
|
|
170
|
+
if (currentBlockHeight > lastValidBlockHeight) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`Transaction ${signature} expired: block height exceeded`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const statusResult = await connection.getSignatureStatuses([signature]);
|
|
177
|
+
const status = statusResult?.value?.[0];
|
|
178
|
+
|
|
179
|
+
if (status) {
|
|
180
|
+
if (status.err) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
`Transaction ${signature} failed: ${JSON.stringify(status.err)}`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (
|
|
186
|
+
status.confirmationStatus === "confirmed" ||
|
|
187
|
+
status.confirmationStatus === "finalized"
|
|
188
|
+
) {
|
|
189
|
+
return signature;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
129
194
|
}
|
|
130
|
-
|
|
195
|
+
|
|
196
|
+
throw new Error(`Transaction ${signature} confirmation timeout`);
|
|
131
197
|
}
|