nara-sdk 1.0.45 → 1.0.49
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 +2 -2
- package/package.json +1 -1
- package/src/constants.ts +4 -3
- package/src/idls/nara_quest.json +86 -25
- package/src/idls/nara_quest.ts +86 -25
- package/src/quest.ts +80 -15
- package/src/tx.ts +83 -40
package/index.ts
CHANGED
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -39,8 +39,9 @@ export const DEFAULT_AGENT_REGISTRY_PROGRAM_ID =
|
|
|
39
39
|
process.env.AGENT_REGISTRY_PROGRAM_ID || "AgentRegistry111111111111111111111111111111";
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* Address Lookup Table
|
|
43
|
-
*
|
|
42
|
+
* Address Lookup Table addresses for transaction optimization.
|
|
43
|
+
* Supports comma-separated list for multiple ALTs.
|
|
44
|
+
* When set, all SDK transactions use VersionedTransaction with these ALTs.
|
|
44
45
|
* When empty, uses legacy transactions.
|
|
45
46
|
*/
|
|
46
|
-
export const DEFAULT_ALT_ADDRESS = process.env.ALT_ADDRESS || "";
|
|
47
|
+
export const DEFAULT_ALT_ADDRESS = process.env.ALT_ADDRESS || "7u3uwQof8YnTrdYE2ZXgAYQLsDxW9cEJjqC3zJHrZXGo";
|
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) {
|
|
@@ -649,34 +698,40 @@ export async function initializeQuest(
|
|
|
649
698
|
}
|
|
650
699
|
|
|
651
700
|
/**
|
|
652
|
-
* Set the
|
|
701
|
+
* Set the reward config (authority only).
|
|
653
702
|
*/
|
|
654
|
-
export async function
|
|
703
|
+
export async function setRewardConfig(
|
|
655
704
|
connection: Connection,
|
|
656
705
|
wallet: Keypair,
|
|
706
|
+
minRewardCount: number,
|
|
657
707
|
maxRewardCount: number,
|
|
658
708
|
options?: QuestOptions
|
|
659
709
|
): Promise<string> {
|
|
660
710
|
const program = createProgram(connection, wallet, options?.programId);
|
|
661
711
|
const ix = await program.methods
|
|
662
|
-
.
|
|
712
|
+
.setRewardConfig(minRewardCount, maxRewardCount)
|
|
663
713
|
.accounts({ authority: wallet.publicKey } as any)
|
|
664
714
|
.instruction();
|
|
665
715
|
return sendTx(connection, wallet, [ix]);
|
|
666
716
|
}
|
|
667
717
|
|
|
668
718
|
/**
|
|
669
|
-
* 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
|
|
670
723
|
*/
|
|
671
|
-
export async function
|
|
724
|
+
export async function setStakeConfig(
|
|
672
725
|
connection: Connection,
|
|
673
726
|
wallet: Keypair,
|
|
674
|
-
|
|
727
|
+
bpsHigh: number,
|
|
728
|
+
bpsLow: number,
|
|
729
|
+
decayMs: number,
|
|
675
730
|
options?: QuestOptions
|
|
676
731
|
): Promise<string> {
|
|
677
732
|
const program = createProgram(connection, wallet, options?.programId);
|
|
678
733
|
const ix = await program.methods
|
|
679
|
-
.
|
|
734
|
+
.setStakeConfig(new BN(bpsHigh), new BN(bpsLow), new BN(decayMs))
|
|
680
735
|
.accounts({ authority: wallet.publicKey } as any)
|
|
681
736
|
.instruction();
|
|
682
737
|
return sendTx(connection, wallet, [ix]);
|
|
@@ -705,7 +760,14 @@ export async function transferQuestAuthority(
|
|
|
705
760
|
export async function getQuestConfig(
|
|
706
761
|
connection: Connection,
|
|
707
762
|
options?: QuestOptions
|
|
708
|
-
): Promise<{
|
|
763
|
+
): Promise<{
|
|
764
|
+
authority: PublicKey;
|
|
765
|
+
minRewardCount: number;
|
|
766
|
+
maxRewardCount: number;
|
|
767
|
+
stakeBpsHigh: number;
|
|
768
|
+
stakeBpsLow: number;
|
|
769
|
+
decayMs: number;
|
|
770
|
+
}> {
|
|
709
771
|
const kp = Keypair.generate();
|
|
710
772
|
const program = createProgram(connection, kp, options?.programId);
|
|
711
773
|
const programId = new PublicKey(options?.programId ?? DEFAULT_QUEST_PROGRAM_ID);
|
|
@@ -718,5 +780,8 @@ export async function getQuestConfig(
|
|
|
718
780
|
authority: config.authority,
|
|
719
781
|
minRewardCount: config.minRewardCount,
|
|
720
782
|
maxRewardCount: config.maxRewardCount,
|
|
783
|
+
stakeBpsHigh: Number(config.stakeBpsHigh.toString()),
|
|
784
|
+
stakeBpsLow: Number(config.stakeBpsLow.toString()),
|
|
785
|
+
decayMs: Number(config.decayMs.toString()),
|
|
721
786
|
};
|
|
722
787
|
}
|
package/src/tx.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared transaction sending utility with optional Address Lookup Table support.
|
|
3
3
|
*
|
|
4
|
-
* When
|
|
5
|
-
* with
|
|
4
|
+
* When ALT addresses are configured, transactions are sent as VersionedTransaction
|
|
5
|
+
* with ALTs for smaller on-chain size. Otherwise, legacy Transaction is used.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
@@ -18,44 +18,63 @@ import {
|
|
|
18
18
|
} from "@solana/web3.js";
|
|
19
19
|
import { DEFAULT_ALT_ADDRESS } from "./constants";
|
|
20
20
|
|
|
21
|
-
let
|
|
22
|
-
let
|
|
23
|
-
let
|
|
21
|
+
let _cachedAlts: AddressLookupTableAccount[] = [];
|
|
22
|
+
let _cachedAltKey: string = "";
|
|
23
|
+
let _overrideAltAddresses: string[] | null = null;
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Set
|
|
27
|
-
* Pass empty
|
|
26
|
+
* Set global ALT addresses at runtime (overrides DEFAULT_ALT_ADDRESS / env).
|
|
27
|
+
* Pass empty array or null to disable ALT.
|
|
28
28
|
*/
|
|
29
|
-
export function setAltAddress(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
export function setAltAddress(addresses: string | string[] | null): void {
|
|
30
|
+
if (addresses === null) {
|
|
31
|
+
_overrideAltAddresses = [];
|
|
32
|
+
} else if (typeof addresses === "string") {
|
|
33
|
+
_overrideAltAddresses = addresses ? [addresses] : [];
|
|
34
|
+
} else {
|
|
35
|
+
_overrideAltAddresses = addresses.filter(Boolean);
|
|
36
|
+
}
|
|
37
|
+
// Invalidate cache when addresses change
|
|
38
|
+
_cachedAlts = [];
|
|
39
|
+
_cachedAltKey = "";
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
/**
|
|
37
|
-
* Get the current effective ALT
|
|
43
|
+
* Get the current effective ALT addresses.
|
|
38
44
|
*/
|
|
39
|
-
export function getAltAddress(): string {
|
|
40
|
-
|
|
45
|
+
export function getAltAddress(): string[] {
|
|
46
|
+
if (_overrideAltAddresses !== null) return _overrideAltAddresses;
|
|
47
|
+
if (!DEFAULT_ALT_ADDRESS) return [];
|
|
48
|
+
// env supports comma-separated list
|
|
49
|
+
return DEFAULT_ALT_ADDRESS.split(",").map((s) => s.trim()).filter(Boolean);
|
|
41
50
|
}
|
|
42
51
|
|
|
43
|
-
async function
|
|
52
|
+
async function loadAlts(
|
|
44
53
|
connection: Connection
|
|
45
|
-
): Promise<AddressLookupTableAccount
|
|
46
|
-
const
|
|
47
|
-
if (!
|
|
54
|
+
): Promise<AddressLookupTableAccount[]> {
|
|
55
|
+
const addrs = getAltAddress();
|
|
56
|
+
if (!addrs.length) return [];
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
if (
|
|
58
|
+
const key = addrs.join(",");
|
|
59
|
+
if (_cachedAlts.length && _cachedAltKey === key) return _cachedAlts;
|
|
51
60
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
const results: AddressLookupTableAccount[] = [];
|
|
62
|
+
for (const addr of addrs) {
|
|
63
|
+
try {
|
|
64
|
+
const result = await connection.getAddressLookupTable(new PublicKey(addr));
|
|
65
|
+
if (result.value) {
|
|
66
|
+
results.push(result.value);
|
|
67
|
+
} else {
|
|
68
|
+
console.warn(`[nara-sdk] ALT not found: ${addr}, skipping`);
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.warn(`[nara-sdk] Failed to load ALT ${addr}: ${e}, skipping`);
|
|
72
|
+
}
|
|
55
73
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
|
|
75
|
+
_cachedAlts = results;
|
|
76
|
+
_cachedAltKey = key;
|
|
77
|
+
return _cachedAlts;
|
|
59
78
|
}
|
|
60
79
|
|
|
61
80
|
/**
|
|
@@ -75,7 +94,7 @@ export async function getRecentPriorityFee(
|
|
|
75
94
|
|
|
76
95
|
/**
|
|
77
96
|
* Send a transaction with optional ALT support.
|
|
78
|
-
* If
|
|
97
|
+
* If ALT addresses are configured, uses VersionedTransaction.
|
|
79
98
|
* Otherwise, uses legacy Transaction.
|
|
80
99
|
*
|
|
81
100
|
* opts.computeUnitLimit - set CU limit (ComputeBudgetProgram.setComputeUnitLimit)
|
|
@@ -113,18 +132,18 @@ export async function sendTx(
|
|
|
113
132
|
}
|
|
114
133
|
const allInstructions = [...budgetIxs, ...instructions];
|
|
115
134
|
|
|
116
|
-
const
|
|
135
|
+
const alts = await loadAlts(connection);
|
|
117
136
|
const { blockhash, lastValidBlockHeight } =
|
|
118
137
|
await connection.getLatestBlockhash("confirmed");
|
|
119
138
|
|
|
120
139
|
let signature: string;
|
|
121
140
|
|
|
122
|
-
if (
|
|
141
|
+
if (alts.length) {
|
|
123
142
|
const message = new TransactionMessage({
|
|
124
143
|
payerKey: payer.publicKey,
|
|
125
144
|
recentBlockhash: blockhash,
|
|
126
145
|
instructions: allInstructions,
|
|
127
|
-
}).compileToV0Message(
|
|
146
|
+
}).compileToV0Message(alts);
|
|
128
147
|
|
|
129
148
|
const tx = new VersionedTransaction(message);
|
|
130
149
|
const allSigners = [payer, ...(signers ?? [])];
|
|
@@ -160,14 +179,38 @@ export async function sendTx(
|
|
|
160
179
|
});
|
|
161
180
|
}
|
|
162
181
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
182
|
+
// Poll for confirmation (avoid confirmTransaction which uses WebSocket)
|
|
183
|
+
const startTime = Date.now();
|
|
184
|
+
const TIMEOUT_MS = 20_000;
|
|
185
|
+
const POLL_INTERVAL_MS = 1_000;
|
|
186
|
+
|
|
187
|
+
while (Date.now() - startTime < TIMEOUT_MS) {
|
|
188
|
+
const currentBlockHeight = await connection.getBlockHeight("confirmed");
|
|
189
|
+
if (currentBlockHeight > lastValidBlockHeight) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Transaction ${signature} expired: block height exceeded`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const statusResult = await connection.getSignatureStatuses([signature]);
|
|
196
|
+
const status = statusResult?.value?.[0];
|
|
197
|
+
|
|
198
|
+
if (status) {
|
|
199
|
+
if (status.err) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Transaction ${signature} failed: ${JSON.stringify(status.err)}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (
|
|
205
|
+
status.confirmationStatus === "confirmed" ||
|
|
206
|
+
status.confirmationStatus === "finalized"
|
|
207
|
+
) {
|
|
208
|
+
return signature;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
171
213
|
}
|
|
172
|
-
|
|
214
|
+
|
|
215
|
+
throw new Error(`Transaction ${signature} confirmation timeout`);
|
|
173
216
|
}
|