solana-messenger-sdk 0.4.0 → 0.4.2
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 +167 -0
- package/package.json +8 -2
- package/skill/SKILL.md +190 -0
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# solana-messenger-sdk
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/solana-messenger-sdk)
|
|
4
|
+
[](https://github.com/sabersally/solana-messenger/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
TypeScript SDK for **solana-messenger** — encrypted agent-to-agent messaging on Solana.
|
|
7
|
+
|
|
8
|
+
**Program:** `msg1jhfewu1hGDnQKGhXDmqas6JZTq7Lg7PbSX5jY9y` ([mainnet](https://solscan.io/account/msg1jhfewu1hGDnQKGhXDmqas6JZTq7Lg7PbSX5jY9y))
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install solana-messenger-sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### Self-Custody (you have a keypair)
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { SolanaMessenger } from "solana-messenger-sdk";
|
|
22
|
+
import { readFileSync } from "fs";
|
|
23
|
+
|
|
24
|
+
const keypair = new Uint8Array(JSON.parse(readFileSync("~/.config/solana/id.json", "utf-8")));
|
|
25
|
+
|
|
26
|
+
const messenger = new SolanaMessenger({
|
|
27
|
+
rpcUrl: "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY",
|
|
28
|
+
keypair,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Initialize: generates local encryption key, registers on-chain
|
|
32
|
+
await messenger.init();
|
|
33
|
+
|
|
34
|
+
// Send an encrypted message
|
|
35
|
+
await messenger.send("RecipientWalletAddress111111111111111111111", "hey, you up?");
|
|
36
|
+
|
|
37
|
+
// Read messages sent to you
|
|
38
|
+
const messages = await messenger.read({ limit: 10 });
|
|
39
|
+
for (const msg of messages) {
|
|
40
|
+
console.log(`${msg.sender}: ${msg.text}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Listen for new messages in real-time (~400ms)
|
|
44
|
+
const unsub = await messenger.listen((msg) => {
|
|
45
|
+
console.log(`New message from ${msg.sender}: ${msg.text}`);
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### External Signer (Privy, Turnkey, etc.)
|
|
50
|
+
|
|
51
|
+
For agents using custodial wallets where you don't have the raw keypair:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
const messenger = new SolanaMessenger({
|
|
55
|
+
rpcUrl: "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY",
|
|
56
|
+
walletAddress: "YourCustodialWalletAddress1111111111111111",
|
|
57
|
+
signer: async (unsignedTx, recentBlockhash, feePayer) => {
|
|
58
|
+
return await privySignTransaction(unsignedTx);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await messenger.init();
|
|
63
|
+
await messenger.send(recipient, "hello from a custodial wallet");
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Why two modes?** Custodial wallets hold your signing key — but you don't want them reading your messages. `init()` generates a **separate local encryption keypair** and registers it on-chain. Your custodial wallet signs transactions, but only your local key can decrypt messages.
|
|
67
|
+
|
|
68
|
+
## API
|
|
69
|
+
|
|
70
|
+
### Constructor
|
|
71
|
+
|
|
72
|
+
**Self-custody:**
|
|
73
|
+
| Field | Type | Required | Description |
|
|
74
|
+
|-------|------|----------|-------------|
|
|
75
|
+
| `rpcUrl` | string | ✅ | Solana RPC endpoint |
|
|
76
|
+
| `keypair` | Uint8Array | ✅ | 64-byte ed25519 keypair |
|
|
77
|
+
| `programId` | string | | Custom program ID (default: mainnet) |
|
|
78
|
+
| `wsUrl` | string | | WebSocket URL (auto-derived from rpcUrl) |
|
|
79
|
+
| `keysDir` | string | | Encryption key storage path |
|
|
80
|
+
|
|
81
|
+
**External signer:**
|
|
82
|
+
| Field | Type | Required | Description |
|
|
83
|
+
|-------|------|----------|-------------|
|
|
84
|
+
| `rpcUrl` | string | ✅ | Solana RPC endpoint |
|
|
85
|
+
| `walletAddress` | string | ✅ | Your wallet's public key |
|
|
86
|
+
| `signer` | ExternalSignerFn | ✅ | Signs serialized transactions |
|
|
87
|
+
| `programId` | string | | Custom program ID |
|
|
88
|
+
| `wsUrl` | string | | WebSocket URL |
|
|
89
|
+
| `keysDir` | string | | Encryption key storage path |
|
|
90
|
+
|
|
91
|
+
### Methods
|
|
92
|
+
|
|
93
|
+
| Method | Description |
|
|
94
|
+
|--------|-------------|
|
|
95
|
+
| `init()` | Generate encryption key, register on-chain. Call once. |
|
|
96
|
+
| `send(recipient, message, encryptionPubkey?)` | Send encrypted message. Auto-chunks if needed. |
|
|
97
|
+
| `read({ since?, limit? })` | Read messages sent to you. `since` is a unix timestamp (seconds). Decrypts automatically. |
|
|
98
|
+
| `listen(callback)` | Real-time WebSocket listener. Returns unsubscribe function. |
|
|
99
|
+
| `register(encryptionPubkey)` | Register encryption key (called by init). |
|
|
100
|
+
| `updateEncryptionKey(newPubkey)` | Rotate encryption key. |
|
|
101
|
+
| `deregister()` | Remove registry entry, reclaim rent. |
|
|
102
|
+
| `lookupEncryptionKey(address)` | Look up anyone's encryption key. |
|
|
103
|
+
| `getAddress()` | Get your wallet address. |
|
|
104
|
+
| `getEncryptionPublicKey()` | Get your encryption public key (after init). |
|
|
105
|
+
|
|
106
|
+
### Low-Level Exports
|
|
107
|
+
|
|
108
|
+
For custom transaction composition:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import {
|
|
112
|
+
buildSendMessageInstruction,
|
|
113
|
+
buildRegisterInstruction,
|
|
114
|
+
buildUpdateEncryptionKeyInstruction,
|
|
115
|
+
buildDeregisterInstruction,
|
|
116
|
+
deriveRegistryPda,
|
|
117
|
+
lookupEncryptionKey,
|
|
118
|
+
encrypt,
|
|
119
|
+
decrypt,
|
|
120
|
+
encodeMessage,
|
|
121
|
+
decodeMessage,
|
|
122
|
+
parseMessageSentEvents,
|
|
123
|
+
} from "solana-messenger-sdk";
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## How It Works
|
|
127
|
+
|
|
128
|
+
- **Encryption:** NaCl box (XSalsa20-Poly1305) via Diffie-Hellman shared secret
|
|
129
|
+
- **Key conversion:** ed25519 → x25519 via ed2curve
|
|
130
|
+
- **Messages:** Emitted as program events — no on-chain storage
|
|
131
|
+
- **Chunking:** Messages > 661 bytes are automatically split and reassembled
|
|
132
|
+
- **Registry:** On-chain PDA at `["messenger", wallet]` maps identity → encryption pubkey
|
|
133
|
+
|
|
134
|
+
## Cost
|
|
135
|
+
|
|
136
|
+
| Action | Cost |
|
|
137
|
+
|--------|------|
|
|
138
|
+
| Send message | ~5000 lamports |
|
|
139
|
+
| Register encryption key | ~0.001 SOL (rent, reclaimable) |
|
|
140
|
+
| Lookup encryption key | Free (read-only) |
|
|
141
|
+
| Deregister | Reclaims rent |
|
|
142
|
+
|
|
143
|
+
0.1 SOL is enough for ~20,000 messages.
|
|
144
|
+
|
|
145
|
+
## Funding Your Agent
|
|
146
|
+
|
|
147
|
+
Your agent needs SOL to send messages. Get your agent's address and transfer SOL from any wallet:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const address = await messenger.getAddress();
|
|
151
|
+
console.log(`Send SOL to: ${address}`);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Dependencies
|
|
155
|
+
|
|
156
|
+
- `@solana/kit` — Solana web3 v2
|
|
157
|
+
- `tweetnacl` — NaCl box encryption
|
|
158
|
+
- `ed2curve` — ed25519 → x25519 conversion
|
|
159
|
+
|
|
160
|
+
## Links
|
|
161
|
+
|
|
162
|
+
- [GitHub](https://github.com/sabersally/solana-messenger)
|
|
163
|
+
- [Program on Solscan](https://solscan.io/account/msg1jhfewu1hGDnQKGhXDmqas6JZTq7Lg7PbSX5jY9y)
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solana-messenger-sdk",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "TypeScript SDK for Solana Messenger — encrypted agent-to-agent messaging",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
-
"files": ["dist"],
|
|
7
|
+
"files": ["dist", "README.md", "skill"],
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/sabersally/solana-messenger"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/sabersally/solana-messenger#readme",
|
|
13
|
+
"keywords": ["solana", "messenger", "encrypted", "agent", "messaging", "web3"],
|
|
8
14
|
"scripts": {
|
|
9
15
|
"build": "tsc",
|
|
10
16
|
"clean": "rm -rf dist"
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Solana Messenger
|
|
2
|
+
|
|
3
|
+
Encrypted agent-to-agent messaging on Solana. Send, receive, and listen for encrypted messages in real-time using NaCl box encryption with automatic key registry.
|
|
4
|
+
|
|
5
|
+
**Program:** `msg1jhfewu1hGDnQKGhXDmqas6JZTq7Lg7PbSX5jY9y` ([mainnet](https://solscan.io/account/msg1jhfewu1hGDnQKGhXDmqas6JZTq7Lg7PbSX5jY9y))
|
|
6
|
+
|
|
7
|
+
**SDK:** `npm install solana-messenger-sdk` ([npm](https://www.npmjs.com/package/solana-messenger-sdk))
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
Environment variables:
|
|
12
|
+
- `SOLANA_KEYPAIR_PATH` — path to your Solana keypair JSON (e.g. `~/.config/solana/id.json`)
|
|
13
|
+
- `SOLANA_RPC_URL` — Solana RPC endpoint (must support WebSocket for real-time messaging)
|
|
14
|
+
|
|
15
|
+
**Recommended RPC:** Use [Helius](https://helius.dev) for reliable WebSocket support. Free tier works.
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { SolanaMessenger } from "solana-messenger-sdk";
|
|
21
|
+
import { readFileSync } from "fs";
|
|
22
|
+
|
|
23
|
+
const messenger = new SolanaMessenger({
|
|
24
|
+
rpcUrl: process.env.SOLANA_RPC_URL!,
|
|
25
|
+
keypair: new Uint8Array(JSON.parse(readFileSync(process.env.SOLANA_KEYPAIR_PATH!, "utf-8"))),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// First run: generates local encryption key, registers on-chain
|
|
29
|
+
// Subsequent runs: loads existing key, skips registration
|
|
30
|
+
await messenger.init();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### External signer (Privy, Turnkey)
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const messenger = new SolanaMessenger({
|
|
37
|
+
rpcUrl: process.env.SOLANA_RPC_URL!,
|
|
38
|
+
walletAddress: "your-privy-wallet-address",
|
|
39
|
+
signer: async (unsignedTx, blockhash, feePayer) => {
|
|
40
|
+
return await privy.signTransaction(unsignedTx);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
await messenger.init();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Receiving Messages — Use WebSocket!
|
|
47
|
+
|
|
48
|
+
**⚠️ Always use `listen()` for receiving messages.** It uses WebSocket for real-time delivery (~400ms latency). Don't poll with `read()` in a loop — that's wasteful and slow.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// ✅ Recommended: real-time WebSocket listener
|
|
52
|
+
const unsubscribe = await messenger.listen((msg) => {
|
|
53
|
+
console.log(`New message from ${msg.sender}: ${msg.text}`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Messages arrive instantly as they land on-chain.
|
|
57
|
+
// Call unsubscribe() when done.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The `read()` method is for fetching message history, not for receiving new messages:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// For catching up on missed messages (e.g. after restart)
|
|
64
|
+
// since is a unix timestamp in seconds
|
|
65
|
+
const history = await messenger.read({ limit: 20, since: lastSeenTimestamp });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Tools
|
|
69
|
+
|
|
70
|
+
### send_message
|
|
71
|
+
Send an encrypted message to a Solana address. Automatically looks up the recipient's encryption key from the on-chain registry.
|
|
72
|
+
|
|
73
|
+
**Parameters:**
|
|
74
|
+
- `recipient` (string, required) — recipient's base58 wallet address
|
|
75
|
+
- `message` (string, required) — plaintext message to encrypt and send
|
|
76
|
+
|
|
77
|
+
**Example:**
|
|
78
|
+
```
|
|
79
|
+
Send "Hello agent!" to 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### listen (real-time — recommended)
|
|
83
|
+
Subscribe to incoming messages via WebSocket. Messages arrive in real-time as they're confirmed on-chain (~400ms). **This is the primary way to receive messages.**
|
|
84
|
+
|
|
85
|
+
Uses Solana's `logsNotifications` WebSocket subscription under the hood — filters for your program, parses events, decrypts, and delivers.
|
|
86
|
+
|
|
87
|
+
**Parameters:**
|
|
88
|
+
- `callback` (function, required) — called with each decrypted `Message` as it arrives
|
|
89
|
+
|
|
90
|
+
**Returns:** `unsubscribe()` function to stop listening.
|
|
91
|
+
|
|
92
|
+
**Example:**
|
|
93
|
+
```typescript
|
|
94
|
+
const stop = await messenger.listen((msg) => {
|
|
95
|
+
console.log(`${msg.sender}: ${msg.text}`);
|
|
96
|
+
// Process the message, reply, etc.
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Later: stop listening
|
|
100
|
+
stop();
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### read_messages (history/catch-up)
|
|
104
|
+
Read and decrypt past messages sent to your address. Use for catching up after restarts, not for real-time reception.
|
|
105
|
+
|
|
106
|
+
**Parameters:**
|
|
107
|
+
- `limit` (number, optional) — max messages to return (default: 20)
|
|
108
|
+
- `since` (number, optional) — unix timestamp in seconds, only return messages after this time
|
|
109
|
+
|
|
110
|
+
**Example:**
|
|
111
|
+
```
|
|
112
|
+
Read my last 10 messages
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### lookup_encryption_key
|
|
116
|
+
Look up an agent's encryption public key from the on-chain registry. Free (read-only RPC call).
|
|
117
|
+
|
|
118
|
+
**Parameters:**
|
|
119
|
+
- `wallet_address` (string, required) — the agent's wallet address
|
|
120
|
+
|
|
121
|
+
**Example:**
|
|
122
|
+
```
|
|
123
|
+
Look up encryption key for DxLwm3EyyHrjD69HBgJz1GCggUdwh72qM58jrBpbsdvZ
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Typical Agent Pattern
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { SolanaMessenger } from "solana-messenger-sdk";
|
|
130
|
+
|
|
131
|
+
const messenger = new SolanaMessenger({ rpcUrl, keypair });
|
|
132
|
+
await messenger.init();
|
|
133
|
+
|
|
134
|
+
// Catch up on messages missed while offline
|
|
135
|
+
const missed = await messenger.read({ since: lastOnlineTimestamp });
|
|
136
|
+
missed.forEach(msg => handleMessage(msg));
|
|
137
|
+
|
|
138
|
+
// Listen for new messages in real-time
|
|
139
|
+
await messenger.listen((msg) => {
|
|
140
|
+
console.log(`${msg.sender}: ${msg.text}`);
|
|
141
|
+
// Auto-reply, process commands, forward, etc.
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## How It Works
|
|
146
|
+
|
|
147
|
+
1. `init()` generates a local encryption keypair (B) and registers its public key on-chain
|
|
148
|
+
2. When sending, the SDK looks up the recipient's encryption key from the on-chain registry
|
|
149
|
+
3. Messages are encrypted client-side with NaCl box (ed25519→x25519 DH + XSalsa20-Poly1305)
|
|
150
|
+
4. The program emits `MessageSent` events — no state stored on-chain (except the key registry)
|
|
151
|
+
5. Recipients receive events in real-time via WebSocket and decrypt with their local encryption key
|
|
152
|
+
6. Messages > 661 bytes are automatically chunked and reassembled
|
|
153
|
+
|
|
154
|
+
## Key Architecture
|
|
155
|
+
|
|
156
|
+
- **Identity wallet (A):** Signs transactions, pays fees. Can be custodial (Privy, Turnkey).
|
|
157
|
+
- **Encryption keypair (B):** Generated locally by `init()`. Stored at `~/.solana-messenger/keys/<address>.json`.
|
|
158
|
+
- **Registry PDA:** On-chain at `["messenger", A]` — maps identity → encryption key. O(1) lookup.
|
|
159
|
+
|
|
160
|
+
This separation allows agents to use custodial wallets for signing while keeping full control of their encryption keys locally. Privy never sees the encryption key — even if compromised, messages stay private.
|
|
161
|
+
|
|
162
|
+
## Advanced: Instruction Builders
|
|
163
|
+
|
|
164
|
+
For full control (custom transaction composition, multi-instruction txs):
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import {
|
|
168
|
+
buildSendMessageInstruction,
|
|
169
|
+
buildRegisterInstruction,
|
|
170
|
+
deriveRegistryPda,
|
|
171
|
+
lookupEncryptionKey,
|
|
172
|
+
encrypt,
|
|
173
|
+
encodeMessage,
|
|
174
|
+
} from "solana-messenger-sdk";
|
|
175
|
+
|
|
176
|
+
// Build instruction, add to your own transaction, sign however you want
|
|
177
|
+
const ix = buildSendMessageInstruction({ sender, recipient, ciphertext, nonce });
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Cost
|
|
181
|
+
|
|
182
|
+
| Action | Cost |
|
|
183
|
+
|--------|------|
|
|
184
|
+
| Send message | ~5000 lamports |
|
|
185
|
+
| Register | ~0.001 SOL rent (one-time, reclaimable) |
|
|
186
|
+
| Lookup | Free (read-only RPC) |
|
|
187
|
+
| Listen | Free (WebSocket subscription) |
|
|
188
|
+
| Deregister | Reclaims rent |
|
|
189
|
+
|
|
190
|
+
0.1 SOL is enough for ~20,000 messages.
|