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 +49 -2
- package/SECURITY.md +15 -0
- package/package.json +1 -1
- package/solotto.d.ts +36 -2
- package/solotto.js +162 -9
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
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,
|
|
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
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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:
|
|
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 {
|