@silkysquad/silk 1.0.0

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/src/cli.ts ADDED
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { walletCreate, walletList, walletFund } from './commands/wallet.js';
4
+ import { balance } from './commands/balance.js';
5
+ import { pay } from './commands/pay.js';
6
+ import { claim } from './commands/claim.js';
7
+ import { cancel } from './commands/cancel.js';
8
+ import { paymentsList, paymentsGet } from './commands/payments.js';
9
+ import { configSetApiUrl, configGetApiUrl, configResetApiUrl, configSetCluster, configGetCluster, configResetCluster } from './commands/config.js';
10
+ import { accountSync, accountStatus, accountSend } from './commands/account.js';
11
+ import { contactsAdd, contactsRemove, contactsList, contactsGet } from './commands/contacts.js';
12
+ import { chat } from './commands/chat.js';
13
+ import { init } from './commands/init.js';
14
+ import { wrapCommand } from './output.js';
15
+
16
+ const program = new Command();
17
+ program
18
+ .name('silk')
19
+ .description('Silkyway SDK — Agent payments on Solana')
20
+ .version('0.1.0')
21
+ .option('--human', 'Human-readable output');
22
+
23
+ // init
24
+ program
25
+ .command('init')
26
+ .description('Initialize Silk CLI (create default wallet and agent ID)')
27
+ .action(wrapCommand(init));
28
+
29
+ // wallet commands
30
+ const wallet = program.command('wallet').description('Manage wallets');
31
+ wallet
32
+ .command('create')
33
+ .argument('[label]', 'wallet label', 'main')
34
+ .description('Create a new wallet')
35
+ .action(wrapCommand(walletCreate));
36
+ wallet
37
+ .command('list')
38
+ .description('List all wallets')
39
+ .action(wrapCommand(walletList));
40
+ wallet
41
+ .command('fund')
42
+ .option('--sol', 'Request SOL only')
43
+ .option('--usdc', 'Request USDC only')
44
+ .option('--wallet <label>', 'Wallet to fund')
45
+ .description('Fund wallet from devnet faucet (devnet only)')
46
+ .action(wrapCommand(walletFund));
47
+
48
+ // balance
49
+ program
50
+ .command('balance')
51
+ .option('--wallet <label>', 'Wallet to check')
52
+ .description('Check wallet balances')
53
+ .action(wrapCommand(balance));
54
+
55
+ // pay
56
+ program
57
+ .command('pay')
58
+ .argument('<recipient>', 'Recipient wallet address')
59
+ .argument('<amount>', 'Amount in USDC')
60
+ .option('--memo <text>', 'Payment memo')
61
+ .option('--wallet <label>', 'Sender wallet')
62
+ .description('Send a USDC payment')
63
+ .action(wrapCommand(pay));
64
+
65
+ // claim
66
+ program
67
+ .command('claim')
68
+ .argument('<transferPda>', 'Transfer PDA to claim')
69
+ .option('--wallet <label>', 'Wallet to claim with')
70
+ .description('Claim a received payment')
71
+ .action(wrapCommand(claim));
72
+
73
+ // cancel
74
+ program
75
+ .command('cancel')
76
+ .argument('<transferPda>', 'Transfer PDA to cancel')
77
+ .option('--wallet <label>', 'Wallet to cancel with')
78
+ .description('Cancel a sent payment')
79
+ .action(wrapCommand(cancel));
80
+
81
+ // payments
82
+ const payments = program.command('payments').description('View payment history');
83
+ payments
84
+ .command('list')
85
+ .option('--wallet <label>', 'Wallet to query')
86
+ .description('List transfers')
87
+ .action(wrapCommand(paymentsList));
88
+ payments
89
+ .command('get')
90
+ .argument('<transferPda>', 'Transfer PDA')
91
+ .description('Get transfer details')
92
+ .action(wrapCommand(paymentsGet));
93
+
94
+ // config
95
+ const config = program.command('config').description('SDK configuration');
96
+ config
97
+ .command('set-api-url')
98
+ .argument('<url>', 'API base URL')
99
+ .description('Set the API base URL')
100
+ .action(wrapCommand(configSetApiUrl));
101
+ config
102
+ .command('get-api-url')
103
+ .description('Show the current API base URL')
104
+ .action(wrapCommand(configGetApiUrl));
105
+ config
106
+ .command('reset-api-url')
107
+ .description('Reset API URL to default')
108
+ .action(wrapCommand(configResetApiUrl));
109
+ config
110
+ .command('set-cluster')
111
+ .argument('<cluster>', 'Cluster: mainnet-beta or devnet')
112
+ .description('Set the Solana cluster')
113
+ .action(wrapCommand(configSetCluster));
114
+ config
115
+ .command('get-cluster')
116
+ .description('Show the current Solana cluster')
117
+ .action(wrapCommand(configGetCluster));
118
+ config
119
+ .command('reset-cluster')
120
+ .description('Reset cluster to default (mainnet-beta)')
121
+ .action(wrapCommand(configResetCluster));
122
+
123
+ // account commands
124
+ const account = program.command('account').description('Manage Silkysig account');
125
+ account
126
+ .command('sync')
127
+ .option('--wallet <label>', 'Wallet to sync')
128
+ .option('--account <pda>', 'Sync a specific account by PDA')
129
+ .description('Discover and sync your account')
130
+ .action(wrapCommand(accountSync));
131
+ account
132
+ .command('status')
133
+ .option('--wallet <label>', 'Wallet to check')
134
+ .description('Show account balance and policy')
135
+ .action(wrapCommand(accountStatus));
136
+ account
137
+ .command('send')
138
+ .argument('<recipient>', 'Recipient wallet address')
139
+ .argument('<amount>', 'Amount in USDC')
140
+ .option('--memo <text>', 'Payment memo')
141
+ .option('--wallet <label>', 'Sender wallet')
142
+ .description('Send from account (policy-enforced)')
143
+ .action(wrapCommand(accountSend));
144
+
145
+ // contacts commands
146
+ const contacts = program.command('contacts').description('Manage address book');
147
+ contacts
148
+ .command('add')
149
+ .argument('<name>', 'Contact name')
150
+ .argument('<address>', 'Solana wallet address')
151
+ .description('Add a contact')
152
+ .action(wrapCommand(contactsAdd));
153
+ contacts
154
+ .command('remove')
155
+ .argument('<name>', 'Contact name')
156
+ .description('Remove a contact')
157
+ .action(wrapCommand(contactsRemove));
158
+ contacts
159
+ .command('list')
160
+ .description('List all contacts')
161
+ .action(wrapCommand(contactsList));
162
+ contacts
163
+ .command('get')
164
+ .argument('<name>', 'Contact name')
165
+ .description('Get a contact address')
166
+ .action(wrapCommand(contactsGet));
167
+
168
+ // chat
169
+ program
170
+ .command('chat')
171
+ .argument('<message>', 'Message to send to support')
172
+ .description('Chat with Silkyway support agent')
173
+ .action(wrapCommand(chat));
174
+
175
+ program.parse();
package/src/client.ts ADDED
@@ -0,0 +1,49 @@
1
+ import axios, { AxiosInstance, AxiosError } from 'axios';
2
+ import { SdkError, ANCHOR_ERROR_MAP } from './errors.js';
3
+
4
+ const DEFAULT_TIMEOUT = 30000;
5
+
6
+ export interface ClientConfig {
7
+ baseUrl?: string;
8
+ timeout?: number;
9
+ }
10
+
11
+ export function createHttpClient(config: ClientConfig = {}): AxiosInstance {
12
+ const client = axios.create({
13
+ baseURL: config.baseUrl || 'http://localhost:3000',
14
+ timeout: config.timeout || DEFAULT_TIMEOUT,
15
+ headers: { 'Content-Type': 'application/json' },
16
+ });
17
+
18
+ client.interceptors.response.use(
19
+ (response) => response,
20
+ (error: AxiosError<{ ok: boolean; error?: string; message?: string }>) => {
21
+ if (error.response?.data?.error) {
22
+ const apiCode = error.response.data.error;
23
+ const apiMessage = error.response.data.message || 'Unknown API error';
24
+
25
+ if (apiCode === 'TX_FAILED') {
26
+ const hexMatch = apiMessage.match(/0x([0-9a-fA-F]+)/);
27
+ if (hexMatch) {
28
+ const errorCode = parseInt(hexMatch[1], 16);
29
+ const anchor = ANCHOR_ERROR_MAP[errorCode];
30
+ if (anchor) {
31
+ throw new SdkError(anchor.code, anchor.message);
32
+ }
33
+ }
34
+ }
35
+
36
+ throw new SdkError(apiCode, apiMessage);
37
+ }
38
+ if (error.response?.data?.message) {
39
+ throw new SdkError('API_ERROR', error.response.data.message);
40
+ }
41
+ if (error.code === 'ECONNABORTED') {
42
+ throw new SdkError('TIMEOUT', 'Request timeout — is the Silkyway server running?');
43
+ }
44
+ throw new SdkError('NETWORK_ERROR', 'Network error — is the Silkyway server running?');
45
+ },
46
+ );
47
+
48
+ return client;
49
+ }
@@ -0,0 +1,210 @@
1
+ import { Keypair, Transaction } from '@solana/web3.js';
2
+ import bs58 from 'bs58';
3
+ import { loadConfig, saveConfig, getWallet, getApiUrl, AccountInfo } from '../config.js';
4
+ import { createHttpClient } from '../client.js';
5
+ import { outputSuccess } from '../output.js';
6
+ import { SdkError, toSilkysigError } from '../errors.js';
7
+ import { validateAddress, validateAmount } from '../validate.js';
8
+ import { resolveRecipient } from '../contacts.js';
9
+
10
+ interface OperatorSlot {
11
+ index: number;
12
+ perTxLimit: string;
13
+ }
14
+
15
+ interface ByOperatorAccount {
16
+ pda: string;
17
+ owner: string;
18
+ mint: string;
19
+ mintDecimals: number;
20
+ isPaused: boolean;
21
+ balance: number;
22
+ operatorSlot: OperatorSlot | null;
23
+ }
24
+
25
+ interface AccountDetail {
26
+ pda: string;
27
+ owner: string;
28
+ mint: string;
29
+ mintDecimals: number;
30
+ isPaused: boolean;
31
+ balance: number;
32
+ operators: Array<{
33
+ index: number;
34
+ pubkey: string;
35
+ perTxLimit: string;
36
+ }>;
37
+ }
38
+
39
+ export async function accountSync(opts: { wallet?: string; account?: string }) {
40
+ const config = loadConfig();
41
+ const wallet = getWallet(config, opts.wallet);
42
+ const client = createHttpClient({ baseUrl: getApiUrl(config) });
43
+
44
+ if (opts.account) {
45
+ // Direct PDA sync
46
+ validateAddress(opts.account, 'account');
47
+ const res = await client.get(`/api/account/${opts.account}`);
48
+ const acct: AccountDetail = res.data.data;
49
+
50
+ // Verify wallet is an operator on this account
51
+ const op = acct.operators.find((o) => o.pubkey === wallet.address);
52
+ if (!op) {
53
+ throw new SdkError(
54
+ 'NOT_OPERATOR',
55
+ `Wallet "${wallet.label}" (${wallet.address}) is not an operator on account ${opts.account}`,
56
+ );
57
+ }
58
+
59
+ const accountInfo: AccountInfo = {
60
+ pda: acct.pda,
61
+ owner: acct.owner,
62
+ mint: acct.mint,
63
+ mintDecimals: acct.mintDecimals,
64
+ operatorIndex: op.index,
65
+ perTxLimit: Number(op.perTxLimit),
66
+ syncedAt: new Date().toISOString(),
67
+ };
68
+ config.account = accountInfo;
69
+ saveConfig(config);
70
+
71
+ outputSuccess({
72
+ action: 'sync',
73
+ pda: acct.pda,
74
+ owner: acct.owner,
75
+ balance: acct.balance,
76
+ perTxLimit: Number(op.perTxLimit),
77
+ mint: acct.mint,
78
+ });
79
+ return;
80
+ }
81
+
82
+ // Discover by operator
83
+ const res = await client.get(`/api/account/by-operator/${wallet.address}`);
84
+ const accounts: ByOperatorAccount[] = res.data.data.accounts;
85
+
86
+ if (accounts.length === 0) {
87
+ outputSuccess({
88
+ action: 'sync',
89
+ found: 0,
90
+ message: `No account found for wallet "${wallet.label}" (${wallet.address}).`,
91
+ hint: `Ask your human to set up your account at:\n https://app.silkyway.ai/account/setup?agent=${wallet.address}`,
92
+ });
93
+ return;
94
+ }
95
+
96
+ // Auto-select first account
97
+ const selected = accounts[0];
98
+ const slot = selected.operatorSlot!;
99
+
100
+ const accountInfo: AccountInfo = {
101
+ pda: selected.pda,
102
+ owner: selected.owner,
103
+ mint: selected.mint,
104
+ mintDecimals: selected.mintDecimals,
105
+ operatorIndex: slot.index,
106
+ perTxLimit: Number(slot.perTxLimit),
107
+ syncedAt: new Date().toISOString(),
108
+ };
109
+ config.account = accountInfo;
110
+ saveConfig(config);
111
+
112
+ if (accounts.length === 1) {
113
+ outputSuccess({
114
+ action: 'sync',
115
+ pda: selected.pda,
116
+ owner: selected.owner,
117
+ balance: selected.balance,
118
+ perTxLimit: Number(slot.perTxLimit),
119
+ mint: selected.mint,
120
+ });
121
+ } else {
122
+ outputSuccess({
123
+ action: 'sync',
124
+ pda: selected.pda,
125
+ owner: selected.owner,
126
+ balance: selected.balance,
127
+ perTxLimit: Number(slot.perTxLimit),
128
+ mint: selected.mint,
129
+ allAccounts: accounts.map((a) => ({
130
+ pda: a.pda,
131
+ owner: a.owner,
132
+ balance: a.balance,
133
+ })),
134
+ hint: 'To use a different account: silk account sync --account <pda>',
135
+ });
136
+ }
137
+ }
138
+
139
+ export async function accountStatus(opts: { wallet?: string }) {
140
+ const config = loadConfig();
141
+ getWallet(config, opts.wallet); // validate wallet exists
142
+
143
+ if (!config.account) {
144
+ throw new SdkError('NO_ACCOUNT', 'No account synced. Run: silk account sync');
145
+ }
146
+
147
+ const client = createHttpClient({ baseUrl: getApiUrl(config) });
148
+ const res = await client.get(`/api/account/${config.account.pda}`);
149
+ const acct: AccountDetail = res.data.data;
150
+
151
+ const op = acct.operators.find((o) => o.index === config.account!.operatorIndex);
152
+ const perTxLimit = op ? Number(op.perTxLimit) : config.account.perTxLimit;
153
+ const perTxLimitHuman = acct.mintDecimals > 0 ? perTxLimit / 10 ** acct.mintDecimals : perTxLimit;
154
+
155
+ outputSuccess({
156
+ action: 'status',
157
+ pda: acct.pda,
158
+ owner: acct.owner,
159
+ balance: acct.balance,
160
+ mint: acct.mint,
161
+ isPaused: acct.isPaused,
162
+ operatorIndex: config.account.operatorIndex,
163
+ perTxLimit: perTxLimitHuman,
164
+ });
165
+ }
166
+
167
+ export async function accountSend(recipient: string, amount: string, opts: { memo?: string; wallet?: string }) {
168
+ recipient = resolveRecipient(recipient);
169
+ const config = loadConfig();
170
+ const wallet = getWallet(config, opts.wallet);
171
+
172
+ if (!config.account) {
173
+ throw new SdkError('NO_ACCOUNT', 'No account synced. Run: silk account sync');
174
+ }
175
+
176
+ validateAddress(recipient, 'recipient');
177
+ const amountNum = validateAmount(amount);
178
+
179
+ // Convert to smallest units
180
+ const amountRaw = Math.round(amountNum * 10 ** config.account.mintDecimals);
181
+
182
+ const client = createHttpClient({ baseUrl: getApiUrl(config) });
183
+
184
+ // 1. Build unsigned transaction
185
+ const buildRes = await client.post('/api/account/transfer', {
186
+ signer: wallet.address,
187
+ accountPda: config.account.pda,
188
+ recipient,
189
+ amount: amountRaw,
190
+ });
191
+
192
+ const { transaction: txBase64 } = buildRes.data.data;
193
+
194
+ // 2. Sign the transaction
195
+ const tx = Transaction.from(Buffer.from(txBase64, 'base64'));
196
+ const keypair = Keypair.fromSecretKey(bs58.decode(wallet.privateKey));
197
+ tx.sign(keypair);
198
+
199
+ // 3. Submit signed transaction
200
+ try {
201
+ const submitRes = await client.post('/api/tx/submit', {
202
+ signedTx: tx.serialize().toString('base64'),
203
+ });
204
+
205
+ const { txid } = submitRes.data.data;
206
+ outputSuccess({ action: 'send', txid, amount: amountNum, recipient });
207
+ } catch (err) {
208
+ throw toSilkysigError(err);
209
+ }
210
+ }
@@ -0,0 +1,14 @@
1
+ import { loadConfig, getWallet, getApiUrl } from '../config.js';
2
+ import { createHttpClient } from '../client.js';
3
+ import { outputSuccess } from '../output.js';
4
+
5
+ export async function balance(opts: { wallet?: string }) {
6
+ const config = loadConfig();
7
+ const wallet = getWallet(config, opts.wallet);
8
+ const client = createHttpClient({ baseUrl: getApiUrl(config) });
9
+
10
+ const res = await client.get(`/api/wallet/${wallet.address}/balance`);
11
+ const data = res.data.data;
12
+
13
+ outputSuccess({ wallet: wallet.label, address: wallet.address, sol: data.sol, tokens: data.tokens });
14
+ }
@@ -0,0 +1,34 @@
1
+ import { Keypair, Transaction } from '@solana/web3.js';
2
+ import bs58 from 'bs58';
3
+ import { loadConfig, getWallet, getApiUrl } from '../config.js';
4
+ import { createHttpClient } from '../client.js';
5
+ import { outputSuccess } from '../output.js';
6
+ import { validateCancel } from '../validate.js';
7
+
8
+ export async function cancel(transferPda: string, opts: { wallet?: string }) {
9
+ const config = loadConfig();
10
+ const wallet = getWallet(config, opts.wallet);
11
+ const client = createHttpClient({ baseUrl: getApiUrl(config) });
12
+
13
+ await validateCancel(client, transferPda, wallet.address);
14
+
15
+ // 1. Build unsigned cancel tx
16
+ const buildRes = await client.post('/api/tx/cancel-transfer', {
17
+ canceller: wallet.address,
18
+ transferPda,
19
+ });
20
+
21
+ const txBase64 = buildRes.data.data.transaction;
22
+
23
+ // 2. Sign
24
+ const tx = Transaction.from(Buffer.from(txBase64, 'base64'));
25
+ const keypair = Keypair.fromSecretKey(bs58.decode(wallet.privateKey));
26
+ tx.sign(keypair);
27
+
28
+ // 3. Submit
29
+ const submitRes = await client.post('/api/tx/submit', {
30
+ signedTx: tx.serialize().toString('base64'),
31
+ });
32
+
33
+ outputSuccess({ action: 'cancel', transferPda, txid: submitRes.data.data.txid });
34
+ }
@@ -0,0 +1,14 @@
1
+ import { loadConfig, getApiUrl, getAgentId } from '../config.js';
2
+ import { createHttpClient } from '../client.js';
3
+ import { outputSuccess } from '../output.js';
4
+
5
+ export async function chat(message: string) {
6
+ const config = loadConfig();
7
+ const agentId = getAgentId(config);
8
+ const client = createHttpClient({ baseUrl: getApiUrl(config) });
9
+
10
+ const res = await client.post('/chat', { agentId, message });
11
+ const data = res.data.data;
12
+
13
+ outputSuccess({ message: data.message });
14
+ }
@@ -0,0 +1,34 @@
1
+ import { Keypair, Transaction } from '@solana/web3.js';
2
+ import bs58 from 'bs58';
3
+ import { loadConfig, getWallet, getApiUrl } from '../config.js';
4
+ import { createHttpClient } from '../client.js';
5
+ import { outputSuccess } from '../output.js';
6
+ import { validateClaim } from '../validate.js';
7
+
8
+ export async function claim(transferPda: string, opts: { wallet?: string }) {
9
+ const config = loadConfig();
10
+ const wallet = getWallet(config, opts.wallet);
11
+ const client = createHttpClient({ baseUrl: getApiUrl(config) });
12
+
13
+ await validateClaim(client, transferPda, wallet.address);
14
+
15
+ // 1. Build unsigned claim tx
16
+ const buildRes = await client.post('/api/tx/claim-transfer', {
17
+ claimer: wallet.address,
18
+ transferPda,
19
+ });
20
+
21
+ const txBase64 = buildRes.data.data.transaction;
22
+
23
+ // 2. Sign
24
+ const tx = Transaction.from(Buffer.from(txBase64, 'base64'));
25
+ const keypair = Keypair.fromSecretKey(bs58.decode(wallet.privateKey));
26
+ tx.sign(keypair);
27
+
28
+ // 3. Submit
29
+ const submitRes = await client.post('/api/tx/submit', {
30
+ signedTx: tx.serialize().toString('base64'),
31
+ });
32
+
33
+ outputSuccess({ action: 'claim', transferPda, txid: submitRes.data.data.txid });
34
+ }
@@ -0,0 +1,46 @@
1
+ import { loadConfig, saveConfig, getApiUrl, getCluster, SolanaCluster } from '../config.js';
2
+ import { outputSuccess } from '../output.js';
3
+ import { SdkError } from '../errors.js';
4
+
5
+ export async function configSetApiUrl(url: string) {
6
+ const config = loadConfig();
7
+ config.apiUrl = url;
8
+ saveConfig(config);
9
+ outputSuccess({ apiUrl: url });
10
+ }
11
+
12
+ export async function configGetApiUrl() {
13
+ const config = loadConfig();
14
+ outputSuccess({ apiUrl: getApiUrl(config) });
15
+ }
16
+
17
+ export async function configResetApiUrl() {
18
+ const config = loadConfig();
19
+ delete config.apiUrl;
20
+ saveConfig(config);
21
+ outputSuccess({ apiUrl: getApiUrl(config), message: 'Reset to default' });
22
+ }
23
+
24
+ const VALID_CLUSTERS: SolanaCluster[] = ['mainnet-beta', 'devnet'];
25
+
26
+ export async function configSetCluster(cluster: string) {
27
+ if (!VALID_CLUSTERS.includes(cluster as SolanaCluster)) {
28
+ throw new SdkError('INVALID_CLUSTER', `Invalid cluster "${cluster}". Valid options: ${VALID_CLUSTERS.join(', ')}`);
29
+ }
30
+ const config = loadConfig();
31
+ config.cluster = cluster as SolanaCluster;
32
+ saveConfig(config);
33
+ outputSuccess({ cluster, apiUrl: getApiUrl(config) });
34
+ }
35
+
36
+ export async function configGetCluster() {
37
+ const config = loadConfig();
38
+ outputSuccess({ cluster: getCluster(config), apiUrl: getApiUrl(config) });
39
+ }
40
+
41
+ export async function configResetCluster() {
42
+ const config = loadConfig();
43
+ delete config.cluster;
44
+ saveConfig(config);
45
+ outputSuccess({ cluster: getCluster(config), apiUrl: getApiUrl(config), message: 'Reset to default (mainnet-beta)' });
46
+ }
@@ -0,0 +1,26 @@
1
+ import { addContact, removeContact, getContact, listContacts } from '../contacts.js';
2
+ import { outputSuccess } from '../output.js';
3
+ import { SdkError } from '../errors.js';
4
+
5
+ export async function contactsAdd(name: string, address: string) {
6
+ addContact(name, address);
7
+ outputSuccess({ action: 'contact_added', name: name.toLowerCase(), address });
8
+ }
9
+
10
+ export async function contactsRemove(name: string) {
11
+ removeContact(name);
12
+ outputSuccess({ action: 'contact_removed', name: name.toLowerCase() });
13
+ }
14
+
15
+ export async function contactsList() {
16
+ const contacts = listContacts();
17
+ outputSuccess({ action: 'contacts_list', contacts });
18
+ }
19
+
20
+ export async function contactsGet(name: string) {
21
+ const contact = getContact(name);
22
+ if (!contact) {
23
+ throw new SdkError('CONTACT_NOT_FOUND', `Contact "${name.toLowerCase()}" not found`);
24
+ }
25
+ outputSuccess({ action: 'contact_get', name: contact.name, address: contact.address });
26
+ }
@@ -0,0 +1,44 @@
1
+ import { Keypair } from '@solana/web3.js';
2
+ import bs58 from 'bs58';
3
+ import { loadConfig, saveConfig, ensureAgentId } from '../config.js';
4
+ import { initContacts } from '../contacts.js';
5
+ import { outputSuccess } from '../output.js';
6
+
7
+ export async function init() {
8
+ const config = loadConfig();
9
+ let walletCreated = false;
10
+ let mainWallet = config.wallets.find((w) => w.label === 'main');
11
+
12
+ if (!mainWallet) {
13
+ const keypair = Keypair.generate();
14
+ const address = keypair.publicKey.toBase58();
15
+ const privateKey = bs58.encode(keypair.secretKey);
16
+
17
+ mainWallet = { label: 'main', address, privateKey };
18
+ config.wallets.push(mainWallet);
19
+
20
+ if (config.wallets.length === 1) {
21
+ config.defaultWallet = 'main';
22
+ }
23
+
24
+ walletCreated = true;
25
+ }
26
+
27
+ const agentIdResult = ensureAgentId(config);
28
+ const contactsCreated = initContacts();
29
+
30
+ if (walletCreated || agentIdResult.created) {
31
+ saveConfig(config);
32
+ }
33
+
34
+ outputSuccess({
35
+ action: 'init',
36
+ wallet_created: walletCreated,
37
+ wallet_label: 'main',
38
+ wallet_address: mainWallet.address,
39
+ agent_id_created: agentIdResult.created,
40
+ agent_id: agentIdResult.agentId,
41
+ contacts_created: contactsCreated,
42
+ message: (walletCreated || agentIdResult.created || contactsCreated) ? 'Initialization complete' : 'Already initialized',
43
+ });
44
+ }