nansen-cli 1.26.0 → 1.27.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/CHANGELOG.md +16 -0
- package/package.json +1 -1
- package/skills/nansen-alerts-webhook-listener/SKILL.md +386 -0
- package/skills/nansen-trading/SKILL.md +12 -1
- package/src/api.js +82 -14
- package/src/cli.js +56 -40
- package/src/index.js +1 -1
- package/src/schema.json +39 -6
- package/src/telemetry.js +8 -6
- package/src/trade-validation.js +58 -0
- package/src/trading.js +55 -93
- package/src/transfer.js +13 -10
- package/src/wallet.js +66 -117
- package/src/x402-svm.js +3 -23
package/src/wallet.js
CHANGED
|
@@ -9,6 +9,7 @@ import path from 'path';
|
|
|
9
9
|
import * as readline from 'readline';
|
|
10
10
|
import { base58 } from '@scure/base';
|
|
11
11
|
import { storePassword, retrievePassword, deletePassword, deleteCredentialsFile } from './keychain.js';
|
|
12
|
+
import { CommandError } from './api.js';
|
|
12
13
|
|
|
13
14
|
// ============= Constants =============
|
|
14
15
|
|
|
@@ -246,6 +247,14 @@ function getWalletFile(name) {
|
|
|
246
247
|
return path.join(getWalletsDir(), `${name}.json`);
|
|
247
248
|
}
|
|
248
249
|
|
|
250
|
+
function requireWalletFile(name) {
|
|
251
|
+
const walletFile = getWalletFile(name);
|
|
252
|
+
if (!fs.existsSync(walletFile)) {
|
|
253
|
+
throw new Error(`Wallet "${name}" not found`);
|
|
254
|
+
}
|
|
255
|
+
return walletFile;
|
|
256
|
+
}
|
|
257
|
+
|
|
249
258
|
/**
|
|
250
259
|
* Verify the global password against stored hash.
|
|
251
260
|
*/
|
|
@@ -335,8 +344,8 @@ function resolveWalletPassword() {
|
|
|
335
344
|
*
|
|
336
345
|
* @param {object} config - wallet config (needs config.passwordHash)
|
|
337
346
|
* @param {object} flags - CLI flags
|
|
338
|
-
* @param {object} deps - { promptFn, log
|
|
339
|
-
* @returns {{ password: string|null, error:
|
|
347
|
+
* @param {object} deps - { promptFn, log }
|
|
348
|
+
* @returns {{ password: string|null, error: object|null }}
|
|
340
349
|
*/
|
|
341
350
|
async function resolvePasswordForCommand(config, flags, deps) {
|
|
342
351
|
if (!config.passwordHash) {
|
|
@@ -353,14 +362,14 @@ async function resolvePasswordForCommand(config, flags, deps) {
|
|
|
353
362
|
|
|
354
363
|
return {
|
|
355
364
|
password: null,
|
|
356
|
-
error:
|
|
365
|
+
error: {
|
|
357
366
|
error: 'PASSWORD_REQUIRED',
|
|
358
367
|
message: 'Wallet is encrypted and no password was found.',
|
|
359
368
|
resolution: [
|
|
360
369
|
'Set NANSEN_WALLET_PASSWORD environment variable',
|
|
361
370
|
'Or re-run wallet create with the password (it will be persisted for future use)',
|
|
362
371
|
],
|
|
363
|
-
}
|
|
372
|
+
},
|
|
364
373
|
};
|
|
365
374
|
}
|
|
366
375
|
|
|
@@ -465,10 +474,7 @@ export function createWallet(name, password) {
|
|
|
465
474
|
* Show wallet details (addresses only, no keys).
|
|
466
475
|
*/
|
|
467
476
|
export function showWallet(name) {
|
|
468
|
-
const walletFile =
|
|
469
|
-
if (!fs.existsSync(walletFile)) {
|
|
470
|
-
throw new Error(`Wallet "${name}" not found`);
|
|
471
|
-
}
|
|
477
|
+
const walletFile = requireWalletFile(name);
|
|
472
478
|
|
|
473
479
|
const data = JSON.parse(fs.readFileSync(walletFile, 'utf8'));
|
|
474
480
|
const config = getWalletConfig();
|
|
@@ -494,10 +500,7 @@ export function showWallet(name) {
|
|
|
494
500
|
* Export private keys for a wallet (requires password).
|
|
495
501
|
*/
|
|
496
502
|
export function exportWallet(name, password) {
|
|
497
|
-
const walletFile =
|
|
498
|
-
if (!fs.existsSync(walletFile)) {
|
|
499
|
-
throw new Error(`Wallet "${name}" not found`);
|
|
500
|
-
}
|
|
503
|
+
const walletFile = requireWalletFile(name);
|
|
501
504
|
|
|
502
505
|
const data = JSON.parse(fs.readFileSync(walletFile, 'utf8'));
|
|
503
506
|
if (data.provider && data.provider !== 'local') {
|
|
@@ -526,10 +529,7 @@ export function exportWallet(name, password) {
|
|
|
526
529
|
* Set the default wallet.
|
|
527
530
|
*/
|
|
528
531
|
export function setDefaultWallet(name) {
|
|
529
|
-
|
|
530
|
-
if (!fs.existsSync(walletFile)) {
|
|
531
|
-
throw new Error(`Wallet "${name}" not found`);
|
|
532
|
-
}
|
|
532
|
+
requireWalletFile(name);
|
|
533
533
|
|
|
534
534
|
const config = getWalletConfig();
|
|
535
535
|
config.defaultWallet = name;
|
|
@@ -542,10 +542,7 @@ export function setDefaultWallet(name) {
|
|
|
542
542
|
* Delete a wallet.
|
|
543
543
|
*/
|
|
544
544
|
export async function deleteWallet(name, password) {
|
|
545
|
-
const walletFile =
|
|
546
|
-
if (!fs.existsSync(walletFile)) {
|
|
547
|
-
throw new Error(`Wallet "${name}" not found`);
|
|
548
|
-
}
|
|
545
|
+
const walletFile = requireWalletFile(name);
|
|
549
546
|
|
|
550
547
|
const data = JSON.parse(fs.readFileSync(walletFile, 'utf8'));
|
|
551
548
|
const config = getWalletConfig();
|
|
@@ -580,7 +577,7 @@ export async function deleteWallet(name, password) {
|
|
|
580
577
|
* Build wallet command handlers for integration into CLI.
|
|
581
578
|
*/
|
|
582
579
|
export function buildWalletCommands(deps = {}) {
|
|
583
|
-
const { log = console.log
|
|
580
|
+
const { log = console.log } = deps;
|
|
584
581
|
|
|
585
582
|
return {
|
|
586
583
|
'wallet': async (args, apiInstance, flags, options) => {
|
|
@@ -599,9 +596,7 @@ export function buildWalletCommands(deps = {}) {
|
|
|
599
596
|
log('');
|
|
600
597
|
return;
|
|
601
598
|
} catch (err) {
|
|
602
|
-
|
|
603
|
-
exit(1);
|
|
604
|
-
return;
|
|
599
|
+
throw new CommandError(`❌ ${err.message}`, 'PRIVY_ERROR');
|
|
605
600
|
}
|
|
606
601
|
}
|
|
607
602
|
// All other subcommands fall through to unified handlers below
|
|
@@ -612,8 +607,8 @@ export function buildWalletCommands(deps = {}) {
|
|
|
612
607
|
const handlers = {
|
|
613
608
|
'create': async () => {
|
|
614
609
|
// Privy wallets are handled above — if we reach here with provider=privy,
|
|
615
|
-
// the privy path already
|
|
616
|
-
// where
|
|
610
|
+
// the privy path already threw CommandError. Guard against environments
|
|
611
|
+
// where throw does not propagate (agent frameworks, test harnesses).
|
|
617
612
|
if (isPrivy) return;
|
|
618
613
|
|
|
619
614
|
const name = options.name || args[1] || 'default';
|
|
@@ -628,28 +623,22 @@ export function buildWalletCommands(deps = {}) {
|
|
|
628
623
|
|
|
629
624
|
// Step 2: If --human flag, allow interactive prompt (requires TTY)
|
|
630
625
|
if (!password && flags.human && !process.stdin.isTTY && !deps.promptFn) {
|
|
631
|
-
|
|
626
|
+
throw new CommandError('--human requires an interactive terminal. Set NANSEN_WALLET_PASSWORD env var instead.', 'NOT_A_TTY', {
|
|
632
627
|
error: 'NOT_A_TTY',
|
|
633
628
|
message: '--human requires an interactive terminal. Set NANSEN_WALLET_PASSWORD env var instead.',
|
|
634
|
-
})
|
|
635
|
-
exit(1);
|
|
636
|
-
return;
|
|
629
|
+
});
|
|
637
630
|
}
|
|
638
631
|
if (!password && flags.human && (process.stdin.isTTY || deps.promptFn)) {
|
|
639
632
|
password = await promptPassword('Enter wallet password: ', deps);
|
|
640
633
|
if (password && password.length < 12) {
|
|
641
|
-
|
|
642
|
-
exit(1);
|
|
643
|
-
return;
|
|
634
|
+
throw new CommandError('❌ Password must be at least 12 characters', 'INVALID_INPUT');
|
|
644
635
|
}
|
|
645
636
|
if (password) {
|
|
646
637
|
const config = getWalletConfig();
|
|
647
638
|
if (!config.passwordHash) {
|
|
648
639
|
const confirm = await promptPassword('Confirm password: ', deps);
|
|
649
640
|
if (password !== confirm) {
|
|
650
|
-
|
|
651
|
-
exit(1);
|
|
652
|
-
return;
|
|
641
|
+
throw new CommandError('❌ Passwords do not match', 'INVALID_INPUT');
|
|
653
642
|
}
|
|
654
643
|
}
|
|
655
644
|
}
|
|
@@ -657,20 +646,16 @@ export function buildWalletCommands(deps = {}) {
|
|
|
657
646
|
|
|
658
647
|
// Step 3: No password available — return structured error for agents
|
|
659
648
|
if (!password) {
|
|
660
|
-
|
|
649
|
+
throw new CommandError('A wallet password is required. Ask the user to provide one.', 'PASSWORD_REQUIRED', {
|
|
661
650
|
error: 'PASSWORD_REQUIRED',
|
|
662
651
|
message: 'A wallet password is required. Ask the user to provide one.',
|
|
663
652
|
instructions: 'Re-run with: NANSEN_WALLET_PASSWORD=<password> nansen wallet create',
|
|
664
653
|
note: 'Password must be at least 12 characters. After creation, the password is saved to the OS keychain automatically — future operations will not require it.',
|
|
665
|
-
})
|
|
666
|
-
exit(1);
|
|
667
|
-
return;
|
|
654
|
+
});
|
|
668
655
|
}
|
|
669
656
|
|
|
670
657
|
if (password.length < 12) {
|
|
671
|
-
|
|
672
|
-
exit(1);
|
|
673
|
-
return;
|
|
658
|
+
throw new CommandError('❌ Password must be at least 12 characters', 'INVALID_INPUT');
|
|
674
659
|
}
|
|
675
660
|
}
|
|
676
661
|
|
|
@@ -678,9 +663,7 @@ export function buildWalletCommands(deps = {}) {
|
|
|
678
663
|
if (password !== null) {
|
|
679
664
|
const config = getWalletConfig();
|
|
680
665
|
if (config.passwordHash && !verifyPassword(password, config)) {
|
|
681
|
-
|
|
682
|
-
exit(1);
|
|
683
|
-
return;
|
|
666
|
+
throw new CommandError('❌ Incorrect password — does not match existing wallets.', 'INCORRECT_PASSWORD');
|
|
684
667
|
}
|
|
685
668
|
}
|
|
686
669
|
|
|
@@ -725,8 +708,7 @@ export function buildWalletCommands(deps = {}) {
|
|
|
725
708
|
log('');
|
|
726
709
|
return;
|
|
727
710
|
} catch (err) {
|
|
728
|
-
|
|
729
|
-
exit(1);
|
|
711
|
+
throw new CommandError(`❌ ${err.message}`, 'CREATE_FAILED');
|
|
730
712
|
}
|
|
731
713
|
},
|
|
732
714
|
|
|
@@ -750,9 +732,7 @@ export function buildWalletCommands(deps = {}) {
|
|
|
750
732
|
'show': async () => {
|
|
751
733
|
const name = options.name || args[1];
|
|
752
734
|
if (!name) {
|
|
753
|
-
|
|
754
|
-
exit(1);
|
|
755
|
-
return;
|
|
735
|
+
throw new CommandError('Usage: nansen wallet show <name>', 'MISSING_ARGS');
|
|
756
736
|
}
|
|
757
737
|
try {
|
|
758
738
|
const result = showWallet(name);
|
|
@@ -764,25 +744,20 @@ export function buildWalletCommands(deps = {}) {
|
|
|
764
744
|
log(` Created: ${result.createdAt}\n`);
|
|
765
745
|
return;
|
|
766
746
|
} catch (err) {
|
|
767
|
-
|
|
768
|
-
exit(1);
|
|
747
|
+
throw new CommandError(`❌ ${err.message}`, 'SHOW_FAILED');
|
|
769
748
|
}
|
|
770
749
|
},
|
|
771
750
|
|
|
772
751
|
'export': async () => {
|
|
773
752
|
const name = options.name || args[1];
|
|
774
753
|
if (!name) {
|
|
775
|
-
|
|
776
|
-
exit(1);
|
|
777
|
-
return;
|
|
754
|
+
throw new CommandError('Usage: nansen wallet export <name>', 'MISSING_ARGS');
|
|
778
755
|
}
|
|
779
756
|
|
|
780
757
|
const config = getWalletConfig();
|
|
781
758
|
const { password, error } = await resolvePasswordForCommand(config, flags, deps);
|
|
782
759
|
if (error) {
|
|
783
|
-
|
|
784
|
-
exit(1);
|
|
785
|
-
return;
|
|
760
|
+
throw new CommandError(error.message, 'PASSWORD_REQUIRED', error);
|
|
786
761
|
}
|
|
787
762
|
try {
|
|
788
763
|
const result = exportWallet(name, password);
|
|
@@ -796,34 +771,28 @@ export function buildWalletCommands(deps = {}) {
|
|
|
796
771
|
log('');
|
|
797
772
|
return;
|
|
798
773
|
} catch (err) {
|
|
799
|
-
|
|
800
|
-
exit(1);
|
|
774
|
+
throw new CommandError(`❌ ${err.message}`, 'EXPORT_FAILED');
|
|
801
775
|
}
|
|
802
776
|
},
|
|
803
777
|
|
|
804
778
|
'default': async () => {
|
|
805
779
|
const name = options.name || args[1];
|
|
806
780
|
if (!name) {
|
|
807
|
-
|
|
808
|
-
exit(1);
|
|
809
|
-
return;
|
|
781
|
+
throw new CommandError('Usage: nansen wallet default <name>', 'MISSING_ARGS');
|
|
810
782
|
}
|
|
811
783
|
try {
|
|
812
784
|
const result = setDefaultWallet(name);
|
|
813
785
|
log(`✓ Default wallet set to "${result.defaultWallet}"`);
|
|
814
786
|
return;
|
|
815
787
|
} catch (err) {
|
|
816
|
-
|
|
817
|
-
exit(1);
|
|
788
|
+
throw new CommandError(`❌ ${err.message}`, 'DEFAULT_FAILED');
|
|
818
789
|
}
|
|
819
790
|
},
|
|
820
791
|
|
|
821
792
|
'delete': async () => {
|
|
822
793
|
const name = options.name || args[1];
|
|
823
794
|
if (!name) {
|
|
824
|
-
|
|
825
|
-
exit(1);
|
|
826
|
-
return;
|
|
795
|
+
throw new CommandError('Usage: nansen wallet delete <name>', 'MISSING_ARGS');
|
|
827
796
|
}
|
|
828
797
|
|
|
829
798
|
// Check if this is a Privy wallet (no password needed)
|
|
@@ -839,9 +808,7 @@ export function buildWalletCommands(deps = {}) {
|
|
|
839
808
|
const config = getWalletConfig();
|
|
840
809
|
const resolved = await resolvePasswordForCommand(config, flags, deps);
|
|
841
810
|
if (resolved.error) {
|
|
842
|
-
|
|
843
|
-
exit(1);
|
|
844
|
-
return;
|
|
811
|
+
throw new CommandError(resolved.error.message, 'PASSWORD_REQUIRED', resolved.error);
|
|
845
812
|
}
|
|
846
813
|
password = resolved.password;
|
|
847
814
|
}
|
|
@@ -857,8 +824,7 @@ export function buildWalletCommands(deps = {}) {
|
|
|
857
824
|
}
|
|
858
825
|
return;
|
|
859
826
|
} catch (err) {
|
|
860
|
-
|
|
861
|
-
exit(1);
|
|
827
|
+
throw new CommandError(`❌ ${err.message}`, 'DELETE_FAILED');
|
|
862
828
|
}
|
|
863
829
|
},
|
|
864
830
|
|
|
@@ -866,28 +832,20 @@ export function buildWalletCommands(deps = {}) {
|
|
|
866
832
|
const { sendTokens } = await import('./transfer.js');
|
|
867
833
|
|
|
868
834
|
if (!options.to) {
|
|
869
|
-
|
|
870
|
-
exit(1);
|
|
871
|
-
return;
|
|
835
|
+
throw new CommandError('--to <address> is required', 'MISSING_ARGS');
|
|
872
836
|
}
|
|
873
837
|
|
|
874
838
|
const isMax = flags.max || options.amount === 'max';
|
|
875
839
|
if (!options.amount && !isMax) {
|
|
876
|
-
|
|
877
|
-
exit(1);
|
|
878
|
-
return;
|
|
840
|
+
throw new CommandError('--amount <number> or --max is required', 'MISSING_ARGS');
|
|
879
841
|
}
|
|
880
842
|
|
|
881
843
|
if (!options.chain) {
|
|
882
|
-
|
|
883
|
-
exit(1);
|
|
884
|
-
return;
|
|
844
|
+
throw new CommandError('--chain <evm|solana> is required', 'MISSING_ARGS');
|
|
885
845
|
}
|
|
886
846
|
|
|
887
847
|
if (!['evm', 'solana', 'ethereum', 'base'].includes(options.chain)) {
|
|
888
|
-
|
|
889
|
-
exit(1);
|
|
890
|
-
return;
|
|
848
|
+
throw new CommandError('--chain must be one of: evm, solana, ethereum, base', 'INVALID_INPUT');
|
|
891
849
|
}
|
|
892
850
|
|
|
893
851
|
const isWalletConnect = options.wallet === 'walletconnect' || options.wallet === 'wc';
|
|
@@ -912,9 +870,7 @@ export function buildWalletCommands(deps = {}) {
|
|
|
912
870
|
const sendConfig = getWalletConfig();
|
|
913
871
|
const resolved = await resolvePasswordForCommand(sendConfig, flags, deps);
|
|
914
872
|
if (resolved.error) {
|
|
915
|
-
|
|
916
|
-
exit(1);
|
|
917
|
-
return;
|
|
873
|
+
throw new CommandError(resolved.error.message, 'PASSWORD_REQUIRED', resolved.error);
|
|
918
874
|
}
|
|
919
875
|
password = resolved.password;
|
|
920
876
|
}
|
|
@@ -960,8 +916,7 @@ export function buildWalletCommands(deps = {}) {
|
|
|
960
916
|
log('');
|
|
961
917
|
return;
|
|
962
918
|
} catch (err) {
|
|
963
|
-
|
|
964
|
-
exit(1);
|
|
919
|
+
throw new CommandError(err.message, 'SEND_FAILED');
|
|
965
920
|
}
|
|
966
921
|
},
|
|
967
922
|
|
|
@@ -979,16 +934,14 @@ export function buildWalletCommands(deps = {}) {
|
|
|
979
934
|
'secure': async () => {
|
|
980
935
|
const { password, source } = retrievePassword();
|
|
981
936
|
if (!password) {
|
|
982
|
-
|
|
937
|
+
throw new CommandError('No wallet password found in any store.', 'NO_PASSWORD_FOUND', {
|
|
983
938
|
error: 'NO_PASSWORD_FOUND',
|
|
984
939
|
message: 'No wallet password found in any store.',
|
|
985
940
|
resolution: [
|
|
986
941
|
'Set NANSEN_WALLET_PASSWORD and run: nansen wallet secure',
|
|
987
942
|
'This will store it in the OS keychain.',
|
|
988
943
|
],
|
|
989
|
-
})
|
|
990
|
-
exit(1);
|
|
991
|
-
return;
|
|
944
|
+
});
|
|
992
945
|
}
|
|
993
946
|
|
|
994
947
|
if (source === 'keychain') {
|
|
@@ -999,20 +952,19 @@ export function buildWalletCommands(deps = {}) {
|
|
|
999
952
|
// Verify password actually decrypts wallets before overwriting keychain
|
|
1000
953
|
const walletConfig = getWalletConfig();
|
|
1001
954
|
if (walletConfig.passwordHash && !verifyPassword(password, walletConfig)) {
|
|
1002
|
-
|
|
955
|
+
const resolution = source === 'file'
|
|
956
|
+
? [
|
|
957
|
+
'The password in ~/.nansen/wallets/.credentials is incorrect.',
|
|
958
|
+
'Run: nansen wallet forget-password then re-run with the correct password: NANSEN_WALLET_PASSWORD=<pw> nansen wallet secure',
|
|
959
|
+
]
|
|
960
|
+
: [
|
|
961
|
+
'Unset NANSEN_WALLET_PASSWORD if it is stale, then re-run: nansen wallet secure',
|
|
962
|
+
];
|
|
963
|
+
throw new CommandError(`Password from '${source}' does not match the wallet's stored hash.`, 'INCORRECT_PASSWORD', {
|
|
1003
964
|
error: 'INCORRECT_PASSWORD',
|
|
1004
965
|
message: `Password from '${source}' does not match the wallet's stored hash.`,
|
|
1005
|
-
resolution
|
|
1006
|
-
|
|
1007
|
-
'The password in ~/.nansen/wallets/.credentials is incorrect.',
|
|
1008
|
-
'Run: nansen wallet forget-password then re-run with the correct password: NANSEN_WALLET_PASSWORD=<pw> nansen wallet secure',
|
|
1009
|
-
]
|
|
1010
|
-
: [
|
|
1011
|
-
'Unset NANSEN_WALLET_PASSWORD if it is stale, then re-run: nansen wallet secure',
|
|
1012
|
-
],
|
|
1013
|
-
}));
|
|
1014
|
-
exit(1);
|
|
1015
|
-
return;
|
|
966
|
+
resolution,
|
|
967
|
+
});
|
|
1016
968
|
}
|
|
1017
969
|
|
|
1018
970
|
// Try to migrate to keychain
|
|
@@ -1027,18 +979,17 @@ export function buildWalletCommands(deps = {}) {
|
|
|
1027
979
|
log(' Removed ~/.nansen/wallets/.credentials.');
|
|
1028
980
|
}
|
|
1029
981
|
} else {
|
|
1030
|
-
|
|
982
|
+
const msg = source === 'file'
|
|
983
|
+
? 'OS keychain is not available. Password remains in ~/.nansen/wallets/.credentials (insecure).'
|
|
984
|
+
: 'OS keychain is not available. Password is only in the NANSEN_WALLET_PASSWORD env var (not persisted).';
|
|
985
|
+
throw new CommandError(msg, 'KEYCHAIN_UNAVAILABLE', {
|
|
1031
986
|
error: 'KEYCHAIN_UNAVAILABLE',
|
|
1032
|
-
message:
|
|
1033
|
-
? 'OS keychain is not available. Password remains in ~/.nansen/wallets/.credentials (insecure).'
|
|
1034
|
-
: 'OS keychain is not available. Password is only in the NANSEN_WALLET_PASSWORD env var (not persisted).',
|
|
987
|
+
message: msg,
|
|
1035
988
|
resolution: [
|
|
1036
989
|
'Set NANSEN_WALLET_PASSWORD in a secrets manager or system keyring',
|
|
1037
990
|
'Use a containerized secrets agent (e.g. Vault, 1Password CLI)',
|
|
1038
991
|
],
|
|
1039
|
-
})
|
|
1040
|
-
exit(1);
|
|
1041
|
-
return;
|
|
992
|
+
});
|
|
1042
993
|
}
|
|
1043
994
|
},
|
|
1044
995
|
|
|
@@ -1104,9 +1055,7 @@ EXAMPLES:
|
|
|
1104
1055
|
};
|
|
1105
1056
|
|
|
1106
1057
|
if (!handlers[subcommand]) {
|
|
1107
|
-
|
|
1108
|
-
exit(1);
|
|
1109
|
-
return;
|
|
1058
|
+
throw new CommandError(`Unknown subcommand: ${subcommand}. Available: ${Object.keys(handlers).join(', ')}`, 'UNKNOWN_COMMAND');
|
|
1110
1059
|
}
|
|
1111
1060
|
|
|
1112
1061
|
// --help on any wallet subcommand shows wallet help instead of executing
|
package/src/x402-svm.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import crypto from 'crypto';
|
|
7
7
|
import { base58Encode, base58DecodePubkey } from './wallet.js';
|
|
8
|
-
import { encodeCompactU16,
|
|
8
|
+
import { encodeCompactU16, deriveATA as _deriveATA } from './transfer.js';
|
|
9
9
|
|
|
10
10
|
// ============= Constants =============
|
|
11
11
|
|
|
@@ -13,7 +13,6 @@ const TOKEN_PROGRAM = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
|
|
|
13
13
|
const _TOKEN_2022_PROGRAM = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
|
|
14
14
|
const COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';
|
|
15
15
|
const MEMO_PROGRAM = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
|
|
16
|
-
const ATA_PROGRAM = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
|
|
17
16
|
const _SYSTEM_PROGRAM = '11111111111111111111111111111111';
|
|
18
17
|
|
|
19
18
|
const DEFAULT_COMPUTE_UNIT_LIMIT = 20000;
|
|
@@ -23,29 +22,10 @@ const DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
|
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
24
|
* Derive Associated Token Account (ATA) address.
|
|
26
|
-
*
|
|
25
|
+
* Returns base58-encoded PDA. Delegates algorithm to transfer.js.
|
|
27
26
|
*/
|
|
28
27
|
export function deriveATA(ownerBase58, mintBase58, tokenProgramBase58 = TOKEN_PROGRAM) {
|
|
29
|
-
|
|
30
|
-
const tokenProgram = base58DecodePubkey(tokenProgramBase58);
|
|
31
|
-
const mint = base58DecodePubkey(mintBase58);
|
|
32
|
-
const ataProgramKey = base58DecodePubkey(ATA_PROGRAM);
|
|
33
|
-
|
|
34
|
-
// find_program_address: try nonce 255 down to 0
|
|
35
|
-
// PDA = SHA256(seeds... || programId || "ProgramDerivedAddress")
|
|
36
|
-
// A valid PDA must NOT be on the ed25519 curve.
|
|
37
|
-
// Checking on-curve in pure JS without a full ed25519 implementation is hard.
|
|
38
|
-
// We use the mathematical approach: decode y-coordinate, compute x², check QR.
|
|
39
|
-
for (let nonce = 255; nonce >= 0; nonce--) {
|
|
40
|
-
const hash = crypto.createHash('sha256')
|
|
41
|
-
.update(Buffer.concat([owner, tokenProgram, mint, Buffer.from([nonce]), ataProgramKey, Buffer.from('ProgramDerivedAddress')]))
|
|
42
|
-
.digest();
|
|
43
|
-
|
|
44
|
-
if (!isOnEd25519Curve(hash)) {
|
|
45
|
-
return base58Encode(hash);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
throw new Error('Could not derive ATA: no valid PDA found');
|
|
28
|
+
return base58Encode(_deriveATA(ownerBase58, mintBase58, tokenProgramBase58));
|
|
49
29
|
}
|
|
50
30
|
|
|
51
31
|
// ============= MessageV0 Builder =============
|