@microslop/ping-directory-sdk 0.1.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.

Potentially problematic release.


This version of @microslop/ping-directory-sdk might be problematic. Click here for more details.

package/src/pda.js ADDED
@@ -0,0 +1,75 @@
1
+ // PDA finders. Each returns [PublicKey, bump].
2
+
3
+ import { PublicKey } from '@solana/web3.js';
4
+ import { PROGRAM_ID, Seeds } from './constants.js';
5
+ import { u32LE, u64LE } from './encoding.js';
6
+
7
+ const seedBytes = (s) => new TextEncoder().encode(s);
8
+
9
+ export function findPDA(seeds) {
10
+ return PublicKey.findProgramAddressSync(seeds, PROGRAM_ID);
11
+ }
12
+
13
+ export const findConfigPDA = () =>
14
+ findPDA([seedBytes(Seeds.CONFIG)]);
15
+
16
+ export const findUsernamePDA = (username) =>
17
+ findPDA([seedBytes(Seeds.USERNAME), new TextEncoder().encode(username)]);
18
+
19
+ // Combined ed25519 metadata PDA — replaces ReverseLookup + CompromisedKey.
20
+ // Holds both `bound_username` (current binding) and `compromised_at`
21
+ // (revocation marker), in one PDA per ed25519 key.
22
+ export const findEd25519AccountPDA = (ed25519Pubkey) => {
23
+ const bytes = ed25519Pubkey instanceof PublicKey ? ed25519Pubkey.toBytes() : ed25519Pubkey;
24
+ return findPDA([seedBytes(Seeds.ED25519), bytes]);
25
+ };
26
+
27
+ export const findNoncePDA = (nonce) =>
28
+ findPDA([seedBytes(Seeds.NONCE), nonce]);
29
+
30
+ export const findGracePeriodPDA = (username) =>
31
+ findPDA([seedBytes(Seeds.GRACE), new TextEncoder().encode(username)]);
32
+
33
+ export const findSaleListingPDA = (username) =>
34
+ findPDA([seedBytes(Seeds.SALE), new TextEncoder().encode(username)]);
35
+
36
+ export const findReferralBalancePDA = (referrerPubkey) => {
37
+ const key = referrerPubkey ?? PublicKey.default;
38
+ const bytes = key instanceof PublicKey ? key.toBytes() : new Uint8Array(key);
39
+ return findPDA([seedBytes(Seeds.REFERRAL), bytes]);
40
+ };
41
+
42
+ export const findUserIndexPDA = (userId) =>
43
+ findPDA([seedBytes(Seeds.USER_INDEX), u64LE(userId)]);
44
+
45
+ export const findUidMapPDA = (username) =>
46
+ findPDA([seedBytes(Seeds.UID_MAP), new TextEncoder().encode(username)]);
47
+
48
+ export const findAdminPDA = (adminPubkey) =>
49
+ findPDA([seedBytes(Seeds.ADMIN), adminPubkey instanceof PublicKey ? adminPubkey.toBytes() : adminPubkey]);
50
+
51
+ // Treasury-wallet role PDA — existence at [b"treasury_wallet", pubkey]
52
+ // authorizes that address to withdraw the treasury (to itself).
53
+ export const findTreasuryWalletPDA = (walletPubkey) =>
54
+ findPDA([seedBytes(Seeds.TREASURY_WALLET), walletPubkey instanceof PublicKey ? walletPubkey.toBytes() : walletPubkey]);
55
+
56
+ export const findBlocklistPDA = (username) =>
57
+ findPDA([seedBytes(Seeds.BLOCKED), new TextEncoder().encode(username)]);
58
+
59
+ // Companion visibility PDA — [b"moderation", username]. Absence = visible.
60
+ export const findModerationPDA = (username) =>
61
+ findPDA([seedBytes(Seeds.MODERATION), new TextEncoder().encode(username)]);
62
+
63
+ export const findShopItemPDA = (itemId) =>
64
+ findPDA([seedBytes(Seeds.SHOP_ITEM), u32LE(itemId)]);
65
+
66
+ // Inventory lives at [b"inventory", username] — items travel with the
67
+ // username (cosmetics survive key rotation / sale).
68
+ export const findInventoryPDA = (username) =>
69
+ findPDA([seedBytes(Seeds.INVENTORY), new TextEncoder().encode(username)]);
70
+
71
+ // ProfilePhoto lives at [b"profile_photo", username] — photo travels
72
+ // with the username.
73
+ export const findProfilePhotoPDA = (username) =>
74
+ findPDA([seedBytes(Seeds.PROFILE_PHOTO), new TextEncoder().encode(username)]);
75
+
package/src/wallet.js ADDED
@@ -0,0 +1,189 @@
1
+ // Wallet-adapter helper — sign + send a Transaction via a connected
2
+ // browser wallet (Phantom, Solflare, Backpack, …).
3
+ //
4
+ // Ported from the legacy SDK (`tx.js#walletSignAndSend`) and adapted to
5
+ // v1.0.0's Connection-based world. The legacy helper operated on raw
6
+ // serialized tx bytes and broadcast through a module-level `sendTx`
7
+ // (which used a custom proxied fetch); v1.0.0 instead takes a
8
+ // `Connection` so we can use `connection.sendRawTransaction` /
9
+ // `connection.confirmTransaction` directly, and `Connection` itself is
10
+ // the integration point for proxied fetch + beforeRequest headers
11
+ // (configured at construction time on PingDirectory).
12
+ //
13
+ // Tries three signing paths in order, mirroring the legacy SDK:
14
+ // 1. Wallet Standard `solana:signTransaction` — wallet returns
15
+ // signed bytes; we broadcast via `connection.sendRawTransaction`.
16
+ // 2. Wallet Standard `solana:signAndSendTransaction` — wallet
17
+ // broadcasts itself and returns a signature.
18
+ // 3. Legacy `provider.signTransaction(tx)` — the wallet object has
19
+ // a top-level `signTransaction` (older Phantom / wallet-adapter
20
+ // shims). We sign, then `sendRawTransaction`.
21
+ //
22
+ // Pure-JS, no extra deps. Uses only @solana/web3.js (already a SDK
23
+ // dep) for the Transaction wire format.
24
+
25
+ import { VersionedTransaction, Transaction } from '@solana/web3.js';
26
+
27
+ // ── base58 encode (for signature bytes returned by Wallet Standard) ──
28
+ // Tiny inline impl — avoids pulling in `bs58`. Identical alphabet to
29
+ // the one Solana uses; signatures are always 64 bytes.
30
+ const B58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
31
+ function b58encode(bytes) {
32
+ if (!bytes || bytes.length === 0) return '';
33
+ let zeros = 0;
34
+ while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
35
+ const digits = [0];
36
+ for (let i = zeros; i < bytes.length; i++) {
37
+ let carry = bytes[i];
38
+ for (let j = 0; j < digits.length; j++) {
39
+ carry += digits[j] << 8;
40
+ digits[j] = carry % 58;
41
+ carry = (carry / 58) | 0;
42
+ }
43
+ while (carry > 0) {
44
+ digits.push(carry % 58);
45
+ carry = (carry / 58) | 0;
46
+ }
47
+ }
48
+ let out = '';
49
+ for (let i = 0; i < zeros; i++) out += '1';
50
+ for (let i = digits.length - 1; i >= 0; i--) out += B58_ALPHABET[digits[i]];
51
+ return out;
52
+ }
53
+
54
+ /**
55
+ * Serialize a Transaction or VersionedTransaction to wire bytes.
56
+ * Both classes expose `.serialize()`; legacy Transactions need
57
+ * `requireAllSignatures: false` because the wallet hasn't signed yet.
58
+ */
59
+ function serializeTx(tx) {
60
+ if (tx instanceof VersionedTransaction) return tx.serialize();
61
+ if (tx instanceof Transaction) return tx.serialize({ requireAllSignatures: false, verifySignatures: false });
62
+ if (tx instanceof Uint8Array) return tx;
63
+ throw new Error('walletSignAndSend: transaction must be a Transaction, VersionedTransaction, or Uint8Array');
64
+ }
65
+
66
+ /**
67
+ * Best-effort detection of an account object suitable for Wallet
68
+ * Standard `solana:signTransaction` calls. Wallet Standard expects
69
+ * `{ address, publicKey, chains, features }`. If the wallet object
70
+ * already has an `account` field (the case when produced by our
71
+ * legacy-style `connectSolanaWallet`), use it directly. Otherwise we
72
+ * synthesize one from `pubkeyBytes`.
73
+ */
74
+ function resolveAccount(wallet, chainId) {
75
+ if (wallet.account) return wallet.account;
76
+ if (wallet.pubkeyBytes) {
77
+ return {
78
+ address: b58encode(wallet.pubkeyBytes),
79
+ publicKey: wallet.pubkeyBytes,
80
+ chains: [chainId],
81
+ features: ['solana:signTransaction', 'solana:signAndSendTransaction'],
82
+ };
83
+ }
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Sign and send a transaction via a connected wallet, then await
89
+ * `confirmed` commitment.
90
+ *
91
+ * @param {Connection} connection — the SDK Connection to broadcast on.
92
+ * @param {object} wallet — wallet adapter:
93
+ * - Wallet Standard: `{ provider: { features }, account?, pubkeyBytes?, type? }`
94
+ * - Legacy: `{ provider: { signTransaction(tx) }, pubkeyBytes? }`
95
+ * @param {Transaction|VersionedTransaction|Uint8Array} transaction
96
+ * @param {object} [opts]
97
+ * @param {string} [opts.chainId='solana:mainnet'] — Wallet Standard
98
+ * chain identifier passed to `signTransaction` so wallets on a
99
+ * mismatched cluster can refuse the prompt instead of silently
100
+ * broadcasting on the wrong network.
101
+ * @param {string} [opts.commitment='confirmed']
102
+ * @returns {Promise<string>} the transaction signature (base58).
103
+ */
104
+ export async function walletSignAndSend(connection, wallet, transaction, opts = {}) {
105
+ if (!connection || typeof connection.sendRawTransaction !== 'function') {
106
+ throw new Error('walletSignAndSend: connection (with sendRawTransaction) is required');
107
+ }
108
+ if (!wallet) throw new Error('walletSignAndSend: wallet is required');
109
+
110
+ const chainId = opts.chainId || 'solana:mainnet';
111
+ const commitment = opts.commitment || 'confirmed';
112
+ const provider = wallet.provider || wallet; // some adapters ARE the provider
113
+ const account = resolveAccount(wallet, chainId);
114
+
115
+ // ── Path 1: Wallet Standard `solana:signTransaction` ────────────────
116
+ // Returns signed bytes; we broadcast.
117
+ const stFeat = provider?.features?.['solana:signTransaction'];
118
+ if (stFeat && account) {
119
+ const txBytes = serializeTx(transaction);
120
+ let signedBytes;
121
+ try {
122
+ const [output] = await stFeat.signTransaction({
123
+ account,
124
+ transaction: txBytes,
125
+ chain: chainId,
126
+ });
127
+ signedBytes = output.signedTransaction;
128
+ } catch (e) {
129
+ // Fall through to next path on signing error (mirrors the legacy SDK).
130
+ console.warn('[wallet] WS signTransaction failed:', e?.message ?? e);
131
+ }
132
+ if (signedBytes) {
133
+ const sig = await connection.sendRawTransaction(signedBytes, {
134
+ skipPreflight: false,
135
+ preflightCommitment: commitment,
136
+ });
137
+ await connection.confirmTransaction(sig, commitment);
138
+ return sig;
139
+ }
140
+ }
141
+
142
+ // ── Path 2: Wallet Standard `solana:signAndSendTransaction` ─────────
143
+ // Wallet broadcasts; returns a 64-byte signature.
144
+ const saFeat = provider?.features?.['solana:signAndSendTransaction'];
145
+ if (saFeat && account) {
146
+ const txBytes = serializeTx(transaction);
147
+ try {
148
+ const [output] = await saFeat.signAndSendTransaction({
149
+ account,
150
+ transaction: txBytes,
151
+ chain: chainId,
152
+ });
153
+ const sig = b58encode(output.signature);
154
+ await connection.confirmTransaction(sig, commitment);
155
+ return sig;
156
+ } catch (e) {
157
+ console.warn('[wallet] WS signAndSendTransaction failed:', e?.message ?? e);
158
+ }
159
+ }
160
+
161
+ // ── Path 3: Legacy `provider.signTransaction(tx)` ───────────────────
162
+ // Older Phantom / wallet-adapter shims — pass a real Transaction
163
+ // instance, get a signed one back, then broadcast its bytes.
164
+ if (typeof provider?.signTransaction === 'function') {
165
+ let signed;
166
+ try {
167
+ // If caller gave us raw bytes, deserialize so the legacy adapter
168
+ // sees a Transaction object. Try VersionedTransaction first
169
+ // (modern format), fall back to legacy Transaction.
170
+ let tx = transaction;
171
+ if (tx instanceof Uint8Array) {
172
+ try { tx = VersionedTransaction.deserialize(tx); }
173
+ catch { tx = Transaction.from(tx); }
174
+ }
175
+ signed = await provider.signTransaction(tx);
176
+ } catch (e) {
177
+ throw new Error('Wallet signing failed: ' + (e?.message ?? 'unknown'));
178
+ }
179
+ const signedBytes = signed instanceof Uint8Array ? signed : signed.serialize();
180
+ const sig = await connection.sendRawTransaction(signedBytes, {
181
+ skipPreflight: false,
182
+ preflightCommitment: commitment,
183
+ });
184
+ await connection.confirmTransaction(sig, commitment);
185
+ return sig;
186
+ }
187
+
188
+ throw new Error('walletSignAndSend: no compatible signing method found on wallet');
189
+ }