@dimcool/wallet 0.1.0-beta.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 +106 -0
- package/dist/index.cjs +430 -0
- package/dist/index.d.cts +120 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.js +399 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @dimcool/wallet
|
|
2
|
+
|
|
3
|
+
Non-custodial wallet toolkit for DIM agents.
|
|
4
|
+
|
|
5
|
+
Canonical docs:
|
|
6
|
+
|
|
7
|
+
- https://docs.dim.cool/capabilities/agent-wallet
|
|
8
|
+
- https://docs.dim.cool/guides/wallet-package
|
|
9
|
+
|
|
10
|
+
`@dimcool/wallet` helps you:
|
|
11
|
+
|
|
12
|
+
- create/load wallets from mnemonic or private key
|
|
13
|
+
- sign Solana messages and transactions
|
|
14
|
+
- authenticate to DIM with `wallet.login(...)`
|
|
15
|
+
- run high-level DIM wallet actions (`send`, `getBalances`, `getInfo`)
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @dimcool/wallet
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { Wallet } from '@dimcool/wallet';
|
|
27
|
+
|
|
28
|
+
const wallet = new Wallet({
|
|
29
|
+
enabledNetworks: ['solana'],
|
|
30
|
+
fromPrivateKey: process.env.DIM_WALLET_PRIVATE_KEY!,
|
|
31
|
+
rpcUrls: {
|
|
32
|
+
solana: 'https://api.mainnet-beta.solana.com',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const session = await wallet.login({
|
|
37
|
+
referralCode: 'optional-referrer',
|
|
38
|
+
walletMeta: { type: 'keypair' },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
console.log('Logged in as', session.user.id);
|
|
42
|
+
console.log('Addresses', wallet.getAddresses());
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Create Wallet
|
|
46
|
+
|
|
47
|
+
### From mnemonic
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const wallet = new Wallet({
|
|
51
|
+
enabledNetworks: ['solana', 'evm'],
|
|
52
|
+
fromMnemonic: process.env.DIM_MNEMONIC!,
|
|
53
|
+
// Solana default path: m/44'/501'/0'/0'
|
|
54
|
+
// Optional alternatives:
|
|
55
|
+
// solanaDerivationPath: "m/44'/501'/1'/0'",
|
|
56
|
+
// solanaAccountIndex: 1,
|
|
57
|
+
rpcUrls: {
|
|
58
|
+
solana: 'https://api.mainnet-beta.solana.com',
|
|
59
|
+
evm: 'https://mainnet.infura.io/v3/<key>',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### From private key
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const wallet = new Wallet({
|
|
68
|
+
enabledNetworks: ['solana'],
|
|
69
|
+
fromPrivateKey: process.env.DIM_WALLET_PRIVATE_KEY!,
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Core APIs
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
wallet.getAddresses(); // { solana?: string, evm?: string }
|
|
77
|
+
wallet.solana?.signMessage('hello');
|
|
78
|
+
wallet.solana?.signAndSendTransaction(tx);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### DIM Convenience Methods
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
await wallet.login({ referralCode: 'optional-referrer' });
|
|
85
|
+
await wallet.setUsername('agentname');
|
|
86
|
+
await wallet.getInfo();
|
|
87
|
+
await wallet.getBalances();
|
|
88
|
+
await wallet.send({ recipient: 'alice.sol', amount: 1.25, token: 'USDC' });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## SDK Interoperability (Optional)
|
|
92
|
+
|
|
93
|
+
If you also use `@dimcool/sdk`, `wallet.getSigner()` returns an SDK-compatible signer:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const signer = wallet.getSigner();
|
|
97
|
+
// signer: { address, signMessage, signTransaction }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Notes
|
|
101
|
+
|
|
102
|
+
- `recipient` in `wallet.send(...)` can be a DIM username, Solana address, or `.sol` domain.
|
|
103
|
+
- `.sol` domains are resolved server-side during transfer preparation.
|
|
104
|
+
- Works in Node.js and browser environments.
|
|
105
|
+
- EVM signing/sending APIs are scaffolded and will be expanded in upcoming releases.
|
|
106
|
+
- `wallet.swap(...)` is reserved for upcoming aggregator-backed swap routes.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
Wallet: () => Wallet,
|
|
34
|
+
createWallet: () => createWallet,
|
|
35
|
+
getWallet: () => getWallet
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// src/polyfills.ts
|
|
40
|
+
if (typeof globalThis !== "undefined" && !globalThis.process) {
|
|
41
|
+
globalThis.process = {
|
|
42
|
+
env: {}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/index.ts
|
|
47
|
+
var import_sdk = require("@dimcool/sdk");
|
|
48
|
+
var import_web3 = require("@solana/web3.js");
|
|
49
|
+
var import_bs58 = __toESM(require("bs58"), 1);
|
|
50
|
+
var bip39 = __toESM(require("bip39"), 1);
|
|
51
|
+
var import_ethers = require("ethers");
|
|
52
|
+
var import_micro_ed25519_hdkey = require("micro-ed25519-hdkey");
|
|
53
|
+
var nacl = __toESM(require("tweetnacl"), 1);
|
|
54
|
+
var DEFAULT_SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
55
|
+
var DEFAULT_API_URL = "https://api.dim.cool";
|
|
56
|
+
var DEFAULT_APP_ID = "dim-agents";
|
|
57
|
+
var DEFAULT_SOLANA_DERIVATION_PATH = "m/44'/501'/0'/0'";
|
|
58
|
+
var EVM_DERIVATION_PATH = "m/44'/60'/0'/0/0";
|
|
59
|
+
var USDC_MULTIPLIER = 1e6;
|
|
60
|
+
var SOLANA_SECRET_KEY_LENGTH = 64;
|
|
61
|
+
function toBase64(bytes) {
|
|
62
|
+
if (typeof Buffer !== "undefined") {
|
|
63
|
+
return Buffer.from(bytes).toString("base64");
|
|
64
|
+
}
|
|
65
|
+
let binary = "";
|
|
66
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
67
|
+
binary += String.fromCharCode(bytes[i]);
|
|
68
|
+
}
|
|
69
|
+
if (typeof btoa === "undefined") {
|
|
70
|
+
throw new Error("Base64 conversion is not available in this environment.");
|
|
71
|
+
}
|
|
72
|
+
return btoa(binary);
|
|
73
|
+
}
|
|
74
|
+
function parseSolanaSecretKey(input) {
|
|
75
|
+
const trimmed = input.trim();
|
|
76
|
+
if (!trimmed) {
|
|
77
|
+
throw new Error("Private key is required.");
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const decoded = new Uint8Array(import_bs58.default.decode(trimmed));
|
|
81
|
+
if (decoded.length === 32 || decoded.length === SOLANA_SECRET_KEY_LENGTH) {
|
|
82
|
+
return decoded;
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
if (typeof Buffer !== "undefined") {
|
|
88
|
+
const decoded = new Uint8Array(Buffer.from(trimmed, "base64"));
|
|
89
|
+
if (decoded.length === 32 || decoded.length === SOLANA_SECRET_KEY_LENGTH) {
|
|
90
|
+
return decoded;
|
|
91
|
+
}
|
|
92
|
+
} else if (typeof atob !== "undefined") {
|
|
93
|
+
const binary = atob(trimmed.replace(/\s/g, ""));
|
|
94
|
+
const bytes = new Uint8Array(binary.length);
|
|
95
|
+
for (let i = 0; i < binary.length; i++) {
|
|
96
|
+
bytes[i] = binary.charCodeAt(i);
|
|
97
|
+
}
|
|
98
|
+
if (bytes.length === 32 || bytes.length === SOLANA_SECRET_KEY_LENGTH) {
|
|
99
|
+
return bytes;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
throw new Error(
|
|
105
|
+
"Invalid Solana private key format. Use base58 or base64 for 32/64-byte secrets."
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
function normalizeEvmPrivateKey(input) {
|
|
109
|
+
const trimmed = input.trim();
|
|
110
|
+
if (!trimmed) {
|
|
111
|
+
throw new Error("EVM private key is required.");
|
|
112
|
+
}
|
|
113
|
+
const prefixed = trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
|
|
114
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(prefixed)) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
"Invalid EVM private key format. Expected 32-byte hex key."
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return prefixed;
|
|
120
|
+
}
|
|
121
|
+
function isEvmHexKey(input) {
|
|
122
|
+
const prefixed = input.startsWith("0x") ? input : `0x${input}`;
|
|
123
|
+
return /^0x[0-9a-fA-F]{64}$/.test(prefixed);
|
|
124
|
+
}
|
|
125
|
+
function createDefaultStorage() {
|
|
126
|
+
if (typeof window !== "undefined") {
|
|
127
|
+
return new import_sdk.BrowserLocalStorage();
|
|
128
|
+
}
|
|
129
|
+
return new import_sdk.NodeStorage();
|
|
130
|
+
}
|
|
131
|
+
function toTransferMinorUnits(amount, token) {
|
|
132
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
133
|
+
throw new Error("Amount must be a positive number.");
|
|
134
|
+
}
|
|
135
|
+
if (token === "SOL") {
|
|
136
|
+
return Math.round(amount * import_web3.LAMPORTS_PER_SOL);
|
|
137
|
+
}
|
|
138
|
+
return Math.round(amount * USDC_MULTIPLIER);
|
|
139
|
+
}
|
|
140
|
+
function getSolanaDerivationPath(options) {
|
|
141
|
+
if (options.solanaDerivationPath) return options.solanaDerivationPath;
|
|
142
|
+
if (options.solanaAccountIndex != null) {
|
|
143
|
+
if (!Number.isInteger(options.solanaAccountIndex) || options.solanaAccountIndex < 0) {
|
|
144
|
+
throw new Error("solanaAccountIndex must be a non-negative integer.");
|
|
145
|
+
}
|
|
146
|
+
return `m/44'/501'/${options.solanaAccountIndex}'/0'`;
|
|
147
|
+
}
|
|
148
|
+
return DEFAULT_SOLANA_DERIVATION_PATH;
|
|
149
|
+
}
|
|
150
|
+
var Wallet = class {
|
|
151
|
+
constructor(options) {
|
|
152
|
+
this.sdk = null;
|
|
153
|
+
this.solanaKeypair = null;
|
|
154
|
+
this.evmWallet = null;
|
|
155
|
+
this.currentUser = null;
|
|
156
|
+
if (!options.enabledNetworks.length) {
|
|
157
|
+
throw new Error("enabledNetworks must include at least one network.");
|
|
158
|
+
}
|
|
159
|
+
this.enabledNetworks = new Set(options.enabledNetworks);
|
|
160
|
+
this.solanaDerivationPath = getSolanaDerivationPath(options);
|
|
161
|
+
this.rpcUrls = {
|
|
162
|
+
solana: options.rpcUrls?.solana || DEFAULT_SOLANA_RPC,
|
|
163
|
+
evm: options.rpcUrls?.evm
|
|
164
|
+
};
|
|
165
|
+
this.sdkConfig = {
|
|
166
|
+
appId: options.sdk?.appId || DEFAULT_APP_ID,
|
|
167
|
+
baseUrl: options.sdk?.baseUrl || DEFAULT_API_URL,
|
|
168
|
+
storage: options.sdk?.storage || createDefaultStorage()
|
|
169
|
+
};
|
|
170
|
+
this.solana = this.enabledNetworks.has("solana") ? {
|
|
171
|
+
signMessage: async (message) => {
|
|
172
|
+
const kp = this.requireSolanaKeypair();
|
|
173
|
+
return nacl.sign.detached(
|
|
174
|
+
new TextEncoder().encode(message),
|
|
175
|
+
kp.secretKey
|
|
176
|
+
);
|
|
177
|
+
},
|
|
178
|
+
signAndSendTransaction: async (transaction) => {
|
|
179
|
+
const connection = new import_web3.Connection(this.rpcUrls.solana, "confirmed");
|
|
180
|
+
const kp = this.requireSolanaKeypair();
|
|
181
|
+
transaction.partialSign(kp);
|
|
182
|
+
const signed = transaction.serialize({
|
|
183
|
+
requireAllSignatures: false
|
|
184
|
+
});
|
|
185
|
+
return connection.sendRawTransaction(signed);
|
|
186
|
+
}
|
|
187
|
+
} : void 0;
|
|
188
|
+
this.ethereum = this.enabledNetworks.has("evm") ? {
|
|
189
|
+
signMessage: async (_message) => {
|
|
190
|
+
throw new Error("EVM signing is not implemented yet.");
|
|
191
|
+
}
|
|
192
|
+
} : void 0;
|
|
193
|
+
if (options.fromMnemonic) {
|
|
194
|
+
this.initializeFromMnemonic(options.fromMnemonic);
|
|
195
|
+
} else if (options.fromPrivateKey) {
|
|
196
|
+
this.initializeFromPrivateKey(options.fromPrivateKey);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
getSdk() {
|
|
200
|
+
if (this.sdk) return this.sdk;
|
|
201
|
+
this.sdk = new import_sdk.SDK({
|
|
202
|
+
appId: this.sdkConfig.appId,
|
|
203
|
+
baseUrl: this.sdkConfig.baseUrl,
|
|
204
|
+
storage: this.sdkConfig.storage
|
|
205
|
+
});
|
|
206
|
+
return this.sdk;
|
|
207
|
+
}
|
|
208
|
+
requireSolanaKeypair() {
|
|
209
|
+
if (!this.solanaKeypair) {
|
|
210
|
+
throw new Error("Solana keypair is not configured.");
|
|
211
|
+
}
|
|
212
|
+
return this.solanaKeypair;
|
|
213
|
+
}
|
|
214
|
+
initializeFromMnemonic(mnemonic) {
|
|
215
|
+
if (!bip39.validateMnemonic(mnemonic)) {
|
|
216
|
+
throw new Error("Invalid mnemonic phrase.");
|
|
217
|
+
}
|
|
218
|
+
if (this.enabledNetworks.has("solana")) {
|
|
219
|
+
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
|
220
|
+
const hd = import_micro_ed25519_hdkey.HDKey.fromMasterSeed(seed.toString("hex"));
|
|
221
|
+
const node = hd.derive(this.solanaDerivationPath);
|
|
222
|
+
if (!node.privateKey) {
|
|
223
|
+
throw new Error("Failed to derive Solana private key from mnemonic.");
|
|
224
|
+
}
|
|
225
|
+
this.solanaKeypair = import_web3.Keypair.fromSeed(node.privateKey);
|
|
226
|
+
}
|
|
227
|
+
if (this.enabledNetworks.has("evm")) {
|
|
228
|
+
const account = import_ethers.HDNodeWallet.fromPhrase(
|
|
229
|
+
mnemonic,
|
|
230
|
+
void 0,
|
|
231
|
+
EVM_DERIVATION_PATH
|
|
232
|
+
);
|
|
233
|
+
this.evmWallet = new import_ethers.Wallet(account.privateKey);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
initializeFromPrivateKey(input) {
|
|
237
|
+
if (typeof input === "string") {
|
|
238
|
+
if (this.enabledNetworks.size === 1 && this.enabledNetworks.has("solana")) {
|
|
239
|
+
this.setKeypair(input);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (this.enabledNetworks.size === 1 && this.enabledNetworks.has("evm")) {
|
|
243
|
+
this.setEvmPrivateKey(input);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (isEvmHexKey(input)) {
|
|
247
|
+
this.setEvmPrivateKey(input);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
this.setKeypair(input);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (this.enabledNetworks.has("solana") && input.solana) {
|
|
254
|
+
this.setKeypair(input.solana);
|
|
255
|
+
}
|
|
256
|
+
if (this.enabledNetworks.has("evm") && input.evm) {
|
|
257
|
+
this.setEvmPrivateKey(input.evm);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
setKeypair(privateKey) {
|
|
261
|
+
if (!this.enabledNetworks.has("solana")) {
|
|
262
|
+
throw new Error("Solana network is not enabled.");
|
|
263
|
+
}
|
|
264
|
+
const keyBytes = typeof privateKey === "string" ? parseSolanaSecretKey(privateKey) : privateKey;
|
|
265
|
+
if (keyBytes.length !== 32 && keyBytes.length !== SOLANA_SECRET_KEY_LENGTH) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`Invalid key length: expected 32 or 64 bytes, got ${keyBytes.length}.`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
this.solanaKeypair = import_web3.Keypair.fromSecretKey(keyBytes);
|
|
271
|
+
}
|
|
272
|
+
setEvmPrivateKey(privateKey) {
|
|
273
|
+
if (!this.enabledNetworks.has("evm")) {
|
|
274
|
+
throw new Error("EVM network is not enabled.");
|
|
275
|
+
}
|
|
276
|
+
this.evmWallet = new import_ethers.Wallet(normalizeEvmPrivateKey(privateKey));
|
|
277
|
+
}
|
|
278
|
+
generateSolanaKeypair() {
|
|
279
|
+
if (!this.enabledNetworks.has("solana")) {
|
|
280
|
+
throw new Error("Solana network is not enabled.");
|
|
281
|
+
}
|
|
282
|
+
this.solanaKeypair = import_web3.Keypair.generate();
|
|
283
|
+
return {
|
|
284
|
+
publicKey: this.solanaKeypair.publicKey.toBase58(),
|
|
285
|
+
secretKey: this.solanaKeypair.secretKey
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
// Intentionally no persistence in v1; wallet state is process memory only.
|
|
289
|
+
async loadFromStorage() {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
clear() {
|
|
293
|
+
this.solanaKeypair = null;
|
|
294
|
+
this.evmWallet = null;
|
|
295
|
+
this.currentUser = null;
|
|
296
|
+
}
|
|
297
|
+
hasKeypair() {
|
|
298
|
+
return this.solanaKeypair !== null;
|
|
299
|
+
}
|
|
300
|
+
getPublicKey() {
|
|
301
|
+
return this.solanaKeypair?.publicKey.toBase58() ?? null;
|
|
302
|
+
}
|
|
303
|
+
getSolanaKeypair() {
|
|
304
|
+
return this.requireSolanaKeypair();
|
|
305
|
+
}
|
|
306
|
+
getSolanaPrivateKeyBase58() {
|
|
307
|
+
return import_bs58.default.encode(this.requireSolanaKeypair().secretKey);
|
|
308
|
+
}
|
|
309
|
+
getAddresses() {
|
|
310
|
+
return {
|
|
311
|
+
...this.solanaKeypair ? { solana: this.solanaKeypair.publicKey.toBase58() } : {},
|
|
312
|
+
...this.evmWallet ? { evm: this.evmWallet.address } : {}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
getSigner() {
|
|
316
|
+
const keypair = this.requireSolanaKeypair();
|
|
317
|
+
return {
|
|
318
|
+
address: keypair.publicKey.toBase58(),
|
|
319
|
+
signMessage: async (message) => {
|
|
320
|
+
const signature = nacl.sign.detached(
|
|
321
|
+
new TextEncoder().encode(message),
|
|
322
|
+
keypair.secretKey
|
|
323
|
+
);
|
|
324
|
+
return toBase64(signature);
|
|
325
|
+
},
|
|
326
|
+
signTransaction: async (transaction) => {
|
|
327
|
+
transaction.partialSign(keypair);
|
|
328
|
+
return transaction;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
async login(options) {
|
|
333
|
+
const sdk = this.getSdk();
|
|
334
|
+
sdk.wallet.setSigner(this.getSigner());
|
|
335
|
+
const response = await sdk.auth.loginWithWallet({
|
|
336
|
+
referralCode: options?.referralCode,
|
|
337
|
+
walletMeta: options?.walletMeta || { type: "keypair" }
|
|
338
|
+
});
|
|
339
|
+
this.currentUser = response.user;
|
|
340
|
+
if (options?.username && !response.user.username) {
|
|
341
|
+
const newUsername = await this.setUsername(options.username);
|
|
342
|
+
this.currentUser = { ...response.user, username: newUsername || null };
|
|
343
|
+
return { ...response, user: this.currentUser };
|
|
344
|
+
}
|
|
345
|
+
return response;
|
|
346
|
+
}
|
|
347
|
+
async setUsername(username) {
|
|
348
|
+
const sdk = this.getSdk();
|
|
349
|
+
const availability = await sdk.users.isUsernameAvailable(username);
|
|
350
|
+
if (!availability.valid) {
|
|
351
|
+
throw new Error("Username is invalid. Use 3-20 alphanumeric characters.");
|
|
352
|
+
}
|
|
353
|
+
if (!availability.available) {
|
|
354
|
+
throw new Error(`Username "${username}" is already taken.`);
|
|
355
|
+
}
|
|
356
|
+
const updated = await sdk.users.updateUsername(username);
|
|
357
|
+
this.currentUser = updated;
|
|
358
|
+
return updated.username;
|
|
359
|
+
}
|
|
360
|
+
async getInfo() {
|
|
361
|
+
const sdk = this.getSdk();
|
|
362
|
+
const isAuthenticated = sdk.auth.isAuthenticated();
|
|
363
|
+
if (!isAuthenticated) {
|
|
364
|
+
return {
|
|
365
|
+
isAuthenticated: false,
|
|
366
|
+
addresses: this.getAddresses()
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
if (!this.currentUser) {
|
|
370
|
+
const sessions = await sdk.auth.getLatestSessions(1);
|
|
371
|
+
const userId = sessions.sessions[0]?.userId;
|
|
372
|
+
if (userId) {
|
|
373
|
+
this.currentUser = await sdk.users.getUserById(userId);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
isAuthenticated: true,
|
|
378
|
+
addresses: this.getAddresses(),
|
|
379
|
+
userId: this.currentUser?.id,
|
|
380
|
+
username: this.currentUser?.username ?? null
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
async getBalances() {
|
|
384
|
+
const sdk = this.getSdk();
|
|
385
|
+
if (!sdk.auth.isAuthenticated()) {
|
|
386
|
+
throw new Error("Wallet must be logged in before calling getBalances().");
|
|
387
|
+
}
|
|
388
|
+
return sdk.wallet.getBalances();
|
|
389
|
+
}
|
|
390
|
+
async send(options) {
|
|
391
|
+
const sdk = this.getSdk();
|
|
392
|
+
const token = options.token || "USDC";
|
|
393
|
+
const amountMinor = toTransferMinorUnits(options.amount, token);
|
|
394
|
+
const result = await sdk.wallet.send(options.recipient, amountMinor, token);
|
|
395
|
+
return {
|
|
396
|
+
signature: result.signature,
|
|
397
|
+
status: result.status,
|
|
398
|
+
recipientAddress: result.recipientAddress,
|
|
399
|
+
amountMinor,
|
|
400
|
+
token,
|
|
401
|
+
feeMinor: result.fee
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
async swap(_options) {
|
|
405
|
+
throw new Error(
|
|
406
|
+
"wallet.swap is not implemented yet. Planned with aggregator-backed routes (e.g. Jupiter)."
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
var walletSingleton = null;
|
|
411
|
+
function createWallet(options) {
|
|
412
|
+
return new Wallet(options);
|
|
413
|
+
}
|
|
414
|
+
function getWallet(options) {
|
|
415
|
+
if (!walletSingleton) {
|
|
416
|
+
if (!options) {
|
|
417
|
+
throw new Error(
|
|
418
|
+
"Wallet singleton is not initialized. Call getWallet(options) once first."
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
walletSingleton = new Wallet(options);
|
|
422
|
+
}
|
|
423
|
+
return walletSingleton;
|
|
424
|
+
}
|
|
425
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
426
|
+
0 && (module.exports = {
|
|
427
|
+
Wallet,
|
|
428
|
+
createWallet,
|
|
429
|
+
getWallet
|
|
430
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { WalletSigner, WalletMeta, LoginResponse, BalanceResponse, TransferToken, SubmitTransferResponse, IStorage } from '@dimcool/sdk';
|
|
2
|
+
import { Transaction, Keypair } from '@solana/web3.js';
|
|
3
|
+
|
|
4
|
+
type EnabledNetwork = 'solana' | 'evm';
|
|
5
|
+
interface WalletRpcUrls {
|
|
6
|
+
solana?: string;
|
|
7
|
+
evm?: string;
|
|
8
|
+
}
|
|
9
|
+
interface WalletSdkConfig {
|
|
10
|
+
appId?: string;
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
storage?: IStorage;
|
|
13
|
+
}
|
|
14
|
+
type WalletPrivateKeyInput = string | {
|
|
15
|
+
solana?: string;
|
|
16
|
+
evm?: string;
|
|
17
|
+
};
|
|
18
|
+
interface WalletOptions {
|
|
19
|
+
enabledNetworks: EnabledNetwork[];
|
|
20
|
+
fromMnemonic?: string;
|
|
21
|
+
fromPrivateKey?: WalletPrivateKeyInput;
|
|
22
|
+
solanaDerivationPath?: string;
|
|
23
|
+
solanaAccountIndex?: number;
|
|
24
|
+
rpcUrls?: WalletRpcUrls;
|
|
25
|
+
sdk?: WalletSdkConfig;
|
|
26
|
+
}
|
|
27
|
+
interface WalletAddresses {
|
|
28
|
+
solana?: string;
|
|
29
|
+
evm?: string;
|
|
30
|
+
}
|
|
31
|
+
interface WalletLoginOptions {
|
|
32
|
+
referralCode?: string;
|
|
33
|
+
username?: string;
|
|
34
|
+
walletMeta?: WalletMeta;
|
|
35
|
+
}
|
|
36
|
+
interface WalletInfo {
|
|
37
|
+
addresses: WalletAddresses;
|
|
38
|
+
isAuthenticated: boolean;
|
|
39
|
+
userId?: string;
|
|
40
|
+
username?: string | null;
|
|
41
|
+
}
|
|
42
|
+
interface WalletSendOptions {
|
|
43
|
+
recipient: string;
|
|
44
|
+
amount: number;
|
|
45
|
+
token?: TransferToken;
|
|
46
|
+
}
|
|
47
|
+
interface WalletSendResult {
|
|
48
|
+
signature: string;
|
|
49
|
+
status: SubmitTransferResponse['status'];
|
|
50
|
+
recipientAddress: string;
|
|
51
|
+
amountMinor: number;
|
|
52
|
+
token: TransferToken;
|
|
53
|
+
feeMinor: number;
|
|
54
|
+
}
|
|
55
|
+
interface WalletSwapOptions {
|
|
56
|
+
from: string;
|
|
57
|
+
to: string;
|
|
58
|
+
amount: string | number;
|
|
59
|
+
}
|
|
60
|
+
interface WalletSolanaApi {
|
|
61
|
+
signMessage(message: string): Promise<Uint8Array>;
|
|
62
|
+
signAndSendTransaction(transaction: Transaction): Promise<string>;
|
|
63
|
+
}
|
|
64
|
+
interface WalletEvmApi {
|
|
65
|
+
signMessage(message: string): Promise<string>;
|
|
66
|
+
}
|
|
67
|
+
interface WalletApi {
|
|
68
|
+
readonly solana?: WalletSolanaApi;
|
|
69
|
+
readonly ethereum?: WalletEvmApi;
|
|
70
|
+
getAddresses(): WalletAddresses;
|
|
71
|
+
getSigner(): WalletSigner;
|
|
72
|
+
login(options?: WalletLoginOptions): Promise<LoginResponse>;
|
|
73
|
+
setUsername(username: string): Promise<string | null | undefined>;
|
|
74
|
+
getInfo(): Promise<WalletInfo>;
|
|
75
|
+
getBalances(): Promise<BalanceResponse>;
|
|
76
|
+
send(options: WalletSendOptions): Promise<WalletSendResult>;
|
|
77
|
+
swap(options: WalletSwapOptions): Promise<never>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
declare class Wallet implements WalletApi {
|
|
81
|
+
private readonly enabledNetworks;
|
|
82
|
+
private readonly rpcUrls;
|
|
83
|
+
private readonly solanaDerivationPath;
|
|
84
|
+
private readonly sdkConfig;
|
|
85
|
+
private sdk;
|
|
86
|
+
private solanaKeypair;
|
|
87
|
+
private evmWallet;
|
|
88
|
+
private currentUser;
|
|
89
|
+
readonly solana?: WalletSolanaApi;
|
|
90
|
+
readonly ethereum?: WalletEvmApi;
|
|
91
|
+
constructor(options: WalletOptions);
|
|
92
|
+
private getSdk;
|
|
93
|
+
private requireSolanaKeypair;
|
|
94
|
+
private initializeFromMnemonic;
|
|
95
|
+
private initializeFromPrivateKey;
|
|
96
|
+
setKeypair(privateKey: string | Uint8Array): void;
|
|
97
|
+
setEvmPrivateKey(privateKey: string): void;
|
|
98
|
+
generateSolanaKeypair(): {
|
|
99
|
+
publicKey: string;
|
|
100
|
+
secretKey: Uint8Array;
|
|
101
|
+
};
|
|
102
|
+
loadFromStorage(): Promise<boolean>;
|
|
103
|
+
clear(): void;
|
|
104
|
+
hasKeypair(): boolean;
|
|
105
|
+
getPublicKey(): string | null;
|
|
106
|
+
getSolanaKeypair(): Keypair;
|
|
107
|
+
getSolanaPrivateKeyBase58(): string;
|
|
108
|
+
getAddresses(): WalletAddresses;
|
|
109
|
+
getSigner(): WalletSigner;
|
|
110
|
+
login(options?: WalletLoginOptions): Promise<LoginResponse>;
|
|
111
|
+
setUsername(username: string): Promise<string | null | undefined>;
|
|
112
|
+
getInfo(): Promise<WalletInfo>;
|
|
113
|
+
getBalances(): Promise<BalanceResponse>;
|
|
114
|
+
send(options: WalletSendOptions): Promise<WalletSendResult>;
|
|
115
|
+
swap(_options: WalletSwapOptions): Promise<never>;
|
|
116
|
+
}
|
|
117
|
+
declare function createWallet(options: WalletOptions): Wallet;
|
|
118
|
+
declare function getWallet(options?: WalletOptions): Wallet;
|
|
119
|
+
|
|
120
|
+
export { type EnabledNetwork, Wallet, type WalletAddresses, type WalletApi, type WalletEvmApi, type WalletInfo, type WalletLoginOptions, type WalletOptions, type WalletPrivateKeyInput, type WalletRpcUrls, type WalletSdkConfig, type WalletSendOptions, type WalletSendResult, type WalletSolanaApi, type WalletSwapOptions, createWallet, getWallet };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { WalletSigner, WalletMeta, LoginResponse, BalanceResponse, TransferToken, SubmitTransferResponse, IStorage } from '@dimcool/sdk';
|
|
2
|
+
import { Transaction, Keypair } from '@solana/web3.js';
|
|
3
|
+
|
|
4
|
+
type EnabledNetwork = 'solana' | 'evm';
|
|
5
|
+
interface WalletRpcUrls {
|
|
6
|
+
solana?: string;
|
|
7
|
+
evm?: string;
|
|
8
|
+
}
|
|
9
|
+
interface WalletSdkConfig {
|
|
10
|
+
appId?: string;
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
storage?: IStorage;
|
|
13
|
+
}
|
|
14
|
+
type WalletPrivateKeyInput = string | {
|
|
15
|
+
solana?: string;
|
|
16
|
+
evm?: string;
|
|
17
|
+
};
|
|
18
|
+
interface WalletOptions {
|
|
19
|
+
enabledNetworks: EnabledNetwork[];
|
|
20
|
+
fromMnemonic?: string;
|
|
21
|
+
fromPrivateKey?: WalletPrivateKeyInput;
|
|
22
|
+
solanaDerivationPath?: string;
|
|
23
|
+
solanaAccountIndex?: number;
|
|
24
|
+
rpcUrls?: WalletRpcUrls;
|
|
25
|
+
sdk?: WalletSdkConfig;
|
|
26
|
+
}
|
|
27
|
+
interface WalletAddresses {
|
|
28
|
+
solana?: string;
|
|
29
|
+
evm?: string;
|
|
30
|
+
}
|
|
31
|
+
interface WalletLoginOptions {
|
|
32
|
+
referralCode?: string;
|
|
33
|
+
username?: string;
|
|
34
|
+
walletMeta?: WalletMeta;
|
|
35
|
+
}
|
|
36
|
+
interface WalletInfo {
|
|
37
|
+
addresses: WalletAddresses;
|
|
38
|
+
isAuthenticated: boolean;
|
|
39
|
+
userId?: string;
|
|
40
|
+
username?: string | null;
|
|
41
|
+
}
|
|
42
|
+
interface WalletSendOptions {
|
|
43
|
+
recipient: string;
|
|
44
|
+
amount: number;
|
|
45
|
+
token?: TransferToken;
|
|
46
|
+
}
|
|
47
|
+
interface WalletSendResult {
|
|
48
|
+
signature: string;
|
|
49
|
+
status: SubmitTransferResponse['status'];
|
|
50
|
+
recipientAddress: string;
|
|
51
|
+
amountMinor: number;
|
|
52
|
+
token: TransferToken;
|
|
53
|
+
feeMinor: number;
|
|
54
|
+
}
|
|
55
|
+
interface WalletSwapOptions {
|
|
56
|
+
from: string;
|
|
57
|
+
to: string;
|
|
58
|
+
amount: string | number;
|
|
59
|
+
}
|
|
60
|
+
interface WalletSolanaApi {
|
|
61
|
+
signMessage(message: string): Promise<Uint8Array>;
|
|
62
|
+
signAndSendTransaction(transaction: Transaction): Promise<string>;
|
|
63
|
+
}
|
|
64
|
+
interface WalletEvmApi {
|
|
65
|
+
signMessage(message: string): Promise<string>;
|
|
66
|
+
}
|
|
67
|
+
interface WalletApi {
|
|
68
|
+
readonly solana?: WalletSolanaApi;
|
|
69
|
+
readonly ethereum?: WalletEvmApi;
|
|
70
|
+
getAddresses(): WalletAddresses;
|
|
71
|
+
getSigner(): WalletSigner;
|
|
72
|
+
login(options?: WalletLoginOptions): Promise<LoginResponse>;
|
|
73
|
+
setUsername(username: string): Promise<string | null | undefined>;
|
|
74
|
+
getInfo(): Promise<WalletInfo>;
|
|
75
|
+
getBalances(): Promise<BalanceResponse>;
|
|
76
|
+
send(options: WalletSendOptions): Promise<WalletSendResult>;
|
|
77
|
+
swap(options: WalletSwapOptions): Promise<never>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
declare class Wallet implements WalletApi {
|
|
81
|
+
private readonly enabledNetworks;
|
|
82
|
+
private readonly rpcUrls;
|
|
83
|
+
private readonly solanaDerivationPath;
|
|
84
|
+
private readonly sdkConfig;
|
|
85
|
+
private sdk;
|
|
86
|
+
private solanaKeypair;
|
|
87
|
+
private evmWallet;
|
|
88
|
+
private currentUser;
|
|
89
|
+
readonly solana?: WalletSolanaApi;
|
|
90
|
+
readonly ethereum?: WalletEvmApi;
|
|
91
|
+
constructor(options: WalletOptions);
|
|
92
|
+
private getSdk;
|
|
93
|
+
private requireSolanaKeypair;
|
|
94
|
+
private initializeFromMnemonic;
|
|
95
|
+
private initializeFromPrivateKey;
|
|
96
|
+
setKeypair(privateKey: string | Uint8Array): void;
|
|
97
|
+
setEvmPrivateKey(privateKey: string): void;
|
|
98
|
+
generateSolanaKeypair(): {
|
|
99
|
+
publicKey: string;
|
|
100
|
+
secretKey: Uint8Array;
|
|
101
|
+
};
|
|
102
|
+
loadFromStorage(): Promise<boolean>;
|
|
103
|
+
clear(): void;
|
|
104
|
+
hasKeypair(): boolean;
|
|
105
|
+
getPublicKey(): string | null;
|
|
106
|
+
getSolanaKeypair(): Keypair;
|
|
107
|
+
getSolanaPrivateKeyBase58(): string;
|
|
108
|
+
getAddresses(): WalletAddresses;
|
|
109
|
+
getSigner(): WalletSigner;
|
|
110
|
+
login(options?: WalletLoginOptions): Promise<LoginResponse>;
|
|
111
|
+
setUsername(username: string): Promise<string | null | undefined>;
|
|
112
|
+
getInfo(): Promise<WalletInfo>;
|
|
113
|
+
getBalances(): Promise<BalanceResponse>;
|
|
114
|
+
send(options: WalletSendOptions): Promise<WalletSendResult>;
|
|
115
|
+
swap(_options: WalletSwapOptions): Promise<never>;
|
|
116
|
+
}
|
|
117
|
+
declare function createWallet(options: WalletOptions): Wallet;
|
|
118
|
+
declare function getWallet(options?: WalletOptions): Wallet;
|
|
119
|
+
|
|
120
|
+
export { type EnabledNetwork, Wallet, type WalletAddresses, type WalletApi, type WalletEvmApi, type WalletInfo, type WalletLoginOptions, type WalletOptions, type WalletPrivateKeyInput, type WalletRpcUrls, type WalletSdkConfig, type WalletSendOptions, type WalletSendResult, type WalletSolanaApi, type WalletSwapOptions, createWallet, getWallet };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
// src/polyfills.ts
|
|
2
|
+
if (typeof globalThis !== "undefined" && !globalThis.process) {
|
|
3
|
+
globalThis.process = {
|
|
4
|
+
env: {}
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
import {
|
|
10
|
+
BrowserLocalStorage,
|
|
11
|
+
NodeStorage,
|
|
12
|
+
SDK
|
|
13
|
+
} from "@dimcool/sdk";
|
|
14
|
+
import {
|
|
15
|
+
Connection,
|
|
16
|
+
Keypair,
|
|
17
|
+
LAMPORTS_PER_SOL
|
|
18
|
+
} from "@solana/web3.js";
|
|
19
|
+
import bs58 from "bs58";
|
|
20
|
+
import * as bip39 from "bip39";
|
|
21
|
+
import { HDNodeWallet, Wallet as EvmWallet } from "ethers";
|
|
22
|
+
import { HDKey } from "micro-ed25519-hdkey";
|
|
23
|
+
import * as nacl from "tweetnacl";
|
|
24
|
+
var DEFAULT_SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
25
|
+
var DEFAULT_API_URL = "https://api.dim.cool";
|
|
26
|
+
var DEFAULT_APP_ID = "dim-agents";
|
|
27
|
+
var DEFAULT_SOLANA_DERIVATION_PATH = "m/44'/501'/0'/0'";
|
|
28
|
+
var EVM_DERIVATION_PATH = "m/44'/60'/0'/0/0";
|
|
29
|
+
var USDC_MULTIPLIER = 1e6;
|
|
30
|
+
var SOLANA_SECRET_KEY_LENGTH = 64;
|
|
31
|
+
function toBase64(bytes) {
|
|
32
|
+
if (typeof Buffer !== "undefined") {
|
|
33
|
+
return Buffer.from(bytes).toString("base64");
|
|
34
|
+
}
|
|
35
|
+
let binary = "";
|
|
36
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
37
|
+
binary += String.fromCharCode(bytes[i]);
|
|
38
|
+
}
|
|
39
|
+
if (typeof btoa === "undefined") {
|
|
40
|
+
throw new Error("Base64 conversion is not available in this environment.");
|
|
41
|
+
}
|
|
42
|
+
return btoa(binary);
|
|
43
|
+
}
|
|
44
|
+
function parseSolanaSecretKey(input) {
|
|
45
|
+
const trimmed = input.trim();
|
|
46
|
+
if (!trimmed) {
|
|
47
|
+
throw new Error("Private key is required.");
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const decoded = new Uint8Array(bs58.decode(trimmed));
|
|
51
|
+
if (decoded.length === 32 || decoded.length === SOLANA_SECRET_KEY_LENGTH) {
|
|
52
|
+
return decoded;
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
if (typeof Buffer !== "undefined") {
|
|
58
|
+
const decoded = new Uint8Array(Buffer.from(trimmed, "base64"));
|
|
59
|
+
if (decoded.length === 32 || decoded.length === SOLANA_SECRET_KEY_LENGTH) {
|
|
60
|
+
return decoded;
|
|
61
|
+
}
|
|
62
|
+
} else if (typeof atob !== "undefined") {
|
|
63
|
+
const binary = atob(trimmed.replace(/\s/g, ""));
|
|
64
|
+
const bytes = new Uint8Array(binary.length);
|
|
65
|
+
for (let i = 0; i < binary.length; i++) {
|
|
66
|
+
bytes[i] = binary.charCodeAt(i);
|
|
67
|
+
}
|
|
68
|
+
if (bytes.length === 32 || bytes.length === SOLANA_SECRET_KEY_LENGTH) {
|
|
69
|
+
return bytes;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
throw new Error(
|
|
75
|
+
"Invalid Solana private key format. Use base58 or base64 for 32/64-byte secrets."
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
function normalizeEvmPrivateKey(input) {
|
|
79
|
+
const trimmed = input.trim();
|
|
80
|
+
if (!trimmed) {
|
|
81
|
+
throw new Error("EVM private key is required.");
|
|
82
|
+
}
|
|
83
|
+
const prefixed = trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
|
|
84
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(prefixed)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
"Invalid EVM private key format. Expected 32-byte hex key."
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return prefixed;
|
|
90
|
+
}
|
|
91
|
+
function isEvmHexKey(input) {
|
|
92
|
+
const prefixed = input.startsWith("0x") ? input : `0x${input}`;
|
|
93
|
+
return /^0x[0-9a-fA-F]{64}$/.test(prefixed);
|
|
94
|
+
}
|
|
95
|
+
function createDefaultStorage() {
|
|
96
|
+
if (typeof window !== "undefined") {
|
|
97
|
+
return new BrowserLocalStorage();
|
|
98
|
+
}
|
|
99
|
+
return new NodeStorage();
|
|
100
|
+
}
|
|
101
|
+
function toTransferMinorUnits(amount, token) {
|
|
102
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
103
|
+
throw new Error("Amount must be a positive number.");
|
|
104
|
+
}
|
|
105
|
+
if (token === "SOL") {
|
|
106
|
+
return Math.round(amount * LAMPORTS_PER_SOL);
|
|
107
|
+
}
|
|
108
|
+
return Math.round(amount * USDC_MULTIPLIER);
|
|
109
|
+
}
|
|
110
|
+
function getSolanaDerivationPath(options) {
|
|
111
|
+
if (options.solanaDerivationPath) return options.solanaDerivationPath;
|
|
112
|
+
if (options.solanaAccountIndex != null) {
|
|
113
|
+
if (!Number.isInteger(options.solanaAccountIndex) || options.solanaAccountIndex < 0) {
|
|
114
|
+
throw new Error("solanaAccountIndex must be a non-negative integer.");
|
|
115
|
+
}
|
|
116
|
+
return `m/44'/501'/${options.solanaAccountIndex}'/0'`;
|
|
117
|
+
}
|
|
118
|
+
return DEFAULT_SOLANA_DERIVATION_PATH;
|
|
119
|
+
}
|
|
120
|
+
var Wallet = class {
|
|
121
|
+
constructor(options) {
|
|
122
|
+
this.sdk = null;
|
|
123
|
+
this.solanaKeypair = null;
|
|
124
|
+
this.evmWallet = null;
|
|
125
|
+
this.currentUser = null;
|
|
126
|
+
if (!options.enabledNetworks.length) {
|
|
127
|
+
throw new Error("enabledNetworks must include at least one network.");
|
|
128
|
+
}
|
|
129
|
+
this.enabledNetworks = new Set(options.enabledNetworks);
|
|
130
|
+
this.solanaDerivationPath = getSolanaDerivationPath(options);
|
|
131
|
+
this.rpcUrls = {
|
|
132
|
+
solana: options.rpcUrls?.solana || DEFAULT_SOLANA_RPC,
|
|
133
|
+
evm: options.rpcUrls?.evm
|
|
134
|
+
};
|
|
135
|
+
this.sdkConfig = {
|
|
136
|
+
appId: options.sdk?.appId || DEFAULT_APP_ID,
|
|
137
|
+
baseUrl: options.sdk?.baseUrl || DEFAULT_API_URL,
|
|
138
|
+
storage: options.sdk?.storage || createDefaultStorage()
|
|
139
|
+
};
|
|
140
|
+
this.solana = this.enabledNetworks.has("solana") ? {
|
|
141
|
+
signMessage: async (message) => {
|
|
142
|
+
const kp = this.requireSolanaKeypair();
|
|
143
|
+
return nacl.sign.detached(
|
|
144
|
+
new TextEncoder().encode(message),
|
|
145
|
+
kp.secretKey
|
|
146
|
+
);
|
|
147
|
+
},
|
|
148
|
+
signAndSendTransaction: async (transaction) => {
|
|
149
|
+
const connection = new Connection(this.rpcUrls.solana, "confirmed");
|
|
150
|
+
const kp = this.requireSolanaKeypair();
|
|
151
|
+
transaction.partialSign(kp);
|
|
152
|
+
const signed = transaction.serialize({
|
|
153
|
+
requireAllSignatures: false
|
|
154
|
+
});
|
|
155
|
+
return connection.sendRawTransaction(signed);
|
|
156
|
+
}
|
|
157
|
+
} : void 0;
|
|
158
|
+
this.ethereum = this.enabledNetworks.has("evm") ? {
|
|
159
|
+
signMessage: async (_message) => {
|
|
160
|
+
throw new Error("EVM signing is not implemented yet.");
|
|
161
|
+
}
|
|
162
|
+
} : void 0;
|
|
163
|
+
if (options.fromMnemonic) {
|
|
164
|
+
this.initializeFromMnemonic(options.fromMnemonic);
|
|
165
|
+
} else if (options.fromPrivateKey) {
|
|
166
|
+
this.initializeFromPrivateKey(options.fromPrivateKey);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
getSdk() {
|
|
170
|
+
if (this.sdk) return this.sdk;
|
|
171
|
+
this.sdk = new SDK({
|
|
172
|
+
appId: this.sdkConfig.appId,
|
|
173
|
+
baseUrl: this.sdkConfig.baseUrl,
|
|
174
|
+
storage: this.sdkConfig.storage
|
|
175
|
+
});
|
|
176
|
+
return this.sdk;
|
|
177
|
+
}
|
|
178
|
+
requireSolanaKeypair() {
|
|
179
|
+
if (!this.solanaKeypair) {
|
|
180
|
+
throw new Error("Solana keypair is not configured.");
|
|
181
|
+
}
|
|
182
|
+
return this.solanaKeypair;
|
|
183
|
+
}
|
|
184
|
+
initializeFromMnemonic(mnemonic) {
|
|
185
|
+
if (!bip39.validateMnemonic(mnemonic)) {
|
|
186
|
+
throw new Error("Invalid mnemonic phrase.");
|
|
187
|
+
}
|
|
188
|
+
if (this.enabledNetworks.has("solana")) {
|
|
189
|
+
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
|
190
|
+
const hd = HDKey.fromMasterSeed(seed.toString("hex"));
|
|
191
|
+
const node = hd.derive(this.solanaDerivationPath);
|
|
192
|
+
if (!node.privateKey) {
|
|
193
|
+
throw new Error("Failed to derive Solana private key from mnemonic.");
|
|
194
|
+
}
|
|
195
|
+
this.solanaKeypair = Keypair.fromSeed(node.privateKey);
|
|
196
|
+
}
|
|
197
|
+
if (this.enabledNetworks.has("evm")) {
|
|
198
|
+
const account = HDNodeWallet.fromPhrase(
|
|
199
|
+
mnemonic,
|
|
200
|
+
void 0,
|
|
201
|
+
EVM_DERIVATION_PATH
|
|
202
|
+
);
|
|
203
|
+
this.evmWallet = new EvmWallet(account.privateKey);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
initializeFromPrivateKey(input) {
|
|
207
|
+
if (typeof input === "string") {
|
|
208
|
+
if (this.enabledNetworks.size === 1 && this.enabledNetworks.has("solana")) {
|
|
209
|
+
this.setKeypair(input);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (this.enabledNetworks.size === 1 && this.enabledNetworks.has("evm")) {
|
|
213
|
+
this.setEvmPrivateKey(input);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (isEvmHexKey(input)) {
|
|
217
|
+
this.setEvmPrivateKey(input);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this.setKeypair(input);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (this.enabledNetworks.has("solana") && input.solana) {
|
|
224
|
+
this.setKeypair(input.solana);
|
|
225
|
+
}
|
|
226
|
+
if (this.enabledNetworks.has("evm") && input.evm) {
|
|
227
|
+
this.setEvmPrivateKey(input.evm);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
setKeypair(privateKey) {
|
|
231
|
+
if (!this.enabledNetworks.has("solana")) {
|
|
232
|
+
throw new Error("Solana network is not enabled.");
|
|
233
|
+
}
|
|
234
|
+
const keyBytes = typeof privateKey === "string" ? parseSolanaSecretKey(privateKey) : privateKey;
|
|
235
|
+
if (keyBytes.length !== 32 && keyBytes.length !== SOLANA_SECRET_KEY_LENGTH) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Invalid key length: expected 32 or 64 bytes, got ${keyBytes.length}.`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
this.solanaKeypair = Keypair.fromSecretKey(keyBytes);
|
|
241
|
+
}
|
|
242
|
+
setEvmPrivateKey(privateKey) {
|
|
243
|
+
if (!this.enabledNetworks.has("evm")) {
|
|
244
|
+
throw new Error("EVM network is not enabled.");
|
|
245
|
+
}
|
|
246
|
+
this.evmWallet = new EvmWallet(normalizeEvmPrivateKey(privateKey));
|
|
247
|
+
}
|
|
248
|
+
generateSolanaKeypair() {
|
|
249
|
+
if (!this.enabledNetworks.has("solana")) {
|
|
250
|
+
throw new Error("Solana network is not enabled.");
|
|
251
|
+
}
|
|
252
|
+
this.solanaKeypair = Keypair.generate();
|
|
253
|
+
return {
|
|
254
|
+
publicKey: this.solanaKeypair.publicKey.toBase58(),
|
|
255
|
+
secretKey: this.solanaKeypair.secretKey
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
// Intentionally no persistence in v1; wallet state is process memory only.
|
|
259
|
+
async loadFromStorage() {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
clear() {
|
|
263
|
+
this.solanaKeypair = null;
|
|
264
|
+
this.evmWallet = null;
|
|
265
|
+
this.currentUser = null;
|
|
266
|
+
}
|
|
267
|
+
hasKeypair() {
|
|
268
|
+
return this.solanaKeypair !== null;
|
|
269
|
+
}
|
|
270
|
+
getPublicKey() {
|
|
271
|
+
return this.solanaKeypair?.publicKey.toBase58() ?? null;
|
|
272
|
+
}
|
|
273
|
+
getSolanaKeypair() {
|
|
274
|
+
return this.requireSolanaKeypair();
|
|
275
|
+
}
|
|
276
|
+
getSolanaPrivateKeyBase58() {
|
|
277
|
+
return bs58.encode(this.requireSolanaKeypair().secretKey);
|
|
278
|
+
}
|
|
279
|
+
getAddresses() {
|
|
280
|
+
return {
|
|
281
|
+
...this.solanaKeypair ? { solana: this.solanaKeypair.publicKey.toBase58() } : {},
|
|
282
|
+
...this.evmWallet ? { evm: this.evmWallet.address } : {}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
getSigner() {
|
|
286
|
+
const keypair = this.requireSolanaKeypair();
|
|
287
|
+
return {
|
|
288
|
+
address: keypair.publicKey.toBase58(),
|
|
289
|
+
signMessage: async (message) => {
|
|
290
|
+
const signature = nacl.sign.detached(
|
|
291
|
+
new TextEncoder().encode(message),
|
|
292
|
+
keypair.secretKey
|
|
293
|
+
);
|
|
294
|
+
return toBase64(signature);
|
|
295
|
+
},
|
|
296
|
+
signTransaction: async (transaction) => {
|
|
297
|
+
transaction.partialSign(keypair);
|
|
298
|
+
return transaction;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
async login(options) {
|
|
303
|
+
const sdk = this.getSdk();
|
|
304
|
+
sdk.wallet.setSigner(this.getSigner());
|
|
305
|
+
const response = await sdk.auth.loginWithWallet({
|
|
306
|
+
referralCode: options?.referralCode,
|
|
307
|
+
walletMeta: options?.walletMeta || { type: "keypair" }
|
|
308
|
+
});
|
|
309
|
+
this.currentUser = response.user;
|
|
310
|
+
if (options?.username && !response.user.username) {
|
|
311
|
+
const newUsername = await this.setUsername(options.username);
|
|
312
|
+
this.currentUser = { ...response.user, username: newUsername || null };
|
|
313
|
+
return { ...response, user: this.currentUser };
|
|
314
|
+
}
|
|
315
|
+
return response;
|
|
316
|
+
}
|
|
317
|
+
async setUsername(username) {
|
|
318
|
+
const sdk = this.getSdk();
|
|
319
|
+
const availability = await sdk.users.isUsernameAvailable(username);
|
|
320
|
+
if (!availability.valid) {
|
|
321
|
+
throw new Error("Username is invalid. Use 3-20 alphanumeric characters.");
|
|
322
|
+
}
|
|
323
|
+
if (!availability.available) {
|
|
324
|
+
throw new Error(`Username "${username}" is already taken.`);
|
|
325
|
+
}
|
|
326
|
+
const updated = await sdk.users.updateUsername(username);
|
|
327
|
+
this.currentUser = updated;
|
|
328
|
+
return updated.username;
|
|
329
|
+
}
|
|
330
|
+
async getInfo() {
|
|
331
|
+
const sdk = this.getSdk();
|
|
332
|
+
const isAuthenticated = sdk.auth.isAuthenticated();
|
|
333
|
+
if (!isAuthenticated) {
|
|
334
|
+
return {
|
|
335
|
+
isAuthenticated: false,
|
|
336
|
+
addresses: this.getAddresses()
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (!this.currentUser) {
|
|
340
|
+
const sessions = await sdk.auth.getLatestSessions(1);
|
|
341
|
+
const userId = sessions.sessions[0]?.userId;
|
|
342
|
+
if (userId) {
|
|
343
|
+
this.currentUser = await sdk.users.getUserById(userId);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
isAuthenticated: true,
|
|
348
|
+
addresses: this.getAddresses(),
|
|
349
|
+
userId: this.currentUser?.id,
|
|
350
|
+
username: this.currentUser?.username ?? null
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
async getBalances() {
|
|
354
|
+
const sdk = this.getSdk();
|
|
355
|
+
if (!sdk.auth.isAuthenticated()) {
|
|
356
|
+
throw new Error("Wallet must be logged in before calling getBalances().");
|
|
357
|
+
}
|
|
358
|
+
return sdk.wallet.getBalances();
|
|
359
|
+
}
|
|
360
|
+
async send(options) {
|
|
361
|
+
const sdk = this.getSdk();
|
|
362
|
+
const token = options.token || "USDC";
|
|
363
|
+
const amountMinor = toTransferMinorUnits(options.amount, token);
|
|
364
|
+
const result = await sdk.wallet.send(options.recipient, amountMinor, token);
|
|
365
|
+
return {
|
|
366
|
+
signature: result.signature,
|
|
367
|
+
status: result.status,
|
|
368
|
+
recipientAddress: result.recipientAddress,
|
|
369
|
+
amountMinor,
|
|
370
|
+
token,
|
|
371
|
+
feeMinor: result.fee
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
async swap(_options) {
|
|
375
|
+
throw new Error(
|
|
376
|
+
"wallet.swap is not implemented yet. Planned with aggregator-backed routes (e.g. Jupiter)."
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
var walletSingleton = null;
|
|
381
|
+
function createWallet(options) {
|
|
382
|
+
return new Wallet(options);
|
|
383
|
+
}
|
|
384
|
+
function getWallet(options) {
|
|
385
|
+
if (!walletSingleton) {
|
|
386
|
+
if (!options) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
"Wallet singleton is not initialized. Call getWallet(options) once first."
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
walletSingleton = new Wallet(options);
|
|
392
|
+
}
|
|
393
|
+
return walletSingleton;
|
|
394
|
+
}
|
|
395
|
+
export {
|
|
396
|
+
Wallet,
|
|
397
|
+
createWallet,
|
|
398
|
+
getWallet
|
|
399
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dimcool/wallet",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"build:tsc": "tsc",
|
|
24
|
+
"dev": "tsc --watch"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@dimcool/sdk": "0.1.0",
|
|
28
|
+
"@solana/web3.js": "^1.95.4",
|
|
29
|
+
"bip39": "^3.1.0",
|
|
30
|
+
"bs58": "^6.0.0",
|
|
31
|
+
"ethers": "^6.15.0",
|
|
32
|
+
"micro-ed25519-hdkey": "^0.1.2",
|
|
33
|
+
"tweetnacl": "^1.0.3"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"tsup": "^8.5.1",
|
|
37
|
+
"typescript": "^5.0.0"
|
|
38
|
+
}
|
|
39
|
+
}
|