solotto 1.0.3 → 1.0.5

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/README.md CHANGED
@@ -18,6 +18,8 @@ A JavaScript SDK for interacting with the Solotto on-chain lottery program on So
18
18
  - [Initialize](#initialize)
19
19
  - [RandomDraw](#randomdraw)
20
20
  - [LockLottery](#locklottery)
21
+ - [ClaimExpired](#claimexpired)
22
+ - [Boost](#boost)
21
23
  - [Lottery](#lottery-api)
22
24
  - [BuyTickets](#buytickets)
23
25
  - [ClaimTicket](#claimticket)
@@ -198,6 +200,49 @@ await manager.LockLottery(authority, lotteryId, 1);
198
200
 
199
201
  ---
200
202
 
203
+ #### ClaimExpired
204
+
205
+ Reclaims the prize pool funds from an expired lottery where the winner did not claim in time. Only callable by the lottery authority.
206
+
207
+ ```js
208
+ const result = await manager.ClaimExpired(authority, lotteryId, encoded);
209
+ ```
210
+
211
+ | Parameter | Type | Default | Description |
212
+ |---|---|---|---|
213
+ | `authority` | `Keypair` | — | The lottery authority keypair. |
214
+ | `lotteryId` | `String` | — | The lottery ID. |
215
+ | `encoded` | `Boolean` | `false` | If `true`, returns encoded transaction. |
216
+
217
+ **Returns:** Updated lottery state object on finalization, `"Prize has already been claimed"` if the prize was already claimed, or the transaction object when encoded.
218
+
219
+ ---
220
+
221
+ #### Boost
222
+
223
+ Boosts a lottery's prize pool by transferring SOL from any wallet. Can be called by anyone, not just the authority. Optionally attaches a memo message to the transaction.
224
+
225
+ ```js
226
+ // Boost lottery #1 with 0.5 SOL
227
+ const result = await manager.Boost(authority, lotteryId, booster, 0.5);
228
+
229
+ // Boost with a memo message
230
+ const result = await manager.Boost(authority, lotteryId, booster, 1.0, "Good luck everyone!");
231
+ ```
232
+
233
+ | Parameter | Type | Default | Description |
234
+ |---|---|---|---|
235
+ | `authority` | `{publicKey}` | — | The lottery authority (only `publicKey` is needed). |
236
+ | `lotteryId` | `String` | — | The lottery ID. |
237
+ | `booster` | `Keypair` | — | The keypair of the wallet sending the boost. |
238
+ | `amount` | `Number` | — | Amount of SOL to boost (e.g. `0.5` for 0.5 SOL). |
239
+ | `message` | `String \| false` | `false` | Optional memo string attached to the transaction. |
240
+ | `encoded` | `Boolean` | `false` | If `true`, returns encoded transaction. |
241
+
242
+ **Returns:** `"boosted"` on success, `"Draw initiated, cannot boost this prize pool"` if the draw has already started, or the transaction object when encoded.
243
+
244
+ ---
245
+
201
246
  ### Lottery API
202
247
 
203
248
  #### BuyTickets
@@ -218,7 +263,7 @@ const result = await lottery.BuyTickets(buyer, authority, lotteryId, amount, enc
218
263
  | `amount` | `Number` | `1` | Number of tickets to purchase (1–4). |
219
264
  | `encoded` | `Boolean` | `false` | If `true`, returns encoded transaction. |
220
265
 
221
- **Returns:** `"finalized"` on success, or the transaction object when encoded.
266
+ **Returns:** `"finalized"` on success, `"Lottery is not active, no tickets can be sold"` if the lottery is inactive, or the transaction object when encoded.
222
267
 
223
268
  **Example:**
224
269
 
@@ -282,12 +327,14 @@ const state = await lottery.GetLottery(authority, lotteryId, fees);
282
327
  ticketPrice: 100000000, // Ticket price in lamports
283
328
  totalTickets: 42, // Total tickets sold
284
329
  winnerTicketNumber: 17, // Winning ticket number (0/null if not drawn)
285
- winnerAddress: "Pubkey...", // Winner's public key (null if not drawn)
330
+ winnerAddress: "Pubkey...", // Winner's public key as string (null if not drawn)
286
331
  isActive: true, // Whether the lottery is active
287
332
  prizePoolBalance: 3780000000, // Prize pool in lamports (after fees if fees=true)
288
333
  drawInitiated: false, // Whether a draw has been initiated
289
334
  prizePoolAddress: "Pubkey...", // Prize pool PDA
290
335
  lotteryAddress: "Pubkey...", // Lottery PDA
336
+ release: "Pubkey...", // Release address (lottery PDA)
337
+ releaseTime: null, // Unix timestamp when unclaimed prizes can be released (null if not set)
291
338
  }
292
339
  ```
293
340
 
package/SECURITY.md ADDED
@@ -0,0 +1,15 @@
1
+ # Security Policy
2
+
3
+ <a name="reporting"></a>
4
+ ## Reporting Security Problems
5
+
6
+ **DO NOT CREATE A GITHUB ISSUE** to report a security problem.
7
+
8
+ Please contact the maintainers:
9
+
10
+ X: @SolDapper
11
+
12
+ <a name="process"></a>
13
+ ## Incident Response Process
14
+
15
+ Maintainers will respond to security incidents as fast as possible, and will keep you informed of our progress. Thank you.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solotto",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Solana lottery client",
5
5
  "type": "module",
6
6
  "types": "./solotto.d.ts",
package/solotto.d.ts CHANGED
@@ -62,7 +62,7 @@ declare module "solotto" {
62
62
  totalTickets: number;
63
63
  /** Winning ticket number, or `0`/`null` if not yet drawn. */
64
64
  winnerTicketNumber: number | null;
65
- /** Winner's public key, or `null` if not yet drawn. */
65
+ /** Winner's public key as a base-58 string, or `null` if not yet drawn. */
66
66
  winnerAddress: string | null;
67
67
  /** Whether the lottery is currently active. */
68
68
  isActive: boolean;
@@ -74,6 +74,10 @@ declare module "solotto" {
74
74
  prizePoolAddress: string;
75
75
  /** Lottery PDA as a base-58 string. */
76
76
  lotteryAddress: string;
77
+ /** Release address (lottery PDA) as a base-58 string. */
78
+ release: string;
79
+ /** Unix timestamp when unclaimed prizes can be released, or `null` if not set. */
80
+ releaseTime: number | null;
77
81
  }
78
82
 
79
83
  // ── Ticket State ──────────────────────────────────────────────────────
@@ -321,5 +325,35 @@ declare module "solotto" {
321
325
  lockState: 0 | 1,
322
326
  encoded?: boolean
323
327
  ): Promise<LotteryState | string | TxResult | undefined>;
328
+
329
+ /**
330
+ * Reclaim prize pool funds from an expired lottery where the winner did not claim in time.
331
+ * @param authority - The lottery authority keypair.
332
+ * @param lotteryId - Lottery numeric identifier.
333
+ * @param encoded - If `true`, return a base64-encoded transaction.
334
+ */
335
+ ClaimExpired(
336
+ authority: Keypair,
337
+ lotteryId: number,
338
+ encoded?: boolean
339
+ ): Promise<LotteryState | string | TxResult | undefined>;
340
+
341
+ /**
342
+ * Boost a lottery's prize pool by transferring SOL from any wallet.
343
+ * @param authority - The lottery authority (only `publicKey` needed).
344
+ * @param lotteryId - Lottery numeric identifier.
345
+ * @param booster - The keypair of the wallet sending the boost.
346
+ * @param amount - Amount of SOL to boost (e.g. `0.5` for 0.5 SOL).
347
+ * @param message - Optional memo string attached to the transaction.
348
+ * @param encoded - If `true`, return a base64-encoded transaction.
349
+ */
350
+ Boost(
351
+ authority: HasPublicKey,
352
+ lotteryId: number,
353
+ booster: Keypair,
354
+ amount: number,
355
+ message?: string | false,
356
+ encoded?: boolean
357
+ ): Promise<string | TxResult | undefined>;
324
358
  }
325
- }
359
+ }
package/solotto.js CHANGED
@@ -1,10 +1,10 @@
1
- import {Connection, PublicKey, TransactionMessage, TransactionInstruction, VersionedTransaction, ComputeBudgetProgram, SystemProgram, Keypair, SYSVAR_SLOT_HASHES_PUBKEY, SYSVAR_CLOCK_PUBKEY, SYSVAR_RECENT_BLOCKHASHES_PUBKEY} from '@solana/web3.js';
1
+ import {Connection, PublicKey, TransactionMessage, TransactionInstruction, VersionedTransaction, ComputeBudgetProgram, SystemProgram, Keypair, SYSVAR_SLOT_HASHES_PUBKEY, LAMPORTS_PER_SOL} from '@solana/web3.js';
2
2
  import bs58 from 'bs58';
3
3
  import BN from 'bn.js';
4
4
  import {createMemoInstruction} from '@solana/spl-memo';
5
5
  import BufferLayout from "buffer-layout";
6
6
  const publicKey=(property="publicKey")=>{return BufferLayout.blob(32,property);};const uint64=(property="uint64")=>{return BufferLayout.blob(8,property);}
7
- import {createSolanaRpcSubscriptions} from "@solana/kit";
7
+ import {createSolanaRpcSubscriptions, SOLANA_ERROR__ADDRESSES__FAILED_TO_FIND_VIABLE_PDA_BUMP_SEED} from "@solana/kit";
8
8
  import {EventEmitter} from 'events';
9
9
 
10
10
  const INSTRUCTIONS = {
@@ -13,6 +13,8 @@ const INSTRUCTIONS = {
13
13
  DRAW_WINNER: 2,
14
14
  CLAIM_PRIZE: 3,
15
15
  LOCK_LOTTERY: 4,
16
+ RELEASE_EXPIRED: 5,
17
+ BOOST_LOTTERY: 6,
16
18
  };
17
19
 
18
20
  class LotteryNetwork {
@@ -320,6 +322,9 @@ class Lottery extends EventEmitter {
320
322
  _tx_.encode = false;
321
323
  }
322
324
  const tx = await network.Tx(_tx_);
325
+ if(tx.logs && tx.logs.includes("Program log: Lottery is not active, no more tickets can be sold")){
326
+ return "Lottery is not active, no tickets can be sold";
327
+ }
323
328
  if(buyer.secretKey && !encoded){
324
329
  tx.transaction.sign([buyer]);
325
330
  const sig = await network.Send(tx.transaction);
@@ -496,12 +501,16 @@ class Lottery extends EventEmitter {
496
501
  offset += 8;
497
502
  return val;
498
503
  });
504
+
499
505
  // 6. winner_address: Option<Pubkey> (1 + 32 bytes)
500
- const winnerAddress = readOption(() => {
501
- const val = buffer.slice(offset, offset + 32);
502
- offset += 32;
503
- return val;
504
- });
506
+ let winnerAddress = null;
507
+ try{
508
+ winnerAddress = readOption(() => {
509
+ const val = buffer.slice(offset, offset + 32);
510
+ offset += 32;
511
+ return new PublicKey(val).toString();
512
+ });
513
+ }catch{}
505
514
  // 7. is_active: bool (1 byte)
506
515
  const isActive = buffer.readUInt8(offset) === 1;
507
516
  offset += 1;
@@ -510,6 +519,15 @@ class Lottery extends EventEmitter {
510
519
  offset += 8;
511
520
  const drawInitiated = buffer.readUInt8(offset) === 1;
512
521
  offset += 1;
522
+ // 9. draw_timestamp: Option<u64> (1 + 8 bytes)
523
+ let releaseTime = null;
524
+ try{
525
+ releaseTime = readOption(() => {
526
+ const val = buffer.readBigUInt64LE(offset);
527
+ offset += 8;
528
+ return Number(releaseTime);
529
+ });
530
+ }catch{}
513
531
  const prizePoolAddress = await this.DerivePrizePoolPDA();
514
532
  const lotteryAddress = await this.DeriveLotteryPDA(new PublicKey(auth), lotteryId);
515
533
  let prizePoolBalance = prizePool;
@@ -526,6 +544,8 @@ class Lottery extends EventEmitter {
526
544
  drawInitiated,
527
545
  prizePoolAddress: prizePoolAddress[0].toString(),
528
546
  lotteryAddress: lotteryAddress[0].toString(),
547
+ release: lotteryAddress[0].toString(),
548
+ releaseTime,
529
549
  };
530
550
  };
531
551
 
@@ -635,8 +655,7 @@ class LotteryManager {
635
655
  const keys = [
636
656
  { pubkey: authority.publicKey, isSigner: true, isWritable: false },
637
657
  { pubkey: lotteryPDA, isSigner: false, isWritable: true },
638
- { pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, isSigner: false, isWritable: false },
639
- { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
658
+ { pubkey: SYSVAR_SLOT_HASHES_PUBKEY, isSigner: false, isWritable: false },
640
659
  { pubkey: prizePoolPDA, isSigner: false, isWritable: true },
641
660
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
642
661
  ];
@@ -737,6 +756,140 @@ class LotteryManager {
737
756
  }
738
757
  }
739
758
 
759
+ /**
760
+ * @param {Keypair} authority - Keypair
761
+ * @param {String} lotteryId - The lottery id
762
+ * @param {Boolean} encoded - true returns encoded transaction
763
+ */
764
+ async ClaimExpired(authority, lotteryId, encoded = false) {
765
+ try{
766
+ async function expiredData() {
767
+ const buffer = Buffer.alloc(1); // 1 byte discriminator + 1 bytes lock status
768
+ buffer.writeUInt8(INSTRUCTIONS.RELEASE_EXPIRED, 0); // lock discriminator
769
+ return buffer;
770
+ }
771
+ const lottery = new Lottery(this.connection, false, this.program);
772
+ const network = new LotteryNetwork(this.connection);
773
+ const [lotteryPDA] = await lottery.DeriveLotteryPDA(authority.publicKey, lotteryId);
774
+ const LOTTO = await lottery.GetLottery(authority, lotteryId);
775
+ const keys = [
776
+ { pubkey: authority.publicKey, isSigner: true, isWritable: true },
777
+ { pubkey: lotteryPDA, isSigner: false, isWritable: true },
778
+ { pubkey: new PublicKey(LOTTO.prizePoolAddress), isSigner: false, isWritable: true },
779
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
780
+ ];
781
+ const ix = new TransactionInstruction({programId: this.program, keys, data: await expiredData()});
782
+ const _tx_ = {};
783
+ _tx_.account = authority.publicKey.toString(); // string : required
784
+ _tx_.instructions = [ix]; // array : required
785
+ _tx_.signers = false; // array : default false
786
+ _tx_.table = false; // array : default false
787
+ _tx_.tolerance = 1.2; // int : default 1.1
788
+ _tx_.compute = true; // bool : default true
789
+ _tx_.fees = true; // bool : default true
790
+ _tx_.priority = "Low"; // string : default Low
791
+ _tx_.memo = false;
792
+ if(encoded){
793
+ _tx_.serialize = true;
794
+ _tx_.encode = true;
795
+ }
796
+ else{
797
+ _tx_.serialize = false;
798
+ _tx_.encode = false;
799
+ }
800
+ const tx = await network.Tx(_tx_);
801
+ //
802
+ if(tx.logs && tx.logs.includes("Program log: Prize has already been claimed")){
803
+ return "Prize has already been claimed";
804
+ }
805
+ if(tx.status !== "ok"){return tx;}
806
+ if(authority.secretKey && !encoded){
807
+ tx.transaction.sign([authority]);
808
+ const sig = await network.Send(tx.transaction);
809
+ console.log("Signature:", sig);
810
+ const status = await network.Status(sig);
811
+ if(status == "finalized"){
812
+ return await lottery.GetLottery({publicKey: authority.publicKey}, lotteryId, false);
813
+ }
814
+ else{return status;}
815
+ }
816
+ else{return tx;}
817
+ }
818
+ catch (error) {
819
+ console.log(error);
820
+ }
821
+ }
822
+
823
+ /**
824
+ * @param {Keypair} authority - Keypair
825
+ * @param {String} lotteryId - The lottery id
826
+ * @param {Keypair} booster - The booster's keypair
827
+ * @param {Number} amount - The amount of sol to boost
828
+ * @param {Boolean} encoded - true returns encoded transaction
829
+ */
830
+ async Boost(authority, lotteryId, booster, amount, message = false, encoded = false) {
831
+ try{
832
+ async function boostData(lotId, amount) {
833
+ const lamports = parseInt(amount * LAMPORTS_PER_SOL);
834
+ const buffer = Buffer.alloc(17); // 1 byte discriminator + 1 bytes price + 8 bytes id
835
+ buffer.writeUInt8(INSTRUCTIONS.BOOST_LOTTERY, 0); // boostLottery discriminator
836
+ buffer.writeBigUInt64LE(BigInt(lotId), 1);
837
+ buffer.writeBigUInt64LE(BigInt(lamports), 9);
838
+ return buffer;
839
+ }
840
+ const lottery = new Lottery(this.connection, false, this.program);
841
+ const network = new LotteryNetwork(this.connection);
842
+ const [lotteryPDA] = await lottery.DeriveLotteryPDA(authority.publicKey, lotteryId);
843
+ const LOTTO = await lottery.GetLottery(authority, lotteryId);
844
+ const keys = [
845
+ { pubkey: booster.publicKey, isSigner: true, isWritable: true },
846
+ { pubkey: lotteryPDA, isSigner: false, isWritable: true },
847
+ { pubkey: new PublicKey(LOTTO.prizePoolAddress), isSigner: false, isWritable: true },
848
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
849
+ ];
850
+ const ix = new TransactionInstruction(
851
+ {programId: this.program, keys, data: await boostData(lotteryId, amount)}
852
+ );
853
+ const _tx_ = {};
854
+ _tx_.account = booster.publicKey.toString(); // string : required
855
+ _tx_.instructions = [ix]; // array : required
856
+ _tx_.signers = false; // array : default false
857
+ _tx_.table = false; // array : default false
858
+ _tx_.tolerance = 1.2; // int : default 1.1
859
+ _tx_.compute = true; // bool : default true
860
+ _tx_.fees = true; // bool : default true
861
+ _tx_.priority = "Low"; // string : default Low
862
+ _tx_.memo = message;
863
+ if(encoded){
864
+ _tx_.serialize = true;
865
+ _tx_.encode = true;
866
+ }
867
+ else{
868
+ _tx_.serialize = false;
869
+ _tx_.encode = false;
870
+ }
871
+ const tx = await network.Tx(_tx_);
872
+ if(tx.logs && tx.logs.includes("Program log: Lottery draw has been initiated, cannot boost prize pool")){
873
+ return "Draw initiated, cannot boost this prize pool";
874
+ }
875
+ if(tx.status !== "ok"){return tx;}
876
+ if(booster.secretKey && !encoded){
877
+ tx.transaction.sign([booster]);
878
+ const sig = await network.Send(tx.transaction);
879
+ console.log("Signature:", sig);
880
+ const status = await network.Status(sig);
881
+ if(status == "finalized"){
882
+ return "boosted";
883
+ }
884
+ else{return status;}
885
+ }
886
+ else{return tx;}
887
+ }
888
+ catch (error) {
889
+ console.log(error);
890
+ }
891
+ }
892
+
740
893
  }
741
894
 
742
895
  export {