-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): init a turbo cli tool featuring KYVE crypto fund PE-6449
- Loading branch information
Showing
4 changed files
with
363 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
#!/usr/bin/env node | ||
|
||
/** | ||
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc. All Rights Reserved. | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
// eslint-disable-next-line header/header -- This is a CLI file | ||
import { Command, program } from 'commander'; | ||
|
||
import { version } from '../version.js'; | ||
import { | ||
applyOptions, | ||
configFromOptions, | ||
globalOptions, | ||
optionMap, | ||
privateKeyFromOptions, | ||
tokenFromOptions, | ||
valueFromOptions, | ||
walletOptions, | ||
} from './cliUtils.js'; | ||
import { cryptoFund, getBalance } from './commands.js'; | ||
|
||
applyOptions( | ||
program | ||
.name('turbo') | ||
.version(version) | ||
.description('Turbo CLI') | ||
.helpCommand(true), | ||
globalOptions, | ||
); | ||
|
||
applyOptions( | ||
program.command('get-balance').description('Get balance of a Turbo address'), | ||
[optionMap.address, optionMap.token], | ||
).action((address, options) => { | ||
getBalance(address, options.token); | ||
}); | ||
|
||
applyOptions( | ||
program.command('top-up').description('Top up a Turbo address with Fiat'), | ||
[optionMap.address, optionMap.value, optionMap.token], | ||
).action((options) => { | ||
console.log( | ||
'TODO: fiat top-up', | ||
options.address, | ||
options.token, | ||
options.value, | ||
); | ||
}); | ||
|
||
applyOptions( | ||
program.command('crypto-fund').description('Top up a wallet with crypto'), | ||
[...walletOptions, optionMap.token, optionMap.value], | ||
).action(async (_commandOptions, command: Command) => { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const options: any = command.optsWithGlobals(); | ||
|
||
const token = tokenFromOptions(options); | ||
const value = valueFromOptions(options); | ||
|
||
const privateKey = await privateKeyFromOptions(options, token); | ||
|
||
const config = configFromOptions(options); | ||
|
||
cryptoFund({ privateKey, value, token, config }); | ||
}); | ||
|
||
applyOptions( | ||
program | ||
.command('upload-folder') | ||
.description('Upload a folder to a Turbo address') | ||
.argument('<folderPath>', 'Directory to upload'), | ||
[...walletOptions, optionMap.token], | ||
).action((directory, options) => { | ||
console.log('upload-folder TODO', directory, options); | ||
}); | ||
|
||
if ( | ||
process.argv[1].includes('.bin/turbo') || // Running from global .bin | ||
process.argv[1].includes('cli/cli') // Running from source | ||
) { | ||
program.parse(process.argv); | ||
} | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
/** | ||
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc. All Rights Reserved. | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
import bs58 from 'bs58'; | ||
import { Command } from 'commander'; | ||
import { readFileSync } from 'fs'; | ||
|
||
import { | ||
TokenType, | ||
TurboUnauthenticatedConfiguration, | ||
defaultTurboConfiguration, | ||
developmentTurboConfiguration, | ||
isTokenType, | ||
privateKeyFromKyveMnemonic, | ||
} from '../node/index.js'; | ||
|
||
interface CommanderOption { | ||
alias: string; | ||
description: string; | ||
default?: string | boolean; | ||
} | ||
|
||
export const optionMap = { | ||
token: { | ||
alias: '-t, --token <type>', | ||
description: 'Token type for wallet or action', | ||
default: 'arweave', | ||
}, | ||
currency: { | ||
alias: '-c, --currency <currency>', | ||
description: 'Currency type to top up with', | ||
default: 'usd', | ||
}, | ||
address: { | ||
alias: '-a, --address <walletAddress>', | ||
description: 'Wallet address to use for action', | ||
}, | ||
value: { | ||
alias: '-v, --value <value>', | ||
description: 'Value of fiat currency or crypto token for action', | ||
}, | ||
walletFile: { | ||
alias: '-w, --wallet-file <filePath>', | ||
description: | ||
'Wallet file to use with the action. Formats accepted: JWK.json, KYVE or ETH private key as a string, or SOL Secret Key as a Uint8Array', | ||
}, | ||
mnemonic: { | ||
alias: '-m, --mnemonic <phrase>', | ||
description: 'Mnemonic to use with the action', | ||
}, | ||
privateKey: { | ||
alias: '-p, --private-key <key>', | ||
description: 'Private key to use with the action', | ||
}, | ||
|
||
gateway: { | ||
alias: '-g, --gateway <url>', | ||
description: 'Set a custom crypto gateway URL', | ||
default: undefined, | ||
}, | ||
dev: { | ||
alias: '--dev', | ||
description: 'Enable development endpoints', | ||
default: false, | ||
}, | ||
debug: { | ||
// TODO: Implement | ||
alias: '--debug', | ||
description: 'Enable verbose logging', | ||
default: false, | ||
}, | ||
quiet: { | ||
// TODO: Implement | ||
alias: '--quiet', | ||
description: 'Disable logging', | ||
default: false, | ||
}, | ||
} as const; | ||
|
||
export const walletOptions = [ | ||
optionMap.walletFile, | ||
optionMap.mnemonic, | ||
optionMap.privateKey, | ||
]; | ||
|
||
export const globalOptions = [ | ||
optionMap.dev, | ||
optionMap.gateway, | ||
optionMap.debug, | ||
optionMap.quiet, | ||
]; | ||
|
||
export function applyOptions( | ||
command: Command, | ||
options: CommanderOption[], | ||
): Command { | ||
[...options].forEach((option) => { | ||
command.option(option.alias, option.description, option.default); | ||
}); | ||
return command; | ||
} | ||
|
||
export function tokenFromOptions(options: unknown): TokenType { | ||
const token = (options as { token: string }).token; | ||
if (token === undefined) { | ||
throw new Error('Token type required'); | ||
} | ||
|
||
if (!isTokenType(token)) { | ||
throw new Error('Invalid token type'); | ||
} | ||
return token; | ||
} | ||
|
||
export function valueFromOptions(options: unknown): string { | ||
const value = (options as { value: string }).value; | ||
if (value === undefined) { | ||
throw new Error('Value is required. Use --value <value>'); | ||
} | ||
return value; | ||
} | ||
|
||
export async function privateKeyFromOptions( | ||
{ | ||
mnemonic, | ||
privateKey, | ||
walletFile, | ||
}: { | ||
walletFile: string | undefined; | ||
mnemonic: string | undefined; | ||
privateKey: string | undefined; | ||
}, | ||
token: TokenType, | ||
): Promise<string> { | ||
if (mnemonic !== undefined) { | ||
if (token === 'kyve') { | ||
return privateKeyFromKyveMnemonic(mnemonic); | ||
} else { | ||
throw new Error( | ||
'mnemonic provided but this token type mnemonic to wallet is not supported', | ||
); | ||
} | ||
} else if (walletFile !== undefined) { | ||
const wallet = JSON.parse(readFileSync(walletFile, 'utf-8')); | ||
|
||
return token === 'solana' ? bs58.encode(wallet) : wallet; | ||
} else if (privateKey !== undefined) { | ||
return privateKey; | ||
} | ||
|
||
throw new Error('mnemonic or wallet file required'); | ||
} | ||
|
||
const tokenToDevGatewayMap: Record<TokenType, string> = { | ||
arweave: 'https://arweave.net', // No arweave test net | ||
solana: 'https://api.devnet.solana.com', | ||
ethereum: 'https://ethereum-holesky-rpc.publicnode.com', | ||
kyve: 'https://api.korellia.kyve.network', | ||
}; | ||
|
||
export function configFromOptions({ | ||
gateway, | ||
dev, | ||
token, | ||
}: { | ||
gateway: string | undefined; | ||
dev: boolean | undefined; | ||
token: TokenType; | ||
}): TurboUnauthenticatedConfiguration { | ||
let config: TurboUnauthenticatedConfiguration = {}; | ||
|
||
if (dev) { | ||
config = developmentTurboConfiguration; | ||
config.gatewayUrl = tokenToDevGatewayMap[token]; | ||
} else { | ||
config = defaultTurboConfiguration; | ||
} | ||
|
||
// If gateway is provided, override the default or dev gateway | ||
if (gateway !== undefined) { | ||
config.gatewayUrl = gateway; | ||
} | ||
|
||
return config; | ||
} | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/** | ||
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc. All Rights Reserved. | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
import { | ||
TokenType, | ||
TurboFactory, | ||
TurboUnauthenticatedConfiguration, | ||
TurboWallet, | ||
isTokenType, | ||
tokenToBaseMap, | ||
} from '../node/index.js'; | ||
|
||
export async function getBalance(address: string, token: string) { | ||
if (!isTokenType(token)) { | ||
throw new Error('Invalid token type!'); | ||
} | ||
|
||
const unauthenticatedTurbo = TurboFactory.unauthenticated({ | ||
paymentServiceConfig: { token }, | ||
}); | ||
console.log('unauthenticatedTurbo', unauthenticatedTurbo); | ||
// const balance = await unauthenticatedTurbo.getBalance({ | ||
// owner: address, | ||
// }); | ||
// TODO: Implement unauthenticated getBalance | ||
console.log('TODO: Get balance for', address); | ||
} | ||
|
||
export interface CryptoFundParams { | ||
token: TokenType; | ||
value: string; | ||
privateKey: TurboWallet; | ||
config: TurboUnauthenticatedConfiguration; | ||
} | ||
/** Fund the connected signer with crypto */ | ||
export async function cryptoFund({ | ||
value, | ||
privateKey, | ||
token, | ||
config, | ||
}: CryptoFundParams) { | ||
const authenticatedTurbo = TurboFactory.authenticated({ | ||
...config, | ||
privateKey: privateKey, | ||
token, | ||
}); | ||
|
||
const result = await authenticatedTurbo.topUpWithTokens({ | ||
tokenAmount: tokenToBaseMap[token](value), | ||
}); | ||
|
||
console.log( | ||
'Sent crypto fund transaction: \n', | ||
JSON.stringify(result, null, 2), | ||
); | ||
} | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters