nara-sdk 1.0.41 → 1.0.43

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 CHANGED
@@ -26,7 +26,16 @@ export {
26
26
  parseQuestReward,
27
27
  computeAnswerHash,
28
28
  createQuestion,
29
+ stake,
30
+ unstake,
31
+ getStakeInfo,
32
+ initializeQuest,
33
+ setMaxRewardCount,
34
+ setMinRewardCount,
35
+ transferQuestAuthority,
36
+ getQuestConfig,
29
37
  type QuestInfo,
38
+ type StakeInfo,
30
39
  type ZkProof,
31
40
  type ZkProofHex,
32
41
  type SubmitAnswerResult,
@@ -105,6 +114,7 @@ export {
105
114
  updatePointsConfig,
106
115
  setReferral,
107
116
  updateReferralConfig,
117
+ updateActivityConfig,
108
118
  type AgentRecord,
109
119
  type AgentInfo,
110
120
  type MemoryMode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nara-sdk",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
4
4
  "description": "SDK for the Nara chain (Solana-compatible)",
5
5
  "module": "index.ts",
6
6
  "main": "index.ts",
@@ -998,3 +998,25 @@ export async function updateReferralConfig(
998
998
  .signers([wallet])
999
999
  .rpc();
1000
1000
  }
1001
+
1002
+ /**
1003
+ * Update the activity reward configuration (admin-only).
1004
+ * @param activityReward - Points awarded per activity
1005
+ * @param referralActivityReward - Points awarded to referrer per activity
1006
+ */
1007
+ export async function updateActivityConfig(
1008
+ connection: Connection,
1009
+ wallet: Keypair,
1010
+ activityReward: number | anchor.BN,
1011
+ referralActivityReward: number | anchor.BN,
1012
+ options?: AgentRegistryOptions
1013
+ ): Promise<string> {
1014
+ const program = createProgram(connection, wallet, options?.programId);
1015
+ const ar = typeof activityReward === "number" ? new anchor.BN(activityReward) : activityReward;
1016
+ const rar = typeof referralActivityReward === "number" ? new anchor.BN(referralActivityReward) : referralActivityReward;
1017
+ return program.methods
1018
+ .updateActivityConfig(ar, rar)
1019
+ .accounts({ admin: wallet.publicKey } as any)
1020
+ .signers([wallet])
1021
+ .rpc();
1022
+ }
@@ -203,6 +203,216 @@
203
203
  ],
204
204
  "args": []
205
205
  },
206
+ {
207
+ "name": "set_max_reward_count",
208
+ "discriminator": [
209
+ 247,
210
+ 62,
211
+ 67,
212
+ 243,
213
+ 249,
214
+ 243,
215
+ 102,
216
+ 62
217
+ ],
218
+ "accounts": [
219
+ {
220
+ "name": "game_config",
221
+ "writable": true,
222
+ "pda": {
223
+ "seeds": [
224
+ {
225
+ "kind": "const",
226
+ "value": [
227
+ 113,
228
+ 117,
229
+ 101,
230
+ 115,
231
+ 116,
232
+ 95,
233
+ 99,
234
+ 111,
235
+ 110,
236
+ 102,
237
+ 105,
238
+ 103
239
+ ]
240
+ }
241
+ ]
242
+ }
243
+ },
244
+ {
245
+ "name": "authority",
246
+ "signer": true
247
+ }
248
+ ],
249
+ "args": [
250
+ {
251
+ "name": "max_reward_count",
252
+ "type": "u32"
253
+ }
254
+ ]
255
+ },
256
+ {
257
+ "name": "set_min_reward_count",
258
+ "discriminator": [
259
+ 108,
260
+ 213,
261
+ 24,
262
+ 47,
263
+ 93,
264
+ 149,
265
+ 58,
266
+ 4
267
+ ],
268
+ "accounts": [
269
+ {
270
+ "name": "game_config",
271
+ "writable": true,
272
+ "pda": {
273
+ "seeds": [
274
+ {
275
+ "kind": "const",
276
+ "value": [
277
+ 113,
278
+ 117,
279
+ 101,
280
+ 115,
281
+ 116,
282
+ 95,
283
+ 99,
284
+ 111,
285
+ 110,
286
+ 102,
287
+ 105,
288
+ 103
289
+ ]
290
+ }
291
+ ]
292
+ }
293
+ },
294
+ {
295
+ "name": "authority",
296
+ "signer": true
297
+ }
298
+ ],
299
+ "args": [
300
+ {
301
+ "name": "min_reward_count",
302
+ "type": "u32"
303
+ }
304
+ ]
305
+ },
306
+ {
307
+ "name": "stake",
308
+ "discriminator": [
309
+ 206,
310
+ 176,
311
+ 202,
312
+ 18,
313
+ 200,
314
+ 209,
315
+ 179,
316
+ 108
317
+ ],
318
+ "accounts": [
319
+ {
320
+ "name": "pool",
321
+ "pda": {
322
+ "seeds": [
323
+ {
324
+ "kind": "const",
325
+ "value": [
326
+ 113,
327
+ 117,
328
+ 101,
329
+ 115,
330
+ 116,
331
+ 95,
332
+ 112,
333
+ 111,
334
+ 111,
335
+ 108
336
+ ]
337
+ }
338
+ ]
339
+ }
340
+ },
341
+ {
342
+ "name": "stake_record",
343
+ "writable": true,
344
+ "pda": {
345
+ "seeds": [
346
+ {
347
+ "kind": "const",
348
+ "value": [
349
+ 113,
350
+ 117,
351
+ 101,
352
+ 115,
353
+ 116,
354
+ 95,
355
+ 115,
356
+ 116,
357
+ 97,
358
+ 107,
359
+ 101
360
+ ]
361
+ },
362
+ {
363
+ "kind": "account",
364
+ "path": "user"
365
+ }
366
+ ]
367
+ }
368
+ },
369
+ {
370
+ "name": "stake_vault",
371
+ "writable": true,
372
+ "pda": {
373
+ "seeds": [
374
+ {
375
+ "kind": "const",
376
+ "value": [
377
+ 113,
378
+ 117,
379
+ 101,
380
+ 115,
381
+ 116,
382
+ 95,
383
+ 115,
384
+ 116,
385
+ 97,
386
+ 107,
387
+ 101,
388
+ 95,
389
+ 118,
390
+ 97,
391
+ 117,
392
+ 108,
393
+ 116
394
+ ]
395
+ }
396
+ ]
397
+ }
398
+ },
399
+ {
400
+ "name": "user",
401
+ "writable": true,
402
+ "signer": true
403
+ },
404
+ {
405
+ "name": "system_program",
406
+ "address": "11111111111111111111111111111111"
407
+ }
408
+ ],
409
+ "args": [
410
+ {
411
+ "name": "amount",
412
+ "type": "u64"
413
+ }
414
+ ]
415
+ },
206
416
  {
207
417
  "name": "submit_answer",
208
418
  "discriminator": [
@@ -268,6 +478,34 @@
268
478
  ]
269
479
  }
270
480
  },
481
+ {
482
+ "name": "stake_record",
483
+ "writable": true,
484
+ "pda": {
485
+ "seeds": [
486
+ {
487
+ "kind": "const",
488
+ "value": [
489
+ 113,
490
+ 117,
491
+ 101,
492
+ 115,
493
+ 116,
494
+ 95,
495
+ 115,
496
+ 116,
497
+ 97,
498
+ 107,
499
+ 101
500
+ ]
501
+ },
502
+ {
503
+ "kind": "account",
504
+ "path": "user"
505
+ }
506
+ ]
507
+ }
508
+ },
271
509
  {
272
510
  "name": "vault",
273
511
  "writable": true,
@@ -393,6 +631,116 @@
393
631
  "type": "pubkey"
394
632
  }
395
633
  ]
634
+ },
635
+ {
636
+ "name": "unstake",
637
+ "discriminator": [
638
+ 90,
639
+ 95,
640
+ 107,
641
+ 42,
642
+ 205,
643
+ 124,
644
+ 50,
645
+ 225
646
+ ],
647
+ "accounts": [
648
+ {
649
+ "name": "pool",
650
+ "pda": {
651
+ "seeds": [
652
+ {
653
+ "kind": "const",
654
+ "value": [
655
+ 113,
656
+ 117,
657
+ 101,
658
+ 115,
659
+ 116,
660
+ 95,
661
+ 112,
662
+ 111,
663
+ 111,
664
+ 108
665
+ ]
666
+ }
667
+ ]
668
+ }
669
+ },
670
+ {
671
+ "name": "stake_record",
672
+ "writable": true,
673
+ "pda": {
674
+ "seeds": [
675
+ {
676
+ "kind": "const",
677
+ "value": [
678
+ 113,
679
+ 117,
680
+ 101,
681
+ 115,
682
+ 116,
683
+ 95,
684
+ 115,
685
+ 116,
686
+ 97,
687
+ 107,
688
+ 101
689
+ ]
690
+ },
691
+ {
692
+ "kind": "account",
693
+ "path": "user"
694
+ }
695
+ ]
696
+ }
697
+ },
698
+ {
699
+ "name": "stake_vault",
700
+ "writable": true,
701
+ "pda": {
702
+ "seeds": [
703
+ {
704
+ "kind": "const",
705
+ "value": [
706
+ 113,
707
+ 117,
708
+ 101,
709
+ 115,
710
+ 116,
711
+ 95,
712
+ 115,
713
+ 116,
714
+ 97,
715
+ 107,
716
+ 101,
717
+ 95,
718
+ 118,
719
+ 97,
720
+ 117,
721
+ 108,
722
+ 116
723
+ ]
724
+ }
725
+ ]
726
+ }
727
+ },
728
+ {
729
+ "name": "user",
730
+ "writable": true,
731
+ "signer": true
732
+ },
733
+ {
734
+ "name": "system_program",
735
+ "address": "11111111111111111111111111111111"
736
+ }
737
+ ],
738
+ "args": [
739
+ {
740
+ "name": "amount",
741
+ "type": "u64"
742
+ }
743
+ ]
396
744
  }
397
745
  ],
398
746
  "accounts": [
@@ -422,6 +770,19 @@
422
770
  188
423
771
  ]
424
772
  },
773
+ {
774
+ "name": "StakeRecord",
775
+ "discriminator": [
776
+ 174,
777
+ 163,
778
+ 11,
779
+ 208,
780
+ 150,
781
+ 236,
782
+ 11,
783
+ 205
784
+ ]
785
+ },
425
786
  {
426
787
  "name": "WinnerRecord",
427
788
  "discriminator": [
@@ -491,6 +852,26 @@
491
852
  "code": 6007,
492
853
  "name": "AlreadyAnswered",
493
854
  "msg": "Already answered this round"
855
+ },
856
+ {
857
+ "code": 6008,
858
+ "name": "InvalidMinRewardCount",
859
+ "msg": "min_reward_count must be > 0 and <= max_reward_count"
860
+ },
861
+ {
862
+ "code": 6009,
863
+ "name": "InvalidMaxRewardCount",
864
+ "msg": "max_reward_count must be >= min_reward_count"
865
+ },
866
+ {
867
+ "code": 6010,
868
+ "name": "UnstakeNotReady",
869
+ "msg": "Cannot unstake until round advances or deadline passes"
870
+ },
871
+ {
872
+ "code": 6011,
873
+ "name": "InsufficientStakeBalance",
874
+ "msg": "Unstake amount exceeds staked balance"
494
875
  }
495
876
  ],
496
877
  "types": [
@@ -535,6 +916,14 @@
535
916
  "name": "authority",
536
917
  "type": "pubkey"
537
918
  },
919
+ {
920
+ "name": "min_reward_count",
921
+ "type": "u32"
922
+ },
923
+ {
924
+ "name": "max_reward_count",
925
+ "type": "u32"
926
+ },
538
927
  {
539
928
  "name": "_padding",
540
929
  "type": {
@@ -593,6 +982,39 @@
593
982
  "name": "difficulty",
594
983
  "type": "u32"
595
984
  },
985
+ {
986
+ "name": "stake_requirement",
987
+ "type": "u64"
988
+ },
989
+ {
990
+ "name": "min_winner_stake",
991
+ "type": "u64"
992
+ },
993
+ {
994
+ "name": "_padding",
995
+ "type": {
996
+ "array": [
997
+ "u8",
998
+ 64
999
+ ]
1000
+ }
1001
+ }
1002
+ ]
1003
+ }
1004
+ },
1005
+ {
1006
+ "name": "StakeRecord",
1007
+ "type": {
1008
+ "kind": "struct",
1009
+ "fields": [
1010
+ {
1011
+ "name": "amount",
1012
+ "type": "u64"
1013
+ },
1014
+ {
1015
+ "name": "stake_round",
1016
+ "type": "u64"
1017
+ },
596
1018
  {
597
1019
  "name": "_padding",
598
1020
  "type": {
@@ -209,6 +209,216 @@ export type NaraQuest = {
209
209
  ],
210
210
  "args": []
211
211
  },
212
+ {
213
+ "name": "setMaxRewardCount",
214
+ "discriminator": [
215
+ 247,
216
+ 62,
217
+ 67,
218
+ 243,
219
+ 249,
220
+ 243,
221
+ 102,
222
+ 62
223
+ ],
224
+ "accounts": [
225
+ {
226
+ "name": "gameConfig",
227
+ "writable": true,
228
+ "pda": {
229
+ "seeds": [
230
+ {
231
+ "kind": "const",
232
+ "value": [
233
+ 113,
234
+ 117,
235
+ 101,
236
+ 115,
237
+ 116,
238
+ 95,
239
+ 99,
240
+ 111,
241
+ 110,
242
+ 102,
243
+ 105,
244
+ 103
245
+ ]
246
+ }
247
+ ]
248
+ }
249
+ },
250
+ {
251
+ "name": "authority",
252
+ "signer": true
253
+ }
254
+ ],
255
+ "args": [
256
+ {
257
+ "name": "maxRewardCount",
258
+ "type": "u32"
259
+ }
260
+ ]
261
+ },
262
+ {
263
+ "name": "setMinRewardCount",
264
+ "discriminator": [
265
+ 108,
266
+ 213,
267
+ 24,
268
+ 47,
269
+ 93,
270
+ 149,
271
+ 58,
272
+ 4
273
+ ],
274
+ "accounts": [
275
+ {
276
+ "name": "gameConfig",
277
+ "writable": true,
278
+ "pda": {
279
+ "seeds": [
280
+ {
281
+ "kind": "const",
282
+ "value": [
283
+ 113,
284
+ 117,
285
+ 101,
286
+ 115,
287
+ 116,
288
+ 95,
289
+ 99,
290
+ 111,
291
+ 110,
292
+ 102,
293
+ 105,
294
+ 103
295
+ ]
296
+ }
297
+ ]
298
+ }
299
+ },
300
+ {
301
+ "name": "authority",
302
+ "signer": true
303
+ }
304
+ ],
305
+ "args": [
306
+ {
307
+ "name": "minRewardCount",
308
+ "type": "u32"
309
+ }
310
+ ]
311
+ },
312
+ {
313
+ "name": "stake",
314
+ "discriminator": [
315
+ 206,
316
+ 176,
317
+ 202,
318
+ 18,
319
+ 200,
320
+ 209,
321
+ 179,
322
+ 108
323
+ ],
324
+ "accounts": [
325
+ {
326
+ "name": "pool",
327
+ "pda": {
328
+ "seeds": [
329
+ {
330
+ "kind": "const",
331
+ "value": [
332
+ 113,
333
+ 117,
334
+ 101,
335
+ 115,
336
+ 116,
337
+ 95,
338
+ 112,
339
+ 111,
340
+ 111,
341
+ 108
342
+ ]
343
+ }
344
+ ]
345
+ }
346
+ },
347
+ {
348
+ "name": "stakeRecord",
349
+ "writable": true,
350
+ "pda": {
351
+ "seeds": [
352
+ {
353
+ "kind": "const",
354
+ "value": [
355
+ 113,
356
+ 117,
357
+ 101,
358
+ 115,
359
+ 116,
360
+ 95,
361
+ 115,
362
+ 116,
363
+ 97,
364
+ 107,
365
+ 101
366
+ ]
367
+ },
368
+ {
369
+ "kind": "account",
370
+ "path": "user"
371
+ }
372
+ ]
373
+ }
374
+ },
375
+ {
376
+ "name": "stakeVault",
377
+ "writable": true,
378
+ "pda": {
379
+ "seeds": [
380
+ {
381
+ "kind": "const",
382
+ "value": [
383
+ 113,
384
+ 117,
385
+ 101,
386
+ 115,
387
+ 116,
388
+ 95,
389
+ 115,
390
+ 116,
391
+ 97,
392
+ 107,
393
+ 101,
394
+ 95,
395
+ 118,
396
+ 97,
397
+ 117,
398
+ 108,
399
+ 116
400
+ ]
401
+ }
402
+ ]
403
+ }
404
+ },
405
+ {
406
+ "name": "user",
407
+ "writable": true,
408
+ "signer": true
409
+ },
410
+ {
411
+ "name": "systemProgram",
412
+ "address": "11111111111111111111111111111111"
413
+ }
414
+ ],
415
+ "args": [
416
+ {
417
+ "name": "amount",
418
+ "type": "u64"
419
+ }
420
+ ]
421
+ },
212
422
  {
213
423
  "name": "submitAnswer",
214
424
  "discriminator": [
@@ -274,6 +484,34 @@ export type NaraQuest = {
274
484
  ]
275
485
  }
276
486
  },
487
+ {
488
+ "name": "stakeRecord",
489
+ "writable": true,
490
+ "pda": {
491
+ "seeds": [
492
+ {
493
+ "kind": "const",
494
+ "value": [
495
+ 113,
496
+ 117,
497
+ 101,
498
+ 115,
499
+ 116,
500
+ 95,
501
+ 115,
502
+ 116,
503
+ 97,
504
+ 107,
505
+ 101
506
+ ]
507
+ },
508
+ {
509
+ "kind": "account",
510
+ "path": "user"
511
+ }
512
+ ]
513
+ }
514
+ },
277
515
  {
278
516
  "name": "vault",
279
517
  "writable": true,
@@ -399,6 +637,116 @@ export type NaraQuest = {
399
637
  "type": "pubkey"
400
638
  }
401
639
  ]
640
+ },
641
+ {
642
+ "name": "unstake",
643
+ "discriminator": [
644
+ 90,
645
+ 95,
646
+ 107,
647
+ 42,
648
+ 205,
649
+ 124,
650
+ 50,
651
+ 225
652
+ ],
653
+ "accounts": [
654
+ {
655
+ "name": "pool",
656
+ "pda": {
657
+ "seeds": [
658
+ {
659
+ "kind": "const",
660
+ "value": [
661
+ 113,
662
+ 117,
663
+ 101,
664
+ 115,
665
+ 116,
666
+ 95,
667
+ 112,
668
+ 111,
669
+ 111,
670
+ 108
671
+ ]
672
+ }
673
+ ]
674
+ }
675
+ },
676
+ {
677
+ "name": "stakeRecord",
678
+ "writable": true,
679
+ "pda": {
680
+ "seeds": [
681
+ {
682
+ "kind": "const",
683
+ "value": [
684
+ 113,
685
+ 117,
686
+ 101,
687
+ 115,
688
+ 116,
689
+ 95,
690
+ 115,
691
+ 116,
692
+ 97,
693
+ 107,
694
+ 101
695
+ ]
696
+ },
697
+ {
698
+ "kind": "account",
699
+ "path": "user"
700
+ }
701
+ ]
702
+ }
703
+ },
704
+ {
705
+ "name": "stakeVault",
706
+ "writable": true,
707
+ "pda": {
708
+ "seeds": [
709
+ {
710
+ "kind": "const",
711
+ "value": [
712
+ 113,
713
+ 117,
714
+ 101,
715
+ 115,
716
+ 116,
717
+ 95,
718
+ 115,
719
+ 116,
720
+ 97,
721
+ 107,
722
+ 101,
723
+ 95,
724
+ 118,
725
+ 97,
726
+ 117,
727
+ 108,
728
+ 116
729
+ ]
730
+ }
731
+ ]
732
+ }
733
+ },
734
+ {
735
+ "name": "user",
736
+ "writable": true,
737
+ "signer": true
738
+ },
739
+ {
740
+ "name": "systemProgram",
741
+ "address": "11111111111111111111111111111111"
742
+ }
743
+ ],
744
+ "args": [
745
+ {
746
+ "name": "amount",
747
+ "type": "u64"
748
+ }
749
+ ]
402
750
  }
403
751
  ],
404
752
  "accounts": [
@@ -428,6 +776,19 @@ export type NaraQuest = {
428
776
  188
429
777
  ]
430
778
  },
779
+ {
780
+ "name": "stakeRecord",
781
+ "discriminator": [
782
+ 174,
783
+ 163,
784
+ 11,
785
+ 208,
786
+ 150,
787
+ 236,
788
+ 11,
789
+ 205
790
+ ]
791
+ },
431
792
  {
432
793
  "name": "winnerRecord",
433
794
  "discriminator": [
@@ -497,6 +858,26 @@ export type NaraQuest = {
497
858
  "code": 6007,
498
859
  "name": "alreadyAnswered",
499
860
  "msg": "Already answered this round"
861
+ },
862
+ {
863
+ "code": 6008,
864
+ "name": "invalidMinRewardCount",
865
+ "msg": "min_reward_count must be > 0 and <= max_reward_count"
866
+ },
867
+ {
868
+ "code": 6009,
869
+ "name": "invalidMaxRewardCount",
870
+ "msg": "max_reward_count must be >= min_reward_count"
871
+ },
872
+ {
873
+ "code": 6010,
874
+ "name": "unstakeNotReady",
875
+ "msg": "Cannot unstake until round advances or deadline passes"
876
+ },
877
+ {
878
+ "code": 6011,
879
+ "name": "insufficientStakeBalance",
880
+ "msg": "Unstake amount exceeds staked balance"
500
881
  }
501
882
  ],
502
883
  "types": [
@@ -541,6 +922,14 @@ export type NaraQuest = {
541
922
  "name": "authority",
542
923
  "type": "pubkey"
543
924
  },
925
+ {
926
+ "name": "minRewardCount",
927
+ "type": "u32"
928
+ },
929
+ {
930
+ "name": "maxRewardCount",
931
+ "type": "u32"
932
+ },
544
933
  {
545
934
  "name": "padding",
546
935
  "type": {
@@ -599,6 +988,39 @@ export type NaraQuest = {
599
988
  "name": "difficulty",
600
989
  "type": "u32"
601
990
  },
991
+ {
992
+ "name": "stakeRequirement",
993
+ "type": "u64"
994
+ },
995
+ {
996
+ "name": "minWinnerStake",
997
+ "type": "u64"
998
+ },
999
+ {
1000
+ "name": "padding",
1001
+ "type": {
1002
+ "array": [
1003
+ "u8",
1004
+ 64
1005
+ ]
1006
+ }
1007
+ }
1008
+ ]
1009
+ }
1010
+ },
1011
+ {
1012
+ "name": "stakeRecord",
1013
+ "type": {
1014
+ "kind": "struct",
1015
+ "fields": [
1016
+ {
1017
+ "name": "amount",
1018
+ "type": "u64"
1019
+ },
1020
+ {
1021
+ "name": "stakeRound",
1022
+ "type": "u64"
1023
+ },
602
1024
  {
603
1025
  "name": "padding",
604
1026
  "type": {
package/src/quest.ts CHANGED
@@ -49,6 +49,17 @@ export interface QuestInfo {
49
49
  deadline: number;
50
50
  timeRemaining: number;
51
51
  expired: boolean;
52
+ /** Minimum stake required to submit an answer (in NARA) */
53
+ stakeRequirement: number;
54
+ /** Minimum stake to be eligible for rewards (in NARA) */
55
+ minWinnerStake: number;
56
+ }
57
+
58
+ export interface StakeInfo {
59
+ /** Current staked amount (in NARA) */
60
+ amount: number;
61
+ /** Round when the stake was made */
62
+ stakeRound: number;
52
63
  }
53
64
 
54
65
  export interface ZkProof {
@@ -77,6 +88,8 @@ export interface QuestOptions {
77
88
  circuitWasmPath?: string | Uint8Array;
78
89
  /** File path (Node.js), URL string, or pre-loaded Uint8Array (browser) */
79
90
  zkeyPath?: string | Uint8Array;
91
+ /** "auto" = auto top-up stake to stakeRequirement; number = stake exact NARA amount */
92
+ stake?: "auto" | number;
80
93
  }
81
94
 
82
95
  export interface ActivityLog {
@@ -211,6 +224,25 @@ function getWinnerRecordPda(
211
224
  return pda;
212
225
  }
213
226
 
227
+ function getStakeRecordPda(
228
+ programId: PublicKey,
229
+ user: PublicKey
230
+ ): PublicKey {
231
+ const [pda] = PublicKey.findProgramAddressSync(
232
+ [new TextEncoder().encode("quest_stake"), user.toBytes()],
233
+ programId
234
+ );
235
+ return pda;
236
+ }
237
+
238
+ function getStakeVaultPda(programId: PublicKey): PublicKey {
239
+ const [pda] = PublicKey.findProgramAddressSync(
240
+ [new TextEncoder().encode("quest_stake_vault")],
241
+ programId
242
+ );
243
+ return pda;
244
+ }
245
+
214
246
  // ─── SDK functions ───────────────────────────────────────────────
215
247
 
216
248
  /**
@@ -246,6 +278,8 @@ export async function getQuestInfo(
246
278
  deadline,
247
279
  timeRemaining: secsLeft,
248
280
  expired: secsLeft <= 0,
281
+ stakeRequirement: Number(pool.stakeRequirement.toString()) / LAMPORTS_PER_SOL,
282
+ minWinnerStake: Number(pool.minWinnerStake.toString()) / LAMPORTS_PER_SOL,
249
283
  };
250
284
  }
251
285
 
@@ -332,31 +366,66 @@ export async function submitAnswer(
332
366
  ): Promise<SubmitAnswerResult> {
333
367
  const program = createProgram(connection, wallet, options?.programId);
334
368
 
335
- if (activityLog) {
336
- const { makeLogActivityIx, makeLogActivityWithReferralIx } = await import("./agent_registry");
369
+ // Build optional stake instruction
370
+ let stakeIx: any = null;
371
+ if (options?.stake !== undefined) {
372
+ let stakeLamports: BN;
373
+ if (options.stake === "auto") {
374
+ const quest = await getQuestInfo(connection, wallet, options);
375
+ const stakeInfo = await getStakeInfo(connection, wallet.publicKey, options);
376
+ const required = quest.stakeRequirement;
377
+ const current = stakeInfo?.amount ?? 0;
378
+ const deficit = required - current;
379
+ if (deficit > 0) {
380
+ stakeLamports = new BN(Math.round(deficit * LAMPORTS_PER_SOL));
381
+ } else {
382
+ stakeLamports = new BN(0);
383
+ }
384
+ } else {
385
+ stakeLamports = new BN(Math.round(options.stake * LAMPORTS_PER_SOL));
386
+ }
387
+ if (!stakeLamports.isZero()) {
388
+ stakeIx = await program.methods
389
+ .stake(stakeLamports)
390
+ .accounts({ user: wallet.publicKey } as any)
391
+ .instruction();
392
+ }
393
+ }
394
+
395
+ // If we need a composite transaction (stake and/or activityLog)
396
+ if (stakeIx || activityLog) {
337
397
  const submitIx = await program.methods
338
398
  .submitAnswer(proof.proofA as any, proof.proofB as any, proof.proofC as any, agent, model)
339
399
  .accounts({ user: wallet.publicKey, payer: wallet.publicKey })
340
400
  .instruction();
341
- const logIx = activityLog.referralAgentId
342
- ? await makeLogActivityWithReferralIx(
343
- connection,
344
- wallet.publicKey,
345
- activityLog.agentId,
346
- activityLog.model,
347
- activityLog.activity,
348
- activityLog.log,
349
- activityLog.referralAgentId
350
- )
351
- : await makeLogActivityIx(
352
- connection,
353
- wallet.publicKey,
354
- activityLog.agentId,
355
- activityLog.model,
356
- activityLog.activity,
357
- activityLog.log
358
- );
359
- const tx = new Transaction().add(submitIx).add(logIx);
401
+
402
+ const tx = new Transaction();
403
+ if (stakeIx) tx.add(stakeIx);
404
+ tx.add(submitIx);
405
+
406
+ if (activityLog) {
407
+ const { makeLogActivityIx, makeLogActivityWithReferralIx } = await import("./agent_registry");
408
+ const logIx = activityLog.referralAgentId
409
+ ? await makeLogActivityWithReferralIx(
410
+ connection,
411
+ wallet.publicKey,
412
+ activityLog.agentId,
413
+ activityLog.model,
414
+ activityLog.activity,
415
+ activityLog.log,
416
+ activityLog.referralAgentId
417
+ )
418
+ : await makeLogActivityIx(
419
+ connection,
420
+ wallet.publicKey,
421
+ activityLog.agentId,
422
+ activityLog.model,
423
+ activityLog.activity,
424
+ activityLog.log
425
+ );
426
+ tx.add(logIx);
427
+ }
428
+
360
429
  tx.feePayer = wallet.publicKey;
361
430
  tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
362
431
  tx.sign(wallet);
@@ -469,6 +538,68 @@ export async function computeAnswerHash(answer: string): Promise<number[]> {
469
538
  return Array.from(bigintToBytes32(BigInt(hashStr)));
470
539
  }
471
540
 
541
+ /**
542
+ * Stake NARA to participate in quests.
543
+ *
544
+ * @param amount - Amount to stake in NARA
545
+ */
546
+ export async function stake(
547
+ connection: Connection,
548
+ wallet: Keypair,
549
+ amount: number,
550
+ options?: QuestOptions
551
+ ): Promise<string> {
552
+ const program = createProgram(connection, wallet, options?.programId);
553
+ const lamports = new BN(Math.round(amount * LAMPORTS_PER_SOL));
554
+ return program.methods
555
+ .stake(lamports)
556
+ .accounts({ user: wallet.publicKey } as any)
557
+ .signers([wallet])
558
+ .rpc();
559
+ }
560
+
561
+ /**
562
+ * Unstake NARA. Can only unstake after the round advances or deadline passes.
563
+ *
564
+ * @param amount - Amount to unstake in NARA
565
+ */
566
+ export async function unstake(
567
+ connection: Connection,
568
+ wallet: Keypair,
569
+ amount: number,
570
+ options?: QuestOptions
571
+ ): Promise<string> {
572
+ const program = createProgram(connection, wallet, options?.programId);
573
+ const lamports = new BN(Math.round(amount * LAMPORTS_PER_SOL));
574
+ return program.methods
575
+ .unstake(lamports)
576
+ .accounts({ user: wallet.publicKey } as any)
577
+ .signers([wallet])
578
+ .rpc();
579
+ }
580
+
581
+ /**
582
+ * Get stake info for a user. Returns null if no stake record exists.
583
+ */
584
+ export async function getStakeInfo(
585
+ connection: Connection,
586
+ user: PublicKey,
587
+ options?: QuestOptions
588
+ ): Promise<StakeInfo | null> {
589
+ const kp = Keypair.generate();
590
+ const program = createProgram(connection, kp, options?.programId);
591
+ const stakeRecordPda = getStakeRecordPda(program.programId, user);
592
+ try {
593
+ const record = await program.account.stakeRecord.fetch(stakeRecordPda);
594
+ return {
595
+ amount: record.amount.toNumber() / LAMPORTS_PER_SOL,
596
+ stakeRound: record.stakeRound.toNumber(),
597
+ };
598
+ } catch {
599
+ return null;
600
+ }
601
+ }
602
+
472
603
  /**
473
604
  * Create a new quest question on-chain (authority only).
474
605
  *
@@ -504,3 +635,94 @@ export async function createQuestion(
504
635
 
505
636
  return signature;
506
637
  }
638
+
639
+ // ─── Admin functions ───────────────────────────────────────────────
640
+
641
+ /**
642
+ * Initialize the quest program (one-time setup). The caller becomes the authority.
643
+ */
644
+ export async function initializeQuest(
645
+ connection: Connection,
646
+ wallet: Keypair,
647
+ options?: QuestOptions
648
+ ): Promise<string> {
649
+ const program = createProgram(connection, wallet, options?.programId);
650
+ return program.methods
651
+ .initialize()
652
+ .accounts({ authority: wallet.publicKey } as any)
653
+ .signers([wallet])
654
+ .rpc();
655
+ }
656
+
657
+ /**
658
+ * Set the maximum reward count (authority only).
659
+ */
660
+ export async function setMaxRewardCount(
661
+ connection: Connection,
662
+ wallet: Keypair,
663
+ maxRewardCount: number,
664
+ options?: QuestOptions
665
+ ): Promise<string> {
666
+ const program = createProgram(connection, wallet, options?.programId);
667
+ return program.methods
668
+ .setMaxRewardCount(maxRewardCount)
669
+ .accounts({ authority: wallet.publicKey } as any)
670
+ .signers([wallet])
671
+ .rpc();
672
+ }
673
+
674
+ /**
675
+ * Set the minimum reward count (authority only).
676
+ */
677
+ export async function setMinRewardCount(
678
+ connection: Connection,
679
+ wallet: Keypair,
680
+ minRewardCount: number,
681
+ options?: QuestOptions
682
+ ): Promise<string> {
683
+ const program = createProgram(connection, wallet, options?.programId);
684
+ return program.methods
685
+ .setMinRewardCount(minRewardCount)
686
+ .accounts({ authority: wallet.publicKey } as any)
687
+ .signers([wallet])
688
+ .rpc();
689
+ }
690
+
691
+ /**
692
+ * Transfer quest program authority to a new address (authority only).
693
+ */
694
+ export async function transferQuestAuthority(
695
+ connection: Connection,
696
+ wallet: Keypair,
697
+ newAuthority: PublicKey,
698
+ options?: QuestOptions
699
+ ): Promise<string> {
700
+ const program = createProgram(connection, wallet, options?.programId);
701
+ return program.methods
702
+ .transferAuthority(newAuthority)
703
+ .accounts({ authority: wallet.publicKey } as any)
704
+ .signers([wallet])
705
+ .rpc();
706
+ }
707
+
708
+ /**
709
+ * Get quest program config (authority, min/max reward count).
710
+ */
711
+ export async function getQuestConfig(
712
+ connection: Connection,
713
+ options?: QuestOptions
714
+ ): Promise<{ authority: PublicKey; minRewardCount: number; maxRewardCount: number }> {
715
+ const kp = Keypair.generate();
716
+ const program = createProgram(connection, kp, options?.programId);
717
+ const programId = new PublicKey(options?.programId ?? DEFAULT_QUEST_PROGRAM_ID);
718
+ const [configPda] = PublicKey.findProgramAddressSync(
719
+ [new TextEncoder().encode("quest_config")],
720
+ programId
721
+ );
722
+ const config = await program.account.gameConfig.fetch(configPda);
723
+ return {
724
+ authority: config.authority,
725
+ minRewardCount: config.minRewardCount,
726
+ maxRewardCount: config.maxRewardCount,
727
+ };
728
+ }