facinet 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/README.md +318 -0
- package/dist/commands/connect.d.ts +7 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +80 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/facilitator.d.ts +40 -0
- package/dist/commands/facilitator.d.ts.map +1 -0
- package/dist/commands/facilitator.js +208 -0
- package/dist/commands/facilitator.js.map +1 -0
- package/dist/commands/pay.d.ts +14 -0
- package/dist/commands/pay.d.ts.map +1 -0
- package/dist/commands/pay.js +155 -0
- package/dist/commands/pay.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/api.d.ts +42 -0
- package/dist/utils/api.d.ts.map +1 -0
- package/dist/utils/api.js +145 -0
- package/dist/utils/api.js.map +1 -0
- package/dist/utils/config.d.ts +25 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +64 -0
- package/dist/utils/config.js.map +1 -0
- package/package.json +43 -0
- package/src/commands/connect.ts +84 -0
- package/src/commands/facilitator.ts +256 -0
- package/src/commands/pay.ts +179 -0
- package/src/index.ts +108 -0
- package/src/utils/api.ts +164 -0
- package/src/utils/config.ts +67 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connect Command
|
|
3
|
+
*
|
|
4
|
+
* Connect wallet to Facinet CLI
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { Wallet } from 'ethers';
|
|
10
|
+
import { saveConfig, getConfig } from '../utils/config';
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
|
|
13
|
+
export async function connectCommand() {
|
|
14
|
+
console.log(chalk.cyan('\nš Connect Wallet to Facinet\n'));
|
|
15
|
+
|
|
16
|
+
const { method } = await inquirer.prompt([
|
|
17
|
+
{
|
|
18
|
+
type: 'list',
|
|
19
|
+
name: 'method',
|
|
20
|
+
message: 'How would you like to connect?',
|
|
21
|
+
choices: [
|
|
22
|
+
{ name: 'Enter Private Key (for testing)', value: 'privateKey' },
|
|
23
|
+
{ name: 'Generate New Wallet', value: 'generate' },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
if (method === 'privateKey') {
|
|
29
|
+
const { privateKey } = await inquirer.prompt([
|
|
30
|
+
{
|
|
31
|
+
type: 'password',
|
|
32
|
+
name: 'privateKey',
|
|
33
|
+
message: 'Enter your private key:',
|
|
34
|
+
mask: '*',
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const wallet = new Wallet(privateKey);
|
|
40
|
+
saveConfig({ privateKey, address: wallet.address });
|
|
41
|
+
|
|
42
|
+
console.log(chalk.green('\nā
Wallet connected successfully!'));
|
|
43
|
+
console.log(chalk.gray(`Address: ${wallet.address}`));
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.log(chalk.red('\nā Invalid private key'));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
const spinner = ora('Generating new wallet...').start();
|
|
50
|
+
|
|
51
|
+
const wallet = Wallet.createRandom();
|
|
52
|
+
|
|
53
|
+
spinner.succeed('Wallet generated!');
|
|
54
|
+
|
|
55
|
+
console.log(chalk.green('\nā
New wallet created!'));
|
|
56
|
+
console.log(chalk.yellow('\nā ļø IMPORTANT: Save your private key securely!'));
|
|
57
|
+
console.log(chalk.gray(`\nAddress: ${wallet.address}`));
|
|
58
|
+
console.log(chalk.gray(`Private Key: ${wallet.privateKey}`));
|
|
59
|
+
console.log(chalk.gray(`Mnemonic: ${wallet.mnemonic?.phrase}\n`));
|
|
60
|
+
|
|
61
|
+
const { confirm } = await inquirer.prompt([
|
|
62
|
+
{
|
|
63
|
+
type: 'confirm',
|
|
64
|
+
name: 'confirm',
|
|
65
|
+
message: 'Have you saved your private key?',
|
|
66
|
+
default: false,
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
if (!confirm) {
|
|
71
|
+
console.log(chalk.red('\nPlease save your private key before continuing.'));
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
saveConfig({ privateKey: wallet.privateKey, address: wallet.address });
|
|
76
|
+
console.log(chalk.green('ā
Configuration saved!'));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check network
|
|
80
|
+
const config = getConfig();
|
|
81
|
+
console.log(chalk.cyan('\nš” Network Configuration:'));
|
|
82
|
+
console.log(chalk.gray(`Network: ${config.network || 'avalanche-fuji'}`));
|
|
83
|
+
console.log(chalk.gray(`API URL: ${config.apiUrl || 'http://localhost:3000'}\n`));
|
|
84
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Facilitator Commands
|
|
3
|
+
*
|
|
4
|
+
* Create and manage facilitators
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import { Wallet } from 'ethers';
|
|
11
|
+
import { getConfig } from '../utils/config';
|
|
12
|
+
import {
|
|
13
|
+
createFacilitator,
|
|
14
|
+
listFacilitators,
|
|
15
|
+
getFacilitatorStatus,
|
|
16
|
+
getFacilitatorBalance,
|
|
17
|
+
} from '../utils/api';
|
|
18
|
+
|
|
19
|
+
interface CreateOptions {
|
|
20
|
+
name?: string;
|
|
21
|
+
recipient?: string;
|
|
22
|
+
url: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ListOptions {
|
|
26
|
+
url: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface StatusOptions {
|
|
30
|
+
url: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a new facilitator
|
|
35
|
+
*/
|
|
36
|
+
async function create(options: CreateOptions) {
|
|
37
|
+
console.log(chalk.cyan('\nš Create New Facilitator\n'));
|
|
38
|
+
|
|
39
|
+
// Check if wallet is connected
|
|
40
|
+
const config = getConfig();
|
|
41
|
+
if (!config.privateKey) {
|
|
42
|
+
console.log(chalk.red('ā No wallet connected. Run `facinet connect` first.'));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Get facilitator details
|
|
47
|
+
const answers = await inquirer.prompt([
|
|
48
|
+
{
|
|
49
|
+
type: 'input',
|
|
50
|
+
name: 'name',
|
|
51
|
+
message: 'Facilitator name:',
|
|
52
|
+
when: !options.name,
|
|
53
|
+
validate: (input: string) => {
|
|
54
|
+
if (input.length < 3 || input.length > 50) {
|
|
55
|
+
return 'Name must be between 3 and 50 characters';
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: 'input',
|
|
62
|
+
name: 'recipient',
|
|
63
|
+
message: 'Payment recipient address:',
|
|
64
|
+
when: !options.recipient,
|
|
65
|
+
default: config.address,
|
|
66
|
+
validate: (input: string) => {
|
|
67
|
+
if (!input.match(/^0x[a-fA-F0-9]{40}$/)) {
|
|
68
|
+
return 'Invalid Ethereum address';
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
const name = options.name || answers.name;
|
|
76
|
+
const recipient = options.recipient || answers.recipient;
|
|
77
|
+
|
|
78
|
+
console.log(chalk.gray(`\nš Facilitator Details:`));
|
|
79
|
+
console.log(chalk.gray(` Name: ${name}`));
|
|
80
|
+
console.log(chalk.gray(` Recipient: ${recipient}`));
|
|
81
|
+
console.log(chalk.gray(` Creator: ${config.address}\n`));
|
|
82
|
+
|
|
83
|
+
const { confirm } = await inquirer.prompt([
|
|
84
|
+
{
|
|
85
|
+
type: 'confirm',
|
|
86
|
+
name: 'confirm',
|
|
87
|
+
message: 'Create facilitator? (Requires 1 USDC registration fee)',
|
|
88
|
+
default: true,
|
|
89
|
+
},
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
if (!confirm) {
|
|
93
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const spinner = ora('Creating facilitator...').start();
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const wallet = new Wallet(config.privateKey);
|
|
101
|
+
|
|
102
|
+
// Generate facilitator wallet
|
|
103
|
+
const facilitatorWallet = Wallet.createRandom();
|
|
104
|
+
|
|
105
|
+
spinner.text = 'Registering facilitator...';
|
|
106
|
+
|
|
107
|
+
const result = await createFacilitator(options.url, {
|
|
108
|
+
name,
|
|
109
|
+
facilitatorWallet: facilitatorWallet.address,
|
|
110
|
+
facilitatorPrivateKey: facilitatorWallet.privateKey,
|
|
111
|
+
paymentRecipient: recipient,
|
|
112
|
+
createdBy: wallet.address,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
spinner.succeed('Facilitator created!');
|
|
116
|
+
|
|
117
|
+
console.log(chalk.green('\nā
Facilitator created successfully!'));
|
|
118
|
+
console.log(chalk.gray(`\nš Details:`));
|
|
119
|
+
console.log(chalk.gray(` ID: ${result.id}`));
|
|
120
|
+
console.log(chalk.gray(` Name: ${name}`));
|
|
121
|
+
console.log(chalk.gray(` Wallet: ${facilitatorWallet.address}`));
|
|
122
|
+
console.log(chalk.gray(` Status: ${result.status}`));
|
|
123
|
+
|
|
124
|
+
console.log(chalk.yellow(`\nā ļø IMPORTANT: Save facilitator wallet private key!`));
|
|
125
|
+
console.log(chalk.gray(` Private Key: ${facilitatorWallet.privateKey}\n`));
|
|
126
|
+
|
|
127
|
+
console.log(chalk.cyan('š” Next steps:'));
|
|
128
|
+
console.log(chalk.gray(` 1. Fund facilitator wallet with AVAX for gas`));
|
|
129
|
+
console.log(chalk.gray(` 2. Check status: facinet facilitator status ${result.id}`));
|
|
130
|
+
console.log(chalk.gray(` 3. Monitor balance: facinet facilitator balance ${result.id}\n`));
|
|
131
|
+
} catch (error: any) {
|
|
132
|
+
spinner.fail('Failed to create facilitator');
|
|
133
|
+
console.log(chalk.red(`\nā Error: ${error.message}\n`));
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* List all facilitators
|
|
140
|
+
*/
|
|
141
|
+
async function list(options: ListOptions) {
|
|
142
|
+
console.log(chalk.cyan('\nš Active Facilitators\n'));
|
|
143
|
+
|
|
144
|
+
const spinner = ora('Loading facilitators...').start();
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const facilitators = await listFacilitators(options.url);
|
|
148
|
+
|
|
149
|
+
spinner.stop();
|
|
150
|
+
|
|
151
|
+
if (facilitators.length === 0) {
|
|
152
|
+
console.log(chalk.yellow('No active facilitators found.\n'));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log(chalk.gray(`Found ${facilitators.length} active facilitator(s):\n`));
|
|
157
|
+
|
|
158
|
+
facilitators.forEach((fac: any, index: number) => {
|
|
159
|
+
console.log(chalk.cyan(`${index + 1}. ${fac.name}`));
|
|
160
|
+
console.log(chalk.gray(` ID: ${fac.id}`));
|
|
161
|
+
console.log(chalk.gray(` Wallet: ${fac.facilitatorWallet}`));
|
|
162
|
+
console.log(chalk.gray(` Status: ${getStatusEmoji(fac.status)} ${fac.status.toUpperCase()}`));
|
|
163
|
+
console.log(chalk.gray(` Payments: ${fac.totalPayments}`));
|
|
164
|
+
console.log(chalk.gray(` Created by: ${fac.createdBy}`));
|
|
165
|
+
console.log('');
|
|
166
|
+
});
|
|
167
|
+
} catch (error: any) {
|
|
168
|
+
spinner.fail('Failed to load facilitators');
|
|
169
|
+
console.log(chalk.red(`\nā Error: ${error.message}\n`));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Check facilitator status
|
|
176
|
+
*/
|
|
177
|
+
async function status(id: string, options: StatusOptions) {
|
|
178
|
+
console.log(chalk.cyan(`\nš Facilitator Status\n`));
|
|
179
|
+
|
|
180
|
+
const spinner = ora('Checking status...').start();
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const fac = await getFacilitatorStatus(options.url, id);
|
|
184
|
+
|
|
185
|
+
spinner.stop();
|
|
186
|
+
|
|
187
|
+
console.log(chalk.cyan(`${fac.name}`));
|
|
188
|
+
console.log(chalk.gray(`ID: ${fac.id}\n`));
|
|
189
|
+
|
|
190
|
+
console.log(chalk.gray(`Status: ${getStatusEmoji(fac.status)} ${chalk.bold(fac.status.toUpperCase())}`));
|
|
191
|
+
console.log(chalk.gray(`Wallet: ${fac.facilitatorWallet}`));
|
|
192
|
+
console.log(chalk.gray(`Recipient: ${fac.paymentRecipient}`));
|
|
193
|
+
console.log(chalk.gray(`Total Payments: ${fac.totalPayments}`));
|
|
194
|
+
console.log(chalk.gray(`Gas Balance: ${fac.gasBalance || 'Unknown'} AVAX`));
|
|
195
|
+
console.log(chalk.gray(`Created: ${new Date(fac.lastUsed).toLocaleString()}\n`));
|
|
196
|
+
|
|
197
|
+
if (fac.status === 'needs_funding') {
|
|
198
|
+
console.log(chalk.yellow('ā ļø Facilitator needs AVAX for gas fees'));
|
|
199
|
+
console.log(chalk.gray(` Send at least 0.1 AVAX to: ${fac.facilitatorWallet}\n`));
|
|
200
|
+
}
|
|
201
|
+
} catch (error: any) {
|
|
202
|
+
spinner.fail('Failed to get status');
|
|
203
|
+
console.log(chalk.red(`\nā Error: ${error.message}\n`));
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Check facilitator balance
|
|
210
|
+
*/
|
|
211
|
+
async function balance(id: string, options: StatusOptions) {
|
|
212
|
+
console.log(chalk.cyan(`\nš° Facilitator Balance\n`));
|
|
213
|
+
|
|
214
|
+
const spinner = ora('Checking balance...').start();
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const result = await getFacilitatorBalance(options.url, id);
|
|
218
|
+
|
|
219
|
+
spinner.stop();
|
|
220
|
+
|
|
221
|
+
console.log(chalk.gray(`Facilitator: ${result.name}`));
|
|
222
|
+
console.log(chalk.gray(`Wallet: ${result.wallet}\n`));
|
|
223
|
+
|
|
224
|
+
console.log(chalk.cyan(`AVAX Balance: ${chalk.bold(result.balance + ' AVAX')}`));
|
|
225
|
+
console.log(chalk.gray(`Status: ${getStatusEmoji(result.status)} ${result.status.toUpperCase()}\n`));
|
|
226
|
+
|
|
227
|
+
if (parseFloat(result.balance) < 0.1) {
|
|
228
|
+
console.log(chalk.yellow('ā ļø Low balance! Facilitator may become inactive.'));
|
|
229
|
+
console.log(chalk.gray(` Recommended: At least 0.1 AVAX\n`));
|
|
230
|
+
}
|
|
231
|
+
} catch (error: any) {
|
|
232
|
+
spinner.fail('Failed to check balance');
|
|
233
|
+
console.log(chalk.red(`\nā Error: ${error.message}\n`));
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function getStatusEmoji(status: string): string {
|
|
239
|
+
switch (status) {
|
|
240
|
+
case 'active':
|
|
241
|
+
return 'ā
';
|
|
242
|
+
case 'needs_funding':
|
|
243
|
+
return 'ā ļø';
|
|
244
|
+
case 'inactive':
|
|
245
|
+
return 'ā';
|
|
246
|
+
default:
|
|
247
|
+
return 'ā';
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const facilitatorCommand = {
|
|
252
|
+
create,
|
|
253
|
+
list,
|
|
254
|
+
status,
|
|
255
|
+
balance,
|
|
256
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pay Command
|
|
3
|
+
*
|
|
4
|
+
* Make a payment via x402 facilitator network
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import { Wallet, JsonRpcProvider, Contract } from 'ethers';
|
|
11
|
+
import { getConfig } from '../utils/config';
|
|
12
|
+
import { selectRandomFacilitator, getFacilitatorById } from '../utils/api';
|
|
13
|
+
|
|
14
|
+
interface PayOptions {
|
|
15
|
+
amount: string;
|
|
16
|
+
to?: string;
|
|
17
|
+
chain: string;
|
|
18
|
+
network?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const CHAINS: Record<string, any> = {
|
|
22
|
+
avalanche: {
|
|
23
|
+
name: 'Avalanche Fuji',
|
|
24
|
+
rpcUrl: 'https://api.avax-test.network/ext/bc/C/rpc',
|
|
25
|
+
usdcAddress: '0x5425890298aed601595a70AB815c96711a31Bc65',
|
|
26
|
+
chainId: 43113,
|
|
27
|
+
},
|
|
28
|
+
ethereum: {
|
|
29
|
+
name: 'Ethereum Sepolia',
|
|
30
|
+
rpcUrl: 'https://rpc.sepolia.org',
|
|
31
|
+
usdcAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
|
|
32
|
+
chainId: 11155111,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const ERC3009_ABI = [
|
|
37
|
+
'function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s) external',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
export async function payCommand(options: PayOptions) {
|
|
41
|
+
console.log(chalk.cyan('\nš³ Make Payment via x402 Network\n'));
|
|
42
|
+
|
|
43
|
+
// Check if wallet is connected
|
|
44
|
+
const config = getConfig();
|
|
45
|
+
if (!config.privateKey) {
|
|
46
|
+
console.log(chalk.red('ā No wallet connected. Run `facinet connect` first.'));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Get recipient if not provided
|
|
51
|
+
let recipient = options.to;
|
|
52
|
+
if (!recipient) {
|
|
53
|
+
const answer = await inquirer.prompt([
|
|
54
|
+
{
|
|
55
|
+
type: 'input',
|
|
56
|
+
name: 'recipient',
|
|
57
|
+
message: 'Recipient address:',
|
|
58
|
+
validate: (input: string) => {
|
|
59
|
+
if (!input.match(/^0x[a-fA-F0-9]{40}$/)) {
|
|
60
|
+
return 'Invalid Ethereum address';
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
]);
|
|
66
|
+
recipient = answer.recipient;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const chain = CHAINS[options.chain];
|
|
70
|
+
if (!chain) {
|
|
71
|
+
console.log(chalk.red(`ā Unsupported chain: ${options.chain}`));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(chalk.gray(`\nš Payment Details:`));
|
|
76
|
+
console.log(chalk.gray(` Amount: ${options.amount} USDC`));
|
|
77
|
+
console.log(chalk.gray(` To: ${recipient}`));
|
|
78
|
+
console.log(chalk.gray(` Chain: ${chain.name}`));
|
|
79
|
+
console.log(chalk.gray(` Your Address: ${config.address}\n`));
|
|
80
|
+
|
|
81
|
+
const { confirm } = await inquirer.prompt([
|
|
82
|
+
{
|
|
83
|
+
type: 'confirm',
|
|
84
|
+
name: 'confirm',
|
|
85
|
+
message: 'Proceed with payment?',
|
|
86
|
+
default: true,
|
|
87
|
+
},
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
if (!confirm) {
|
|
91
|
+
console.log(chalk.yellow('Payment cancelled.'));
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let spinner = ora('Selecting random facilitator...').start();
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Get random facilitator
|
|
99
|
+
const facilitator = await selectRandomFacilitator(config.apiUrl || 'http://localhost:3000');
|
|
100
|
+
|
|
101
|
+
spinner.succeed(`Selected facilitator: ${chalk.green(facilitator.name)}`);
|
|
102
|
+
|
|
103
|
+
// Initialize wallet and provider
|
|
104
|
+
spinner = ora('Preparing transaction...').start();
|
|
105
|
+
const wallet = new Wallet(config.privateKey);
|
|
106
|
+
const provider = new JsonRpcProvider(chain.rpcUrl);
|
|
107
|
+
const connectedWallet = wallet.connect(provider);
|
|
108
|
+
|
|
109
|
+
// Create ERC-3009 authorization
|
|
110
|
+
const amount = BigInt(parseFloat(options.amount) * 1_000_000); // 6 decimals for USDC
|
|
111
|
+
const validAfter = Math.floor(Date.now() / 1000) - 60;
|
|
112
|
+
const validBefore = validAfter + 3600; // 1 hour validity
|
|
113
|
+
const nonce = '0x' + Array.from({ length: 64 }, () => Math.floor(Math.random() * 16).toString(16)).join('');
|
|
114
|
+
|
|
115
|
+
// EIP-712 Domain
|
|
116
|
+
const domain = {
|
|
117
|
+
name: 'USD Coin',
|
|
118
|
+
version: '2',
|
|
119
|
+
chainId: chain.chainId,
|
|
120
|
+
verifyingContract: chain.usdcAddress,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const types = {
|
|
124
|
+
TransferWithAuthorization: [
|
|
125
|
+
{ name: 'from', type: 'address' },
|
|
126
|
+
{ name: 'to', type: 'address' },
|
|
127
|
+
{ name: 'value', type: 'uint256' },
|
|
128
|
+
{ name: 'validAfter', type: 'uint256' },
|
|
129
|
+
{ name: 'validBefore', type: 'uint256' },
|
|
130
|
+
{ name: 'nonce', type: 'bytes32' },
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const value = {
|
|
135
|
+
from: wallet.address,
|
|
136
|
+
to: recipient,
|
|
137
|
+
value: amount,
|
|
138
|
+
validAfter: validAfter,
|
|
139
|
+
validBefore: validBefore,
|
|
140
|
+
nonce: nonce,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
spinner.text = 'Signing authorization...';
|
|
144
|
+
const signature = await connectedWallet.signTypedData(domain, types, value);
|
|
145
|
+
|
|
146
|
+
spinner.succeed('Authorization signed!');
|
|
147
|
+
|
|
148
|
+
// Send to facilitator for execution
|
|
149
|
+
spinner = ora('Submitting to facilitator...').start();
|
|
150
|
+
|
|
151
|
+
// In real implementation, call your API here
|
|
152
|
+
const paymentPayload = {
|
|
153
|
+
signature,
|
|
154
|
+
authorization: {
|
|
155
|
+
from: wallet.address,
|
|
156
|
+
to: recipient,
|
|
157
|
+
value: amount.toString(),
|
|
158
|
+
validAfter: validAfter.toString(),
|
|
159
|
+
validBefore: validBefore.toString(),
|
|
160
|
+
nonce: nonce,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
spinner.succeed('Payment submitted!');
|
|
165
|
+
|
|
166
|
+
console.log(chalk.green('\nā
Payment processed successfully!'));
|
|
167
|
+
console.log(chalk.gray(`\nš Payment Details:`));
|
|
168
|
+
console.log(chalk.gray(` Facilitator: ${facilitator.name}`));
|
|
169
|
+
console.log(chalk.gray(` Amount: ${options.amount} USDC`));
|
|
170
|
+
console.log(chalk.gray(` Recipient: ${recipient}`));
|
|
171
|
+
console.log(chalk.gray(` Chain: ${chain.name}\n`));
|
|
172
|
+
|
|
173
|
+
console.log(chalk.cyan('š” Tip: View transaction on block explorer'));
|
|
174
|
+
} catch (error: any) {
|
|
175
|
+
spinner.fail('Payment failed');
|
|
176
|
+
console.log(chalk.red(`\nā Error: ${error.message}\n`));
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Facinet CLI - x402 Facilitator Network
|
|
5
|
+
*
|
|
6
|
+
* Command-line tool for making payments and managing facilitators
|
|
7
|
+
* on the x402 payment network.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import { payCommand } from './commands/pay';
|
|
13
|
+
import { facilitatorCommand } from './commands/facilitator';
|
|
14
|
+
import { connectCommand } from './commands/connect';
|
|
15
|
+
|
|
16
|
+
const program = new Command();
|
|
17
|
+
|
|
18
|
+
// ASCII Art Banner
|
|
19
|
+
console.log(chalk.cyan(`
|
|
20
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
21
|
+
ā ā
|
|
22
|
+
ā āāāāāāāā āāāāāā āāāāāāāāāāāāāā ā
|
|
23
|
+
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
|
|
24
|
+
ā āāāāāā āāāāāāāāāāā āāāāāāāāā ā
|
|
25
|
+
ā āāāāāā āāāāāāāāāāā āāāāāāāāāāā
|
|
26
|
+
ā āāā āāā āāāāāāāāāāāāāāāāā āāāā
|
|
27
|
+
ā āāā āāā āāā āāāāāāāāāāāāā āāā
|
|
28
|
+
ā ā
|
|
29
|
+
ā x402 Facilitator Network CLI ā
|
|
30
|
+
ā ā
|
|
31
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
32
|
+
`));
|
|
33
|
+
|
|
34
|
+
program
|
|
35
|
+
.name('facinet')
|
|
36
|
+
.description('CLI tool for x402 Facilitator Network')
|
|
37
|
+
.version('1.0.0');
|
|
38
|
+
|
|
39
|
+
// Connect wallet command
|
|
40
|
+
program
|
|
41
|
+
.command('connect')
|
|
42
|
+
.description('Connect your wallet to Facinet')
|
|
43
|
+
.action(connectCommand);
|
|
44
|
+
|
|
45
|
+
// Pay command
|
|
46
|
+
program
|
|
47
|
+
.command('pay')
|
|
48
|
+
.description('Make a payment via x402 facilitator network')
|
|
49
|
+
.option('-a, --amount <amount>', 'Payment amount in USDC', '1')
|
|
50
|
+
.option('-t, --to <address>', 'Recipient address')
|
|
51
|
+
.option('-c, --chain <chain>', 'Blockchain network', 'avalanche')
|
|
52
|
+
.option('-n, --network <url>', 'Custom network URL')
|
|
53
|
+
.action(payCommand);
|
|
54
|
+
|
|
55
|
+
// Facilitator commands
|
|
56
|
+
const facilitator = program
|
|
57
|
+
.command('facilitator')
|
|
58
|
+
.alias('fac')
|
|
59
|
+
.description('Manage facilitators');
|
|
60
|
+
|
|
61
|
+
facilitator
|
|
62
|
+
.command('create')
|
|
63
|
+
.description('Create a new facilitator')
|
|
64
|
+
.option('-n, --name <name>', 'Facilitator name')
|
|
65
|
+
.option('-r, --recipient <address>', 'Payment recipient address')
|
|
66
|
+
.option('-u, --url <url>', 'API URL', 'https://x402-avalanche-chi.vercel.app')
|
|
67
|
+
.action(facilitatorCommand.create);
|
|
68
|
+
|
|
69
|
+
facilitator
|
|
70
|
+
.command('list')
|
|
71
|
+
.description('List all active facilitators')
|
|
72
|
+
.option('-u, --url <url>', 'API URL', 'https://x402-avalanche-chi.vercel.app')
|
|
73
|
+
.action(facilitatorCommand.list);
|
|
74
|
+
|
|
75
|
+
facilitator
|
|
76
|
+
.command('status <id>')
|
|
77
|
+
.description('Check facilitator status')
|
|
78
|
+
.option('-u, --url <url>', 'API URL', 'https://x402-avalanche-chi.vercel.app')
|
|
79
|
+
.action(facilitatorCommand.status);
|
|
80
|
+
|
|
81
|
+
facilitator
|
|
82
|
+
.command('balance <id>')
|
|
83
|
+
.description('Check facilitator gas balance')
|
|
84
|
+
.option('-u, --url <url>', 'API URL', 'https://x402-avalanche-chi.vercel.app')
|
|
85
|
+
.action(facilitatorCommand.balance);
|
|
86
|
+
|
|
87
|
+
// Help command
|
|
88
|
+
program.on('--help', () => {
|
|
89
|
+
console.log('');
|
|
90
|
+
console.log(chalk.cyan('Examples:'));
|
|
91
|
+
console.log('');
|
|
92
|
+
console.log(' $ facinet connect');
|
|
93
|
+
console.log(' $ facinet pay --amount 1 --to 0x123...');
|
|
94
|
+
console.log(' $ facinet facilitator create --name "MyNode"');
|
|
95
|
+
console.log(' $ facinet facilitator list');
|
|
96
|
+
console.log(' $ facinet facilitator status fac_xyz123');
|
|
97
|
+
console.log('');
|
|
98
|
+
console.log(chalk.cyan('Documentation:'));
|
|
99
|
+
console.log(' https://github.com/your-repo/facinet');
|
|
100
|
+
console.log('');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
program.parse(process.argv);
|
|
104
|
+
|
|
105
|
+
// Show help if no command provided
|
|
106
|
+
if (!process.argv.slice(2).length) {
|
|
107
|
+
program.outputHelp();
|
|
108
|
+
}
|