sui-game-sdk 0.0.1
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 +163 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +79 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +19 -0
- package/dist/session.d.ts +23 -0
- package/dist/session.js +47 -0
- package/dist/spawner.d.ts +19 -0
- package/dist/spawner.js +37 -0
- package/dist/sync.d.ts +20 -0
- package/dist/sync.js +55 -0
- package/package.json +46 -0
- package/starter/README.md +57 -0
- package/starter/client/index.ts +79 -0
- package/starter/move/Move.toml +9 -0
- package/starter/move/sources/hero.move +109 -0
- package/starter/scripts/deploy.ts +37 -0
- package/starters/godot/README.md +71 -0
- package/starters/godot/deploy.js +39 -0
- package/starters/godot/move/Move.toml +9 -0
- package/starters/godot/move/sources/hero.move +109 -0
- package/starters/godot/project.godot +29 -0
- package/starters/godot/scenes/main.tscn +22 -0
- package/starters/godot/scripts/game_manager.gd +14 -0
- package/starters/godot/scripts/player.gd +10 -0
- package/starters/godot/scripts/sui/hero_controller.gd +56 -0
- package/starters/godot/scripts/sui/sui_service.gd +62 -0
- package/starters/nextjs/.env.local.example +2 -0
- package/starters/nextjs/README.md +110 -0
- package/starters/nextjs/app/globals.css +3 -0
- package/starters/nextjs/app/layout.tsx +22 -0
- package/starters/nextjs/app/page.tsx +5 -0
- package/starters/nextjs/components/HeroGame.tsx +161 -0
- package/starters/nextjs/lib/sui-client.ts +22 -0
- package/starters/nextjs/move/Move.toml +9 -0
- package/starters/nextjs/move/sources/hero.move +109 -0
- package/starters/nextjs/next.config.js +4 -0
- package/starters/nextjs/package.json +30 -0
- package/starters/nextjs/postcss.config.js +6 -0
- package/starters/nextjs/scripts/deploy.ts +42 -0
- package/starters/nextjs/tailwind.config.js +12 -0
- package/starters/nextjs/tsconfig.json +39 -0
- package/starters/unity/Assets/Scenes/Main.unity +197 -0
- package/starters/unity/Assets/Scripts/Core/GameManager.cs +38 -0
- package/starters/unity/Assets/Scripts/Core/PlayerController.cs +37 -0
- package/starters/unity/Assets/Scripts/Sui/HeroManager.cs +99 -0
- package/starters/unity/Assets/Scripts/Sui/SuiManager.cs +94 -0
- package/starters/unity/README.md +72 -0
- package/starters/unity/deploy.js +39 -0
- package/starters/unity/move/Move.toml +9 -0
- package/starters/unity/move/sources/hero.move +109 -0
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# SUI Game SDK
|
|
2
|
+
|
|
3
|
+
The ultimate SDK for building games on SUI. Includes Session Keys, Asset Spawning, and Game State Synchronization.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### 1. Session Manager
|
|
8
|
+
Eliminate wallet popups during gameplay.
|
|
9
|
+
- Generate ephemeral Ed25519 keys locally
|
|
10
|
+
- Export/Import session state
|
|
11
|
+
- Sign transactions seamlessly
|
|
12
|
+
|
|
13
|
+
### 2. Asset Spawner
|
|
14
|
+
Simplified API for minting game objects using Move calls.
|
|
15
|
+
- Fluent `spawn(recipient, attributes)` API
|
|
16
|
+
- Auto-formats vector arguments for attributes
|
|
17
|
+
|
|
18
|
+
### 3. Game State Sync
|
|
19
|
+
React to on-chain game events in real-time.
|
|
20
|
+
- Poll for specific Move events
|
|
21
|
+
- Subscription-based handler pattern
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install sui-game-sdk
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { SessionManager, AssetSpawner, GameStateSync } from 'sui-game-sdk';
|
|
33
|
+
import { SuiClient } from '@mysten/sui/client';
|
|
34
|
+
|
|
35
|
+
const client = new SuiClient({ url: 'https://fullnode.testnet.sui.io' });
|
|
36
|
+
|
|
37
|
+
// 1. Create Session
|
|
38
|
+
const session = new SessionManager(client);
|
|
39
|
+
console.log('Session Address:', session.sessionAddress);
|
|
40
|
+
|
|
41
|
+
// 2. Spawn Asset
|
|
42
|
+
const spawner = new AssetSpawner(client, {
|
|
43
|
+
packageId: '0x...',
|
|
44
|
+
module: 'hero',
|
|
45
|
+
function: 'mint_hero',
|
|
46
|
+
adminCapId: '0x...'
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// 3. Listen to Events
|
|
50
|
+
const sync = new GameStateSync(client);
|
|
51
|
+
sync.subscribe('0x...::hero::LevelUpEvent', (event) => {
|
|
52
|
+
console.log('Level Up!', event.parsedJson);
|
|
53
|
+
});
|
|
54
|
+
sync.startPolling('0x...');
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Starter Templates
|
|
58
|
+
|
|
59
|
+
Kickstart your game development with our ready-to-use starter templates:
|
|
60
|
+
|
|
61
|
+
### Generate a Starter
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx sui-game-sdk init
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
You'll be prompted to choose from:
|
|
68
|
+
|
|
69
|
+
#### 1. **TypeScript (Web/Node)**
|
|
70
|
+
Full-featured starter with all SDK components. Perfect for web games or backend services.
|
|
71
|
+
- Uses SessionManager, AssetSpawner, GameStateSync
|
|
72
|
+
- Includes sample "Sui Quest" game
|
|
73
|
+
- Move contract included
|
|
74
|
+
|
|
75
|
+
#### 2. **Next.js (Web App)**
|
|
76
|
+
Modern React web application with beautiful UI.
|
|
77
|
+
- Direct SDK integration
|
|
78
|
+
- Real-time event streaming
|
|
79
|
+
- Tailwind CSS + glassmorphic design
|
|
80
|
+
- Production-ready structure
|
|
81
|
+
|
|
82
|
+
#### 3. **Unity (C# Scripts)**
|
|
83
|
+
Game engine integration for Unity developers.
|
|
84
|
+
- Simulates SDK patterns in C#
|
|
85
|
+
- HTTP/RPC blockchain communication
|
|
86
|
+
- Organized Assets folder structure
|
|
87
|
+
- Compatible with Unity 2021.3+
|
|
88
|
+
|
|
89
|
+
#### 4. **Godot (GDScript)**
|
|
90
|
+
Game engine integration for Godot developers.
|
|
91
|
+
- Simulates SDK patterns in GDScript
|
|
92
|
+
- HTTP/RPC blockchain communication
|
|
93
|
+
- Standard Godot 4 project structure
|
|
94
|
+
- Signals for event handling
|
|
95
|
+
|
|
96
|
+
### Starter Features
|
|
97
|
+
|
|
98
|
+
All starters include:
|
|
99
|
+
- ā
Sample "Sui Quest" game (Mint Hero, Slay Boar, Level Up)
|
|
100
|
+
- ā
Move smart contract (`hero.move`)
|
|
101
|
+
- ā
Deployment scripts
|
|
102
|
+
- ā
Comprehensive README
|
|
103
|
+
- ā
SDK integration examples
|
|
104
|
+
|
|
105
|
+
### How Starters Use the SDK
|
|
106
|
+
|
|
107
|
+
- **TypeScript & Next.js**: Direct SDK imports (`import { SessionManager } from 'sui-game-sdk'`)
|
|
108
|
+
- **Unity & Godot**: Simulate SDK concepts using native languages (C#/GDScript)
|
|
109
|
+
|
|
110
|
+
For detailed SDK usage comparison, see [SDK_USAGE.md](./SDK_USAGE.md)
|
|
111
|
+
|
|
112
|
+
## Documentation
|
|
113
|
+
|
|
114
|
+
- [SDK Usage Guide](./SDK_USAGE.md) - How each starter uses the SDK
|
|
115
|
+
- [TypeScript Starter](./starter/README.md)
|
|
116
|
+
- [Next.js Starter](./starters/nextjs/README.md)
|
|
117
|
+
- [Unity Starter](./starters/unity/README.md)
|
|
118
|
+
- [Godot Starter](./starters/godot/README.md)
|
|
119
|
+
|
|
120
|
+
## Example: Building a Simple RPG
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { SessionManager, GameStateSync } from 'sui-game-sdk';
|
|
124
|
+
import { Transaction } from '@mysten/sui/transactions';
|
|
125
|
+
|
|
126
|
+
// Setup
|
|
127
|
+
const client = new SuiClient({ url: 'https://fullnode.testnet.sui.io' });
|
|
128
|
+
const session = new SessionManager(client);
|
|
129
|
+
|
|
130
|
+
// Execute game action
|
|
131
|
+
const tx = new Transaction();
|
|
132
|
+
tx.moveCall({
|
|
133
|
+
target: `${PACKAGE_ID}::hero::slay_boar`,
|
|
134
|
+
arguments: [tx.object(heroId)]
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const result = await session.executeSessionTx(tx);
|
|
138
|
+
console.log('Action completed:', result.digest);
|
|
139
|
+
|
|
140
|
+
// Listen for results
|
|
141
|
+
const sync = new GameStateSync(client);
|
|
142
|
+
sync.subscribe(`${PACKAGE_ID}::hero::AttackEvent`, (event) => {
|
|
143
|
+
console.log('Damage dealt:', event.parsedJson.damage_dealt);
|
|
144
|
+
});
|
|
145
|
+
sync.startPolling(PACKAGE_ID);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Development
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# Install dependencies
|
|
152
|
+
npm install
|
|
153
|
+
|
|
154
|
+
# Build
|
|
155
|
+
npm run build
|
|
156
|
+
|
|
157
|
+
# Run tests
|
|
158
|
+
npm test
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT
|
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
9
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const program = new commander_1.Command();
|
|
12
|
+
program
|
|
13
|
+
.name('sui-game-sdk')
|
|
14
|
+
.description('CLI to scaffold Sui Game projects')
|
|
15
|
+
.version('0.0.1');
|
|
16
|
+
program
|
|
17
|
+
.command('init')
|
|
18
|
+
.description('Initialize a new game project')
|
|
19
|
+
.action(async () => {
|
|
20
|
+
const response = await (0, prompts_1.default)([
|
|
21
|
+
{
|
|
22
|
+
type: 'text',
|
|
23
|
+
name: 'projectName',
|
|
24
|
+
message: 'What is the name of your project?',
|
|
25
|
+
initial: 'my-sui-game'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: 'select',
|
|
29
|
+
name: 'template',
|
|
30
|
+
message: 'Which starter template would you like?',
|
|
31
|
+
choices: [
|
|
32
|
+
{ title: 'TypeScript (Web/Node)', value: 'starter' },
|
|
33
|
+
{ title: 'Next.js (Web App)', value: 'nextjs' },
|
|
34
|
+
{ title: 'Unity (C# Scripts)', value: 'unity' },
|
|
35
|
+
{ title: 'Godot (GDScript)', value: 'godot' }
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
]);
|
|
39
|
+
if (!response.template || !response.projectName)
|
|
40
|
+
return;
|
|
41
|
+
const targetDir = path_1.default.join(process.cwd(), response.projectName);
|
|
42
|
+
const templateDir = path_1.default.resolve(__dirname, '../starters', response.template === 'starter' ? '../starter' : response.template); // Fix path logic based on structure
|
|
43
|
+
// In installed package: dist/bin/cli.js -> __dirname is dist/bin
|
|
44
|
+
// We need to go up two levels to find 'starter' and 'starters' at root
|
|
45
|
+
let sourcePath = '';
|
|
46
|
+
if (response.template === 'starter') {
|
|
47
|
+
sourcePath = path_1.default.resolve(__dirname, '../../starter');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
sourcePath = path_1.default.resolve(__dirname, '../../starters', response.template);
|
|
51
|
+
}
|
|
52
|
+
if (fs_extra_1.default.existsSync(targetDir)) {
|
|
53
|
+
console.error(`Error: Directory ${targetDir} already exists.`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
console.log(`\nCreating project in ${targetDir}...`);
|
|
57
|
+
try {
|
|
58
|
+
await fs_extra_1.default.copy(sourcePath, targetDir);
|
|
59
|
+
console.log('\nSuccess! Created project.');
|
|
60
|
+
console.log(`\ncd ${response.projectName}`);
|
|
61
|
+
if (response.template === 'starter') {
|
|
62
|
+
console.log('npm install');
|
|
63
|
+
console.log('npm run deploy');
|
|
64
|
+
}
|
|
65
|
+
else if (response.template === 'nextjs') {
|
|
66
|
+
console.log('npm install');
|
|
67
|
+
console.log('cp .env.local.example .env.local');
|
|
68
|
+
console.log('# Edit .env.local with your package ID');
|
|
69
|
+
console.log('npm run dev');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.log('Follow the README.md instructions to integrate with your engine.');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
console.error('Error creating project:', e);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
program.parse();
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./session"), exports);
|
|
18
|
+
__exportStar(require("./spawner"), exports);
|
|
19
|
+
__exportStar(require("./sync"), exports);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SuiClient } from '@mysten/sui/client';
|
|
2
|
+
import { Transaction } from '@mysten/sui/transactions';
|
|
3
|
+
export declare class SessionManager {
|
|
4
|
+
private ephemeralKeyPair;
|
|
5
|
+
private client;
|
|
6
|
+
sessionAddress: string;
|
|
7
|
+
constructor(client: SuiClient, existingSecretKey?: string);
|
|
8
|
+
/**
|
|
9
|
+
* Gets the public key of the session (ephemeral) wallet.
|
|
10
|
+
* The main wallet should authorize this key to sign transactions on its behalf
|
|
11
|
+
* (e.g. via Sponsored Transactions or a Smart Account capability).
|
|
12
|
+
*/
|
|
13
|
+
getPublicKey(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Export the session key to save local state (e.g. localStorage).
|
|
16
|
+
*/
|
|
17
|
+
exportSession(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Signs and Executes a transaction using the session key.
|
|
20
|
+
* Note: This usually requires the transaction to be Sponsored or the Session Key to handle gas.
|
|
21
|
+
*/
|
|
22
|
+
executeSessionTx(tx: Transaction): Promise<import("@mysten/sui/client").SuiTransactionBlockResponse>;
|
|
23
|
+
}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SessionManager = void 0;
|
|
4
|
+
const ed25519_1 = require("@mysten/sui/keypairs/ed25519");
|
|
5
|
+
class SessionManager {
|
|
6
|
+
constructor(client, existingSecretKey) {
|
|
7
|
+
this.client = client;
|
|
8
|
+
if (existingSecretKey) {
|
|
9
|
+
this.ephemeralKeyPair = ed25519_1.Ed25519Keypair.fromSecretKey(existingSecretKey);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
this.ephemeralKeyPair = new ed25519_1.Ed25519Keypair();
|
|
13
|
+
}
|
|
14
|
+
this.sessionAddress = this.ephemeralKeyPair.toSuiAddress();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Gets the public key of the session (ephemeral) wallet.
|
|
18
|
+
* The main wallet should authorize this key to sign transactions on its behalf
|
|
19
|
+
* (e.g. via Sponsored Transactions or a Smart Account capability).
|
|
20
|
+
*/
|
|
21
|
+
getPublicKey() {
|
|
22
|
+
return this.ephemeralKeyPair.getPublicKey().toBase64();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Export the session key to save local state (e.g. localStorage).
|
|
26
|
+
*/
|
|
27
|
+
exportSession() {
|
|
28
|
+
return this.ephemeralKeyPair.getSecretKey();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Signs and Executes a transaction using the session key.
|
|
32
|
+
* Note: This usually requires the transaction to be Sponsored or the Session Key to handle gas.
|
|
33
|
+
*/
|
|
34
|
+
async executeSessionTx(tx) {
|
|
35
|
+
tx.setSender(this.sessionAddress);
|
|
36
|
+
const { bytes, signature } = await tx.sign({ client: this.client, signer: this.ephemeralKeyPair });
|
|
37
|
+
return this.client.executeTransactionBlock({
|
|
38
|
+
transactionBlock: bytes,
|
|
39
|
+
signature: signature,
|
|
40
|
+
options: {
|
|
41
|
+
showEffects: true,
|
|
42
|
+
showEvents: true
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.SessionManager = SessionManager;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Transaction } from '@mysten/sui/transactions';
|
|
2
|
+
import { SuiClient } from '@mysten/sui/client';
|
|
3
|
+
export type SpawnConfig = {
|
|
4
|
+
packageId: string;
|
|
5
|
+
module: string;
|
|
6
|
+
function: string;
|
|
7
|
+
adminCapId: string;
|
|
8
|
+
};
|
|
9
|
+
export declare class AssetSpawner {
|
|
10
|
+
private client;
|
|
11
|
+
private config;
|
|
12
|
+
constructor(client: SuiClient, config: SpawnConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Creates a transaction to spawn a new Game Asset (NFT).
|
|
15
|
+
* @param recipient The player address receiving the asset
|
|
16
|
+
* @param attributes Optional map of attributes to set immediately (if contract supports it)
|
|
17
|
+
*/
|
|
18
|
+
spawn(recipient: string, attributes?: Record<string, any>): Transaction;
|
|
19
|
+
}
|
package/dist/spawner.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AssetSpawner = void 0;
|
|
4
|
+
const transactions_1 = require("@mysten/sui/transactions");
|
|
5
|
+
class AssetSpawner {
|
|
6
|
+
constructor(client, config) {
|
|
7
|
+
this.client = client;
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Creates a transaction to spawn a new Game Asset (NFT).
|
|
12
|
+
* @param recipient The player address receiving the asset
|
|
13
|
+
* @param attributes Optional map of attributes to set immediately (if contract supports it)
|
|
14
|
+
*/
|
|
15
|
+
spawn(recipient, attributes) {
|
|
16
|
+
const tx = new transactions_1.Transaction();
|
|
17
|
+
// This assumes a standard mint function signature:
|
|
18
|
+
// mint(admin_cap, recipient, attributes_keys, attributes_values)
|
|
19
|
+
// or similar. This is a generic implementation.
|
|
20
|
+
const args = [
|
|
21
|
+
tx.object(this.config.adminCapId),
|
|
22
|
+
tx.pure.address(recipient)
|
|
23
|
+
];
|
|
24
|
+
if (attributes) {
|
|
25
|
+
const keys = Object.keys(attributes);
|
|
26
|
+
const values = Object.values(attributes).map(String);
|
|
27
|
+
args.push(tx.pure.vector('string', keys));
|
|
28
|
+
args.push(tx.pure.vector('string', values));
|
|
29
|
+
}
|
|
30
|
+
tx.moveCall({
|
|
31
|
+
target: `${this.config.packageId}::${this.config.module}::${this.config.function}`,
|
|
32
|
+
arguments: args
|
|
33
|
+
});
|
|
34
|
+
return tx;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.AssetSpawner = AssetSpawner;
|
package/dist/sync.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SuiClient, SuiEvent } from '@mysten/sui/client';
|
|
2
|
+
export type GameEventHandler = (event: SuiEvent) => void;
|
|
3
|
+
export declare class GameStateSync {
|
|
4
|
+
private client;
|
|
5
|
+
private pollingInterval;
|
|
6
|
+
private handlers;
|
|
7
|
+
private lastTxDigest;
|
|
8
|
+
constructor(client: SuiClient);
|
|
9
|
+
/**
|
|
10
|
+
* Subscribe to a specific Move Event type (e.g. "0x...::game::AttackEvent")
|
|
11
|
+
*/
|
|
12
|
+
subscribe(eventType: string, handler: GameEventHandler): void;
|
|
13
|
+
/**
|
|
14
|
+
* Starts polling for events.
|
|
15
|
+
* @param packageId The package to filter events by
|
|
16
|
+
* @param intervalMs Polling interval in milliseconds
|
|
17
|
+
*/
|
|
18
|
+
startPolling(packageId: string, intervalMs?: number): Promise<void>;
|
|
19
|
+
stopPolling(): void;
|
|
20
|
+
}
|
package/dist/sync.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GameStateSync = void 0;
|
|
4
|
+
class GameStateSync {
|
|
5
|
+
constructor(client) {
|
|
6
|
+
this.pollingInterval = null;
|
|
7
|
+
this.handlers = new Map();
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Subscribe to a specific Move Event type (e.g. "0x...::game::AttackEvent")
|
|
12
|
+
*/
|
|
13
|
+
subscribe(eventType, handler) {
|
|
14
|
+
if (!this.handlers.has(eventType)) {
|
|
15
|
+
this.handlers.set(eventType, []);
|
|
16
|
+
}
|
|
17
|
+
this.handlers.get(eventType)?.push(handler);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Starts polling for events.
|
|
21
|
+
* @param packageId The package to filter events by
|
|
22
|
+
* @param intervalMs Polling interval in milliseconds
|
|
23
|
+
*/
|
|
24
|
+
async startPolling(packageId, intervalMs = 2000) {
|
|
25
|
+
if (this.pollingInterval)
|
|
26
|
+
return;
|
|
27
|
+
this.pollingInterval = setInterval(async () => {
|
|
28
|
+
try {
|
|
29
|
+
// Fetch events
|
|
30
|
+
const events = await this.client.queryEvents({
|
|
31
|
+
query: { MoveModule: { package: packageId, module: 'game' } }, // simplified query
|
|
32
|
+
limit: 10,
|
|
33
|
+
cursor: this.lastTxDigest ? { txDigest: this.lastTxDigest, eventSeq: '0' } : undefined
|
|
34
|
+
});
|
|
35
|
+
for (const event of events.data) {
|
|
36
|
+
const handlers = this.handlers.get(event.type);
|
|
37
|
+
if (handlers) {
|
|
38
|
+
handlers.forEach(h => h(event));
|
|
39
|
+
}
|
|
40
|
+
this.lastTxDigest = event.id.txDigest;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
console.error('Error polling game state:', e);
|
|
45
|
+
}
|
|
46
|
+
}, intervalMs);
|
|
47
|
+
}
|
|
48
|
+
stopPolling() {
|
|
49
|
+
if (this.pollingInterval) {
|
|
50
|
+
clearInterval(this.pollingInterval);
|
|
51
|
+
this.pollingInterval = null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.GameStateSync = GameStateSync;
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sui-game-sdk",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "The ultimate SDK for building games on SUI. Session Keys, Spawning, & Sync.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sui-game-sdk": "./dist/bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"starter",
|
|
13
|
+
"starters",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "rm -rf dist && tsc",
|
|
18
|
+
"test": "vitest",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"sui",
|
|
23
|
+
"game",
|
|
24
|
+
"sdk",
|
|
25
|
+
"move",
|
|
26
|
+
"blockchain",
|
|
27
|
+
"gaming"
|
|
28
|
+
],
|
|
29
|
+
"author": "dotandev",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@mysten/bcs": "^1.1.0",
|
|
33
|
+
"@mysten/sui": "^1.21.1",
|
|
34
|
+
"commander": "^14.0.2",
|
|
35
|
+
"fs-extra": "^11.3.3",
|
|
36
|
+
"prompts": "^2.4.2"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/fs-extra": "^11.0.4",
|
|
40
|
+
"@types/node": "^22.10.7",
|
|
41
|
+
"@types/prompts": "^2.4.9",
|
|
42
|
+
"ts-node": "^10.9.2",
|
|
43
|
+
"typescript": "^5.7.3",
|
|
44
|
+
"vitest": "^3.0.4"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# SUI Game SDK - TypeScript Starter
|
|
2
|
+
|
|
3
|
+
The reference implementation for the `sui-game-sdk`. This starter project demonstrates the full capabilities of the SDK including Session Management, Asset Spawning, and Game State Synchronization.
|
|
4
|
+
|
|
5
|
+
## š¦ Features
|
|
6
|
+
- **Session Manager**: Automated session key creation and management.
|
|
7
|
+
- **Asset Spawner**: Streamlined asset creation transactions.
|
|
8
|
+
- **Game State Sync**: Real-time event listening and object syncing.
|
|
9
|
+
- **Sui Quest**: A complete sample game logic (Mint Hero, Slay Boar).
|
|
10
|
+
|
|
11
|
+
## š Quick Start
|
|
12
|
+
|
|
13
|
+
### 1. Install Dependencies
|
|
14
|
+
```bash
|
|
15
|
+
npm install
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### 2. Deploy the Game Contract
|
|
19
|
+
This starter requires the `sui_quest` Move package to be published.
|
|
20
|
+
```bash
|
|
21
|
+
# Deploys the contract in 'move/' to Testnet (or localnet if configured)
|
|
22
|
+
npm run deploy
|
|
23
|
+
```
|
|
24
|
+
> **Important**: The deploy script will update `client/index.ts` automatically (or print the ID you need to set).
|
|
25
|
+
|
|
26
|
+
### 3. Run the Client
|
|
27
|
+
Start the game client simulation:
|
|
28
|
+
```bash
|
|
29
|
+
npm start
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## š® How it Works
|
|
33
|
+
|
|
34
|
+
### Session
|
|
35
|
+
The `SessionManager` creates a temporary wallet for the player.
|
|
36
|
+
```typescript
|
|
37
|
+
const session = new SessionManager(client);
|
|
38
|
+
console.log(session.sessionAddress); // fund this!
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Spawning
|
|
42
|
+
The `AssetSpawner` handles complex Move calls to create assets.
|
|
43
|
+
```typescript
|
|
44
|
+
// (Conceptual usage, check specific implementation)
|
|
45
|
+
spawner.spawn(playerAddress, { ... });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Syncing
|
|
49
|
+
The `GameStateSync` listens for on-chain events to update the game UI.
|
|
50
|
+
```typescript
|
|
51
|
+
syncer.subscribe('AttackEvent', (event) => {
|
|
52
|
+
console.log('Damage:', event.damage);
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## š License
|
|
57
|
+
MIT
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { SuiClient } from '@mysten/sui/client';
|
|
2
|
+
import { SessionManager, AssetSpawner, GameStateSync } from '../../src'; // Import from local SDK
|
|
3
|
+
import { Transaction } from '@mysten/sui/transactions';
|
|
4
|
+
|
|
5
|
+
// API: Replace these after running `npm run deploy`
|
|
6
|
+
const PACKAGE_ID = process.env.GAME_PACKAGE_ID || '0x_REPLACE_WITH_PACKAGE_ID';
|
|
7
|
+
const GAME_ADMIN_CAP = process.env.GAME_ADMIN_CAP || '0x_REPLACE_WITH_ADMIN_CAP'; // Found in publish output
|
|
8
|
+
|
|
9
|
+
const MODULE = 'hero';
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
console.log('--- SUI QUEST CLIENT ---');
|
|
13
|
+
|
|
14
|
+
const client = new SuiClient({ url: 'https://fullnode.testnet.sui.io' });
|
|
15
|
+
|
|
16
|
+
// 1. Account Setup (Session)
|
|
17
|
+
// In a real app, we'd take a private key from env or wallet adapter
|
|
18
|
+
const session = new SessionManager(client);
|
|
19
|
+
console.log(`Player Session Address: ${session.sessionAddress}`);
|
|
20
|
+
console.log('Fund this address with Testnet SUI to play!');
|
|
21
|
+
|
|
22
|
+
// 2. Listen to the Game
|
|
23
|
+
const syncer = new GameStateSync(client);
|
|
24
|
+
console.log(`\nSubscribing to events from package: ${PACKAGE_ID}...`);
|
|
25
|
+
|
|
26
|
+
syncer.subscribe(`${PACKAGE_ID}::${MODULE}::AttackEvent`, (event) => {
|
|
27
|
+
const data = event.parsedJson as any;
|
|
28
|
+
console.log(`āļø Hero dealt ${data.damage_dealt} damage!`);
|
|
29
|
+
});
|
|
30
|
+
syncer.subscribe(`${PACKAGE_ID}::${MODULE}::LevelUpEvent`, (event) => {
|
|
31
|
+
const data = event.parsedJson as any;
|
|
32
|
+
console.log(`š LEVEL UP! New Level: ${data.new_level}`);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
console.log('Polling for events...');
|
|
36
|
+
syncer.startPolling(PACKAGE_ID);
|
|
37
|
+
|
|
38
|
+
// 3. Gameplay Loop (Simulation)
|
|
39
|
+
if (PACKAGE_ID.startsWith('0x_REPLACE')) {
|
|
40
|
+
console.warn('\nā ļø Please deploy the contract first and set game package ID in client/index.ts');
|
|
41
|
+
console.warn('Run: npm run deploy');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Demo: Simulation of Action
|
|
46
|
+
console.log('\n[Simulating "Slay Boar" Action...]');
|
|
47
|
+
console.log('Note: This will fail if the session wallet has no gas or no Hero object.');
|
|
48
|
+
|
|
49
|
+
// In a real game, you would find the Hero object ID owned by the session
|
|
50
|
+
try {
|
|
51
|
+
const heroes = await client.getOwnedObjects({
|
|
52
|
+
owner: session.sessionAddress,
|
|
53
|
+
options: { showType: true, showContent: true }
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const heroObj = heroes.data.find(d => d.data?.type === `${PACKAGE_ID}::${MODULE}::Hero`);
|
|
57
|
+
|
|
58
|
+
if (heroObj) {
|
|
59
|
+
const heroId = heroObj.data?.objectId;
|
|
60
|
+
console.log(`Found Hero: ${heroId}. Attempting to Slay Boar...`);
|
|
61
|
+
|
|
62
|
+
const tx = new Transaction();
|
|
63
|
+
tx.moveCall({
|
|
64
|
+
target: `${PACKAGE_ID}::${MODULE}::slay_boar`,
|
|
65
|
+
arguments: [tx.object(heroId!)]
|
|
66
|
+
});
|
|
67
|
+
const res = await session.executeSessionTx(tx);
|
|
68
|
+
console.log('Action Executed! Digest:', res.digest);
|
|
69
|
+
} else {
|
|
70
|
+
console.log('No Hero found. You need to mint one first (requires Admin Cap or open minting).');
|
|
71
|
+
// Mock minting call if we had one open
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.error('Error during simulation:', e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main().catch(console.error);
|