diff --git a/src/cli/cli.ts b/src/cli/cli.ts
index 84068283..bb43d222 100644
--- a/src/cli/cli.ts
+++ b/src/cli/cli.ts
@@ -20,7 +20,7 @@
import { Command, program } from 'commander';
import { version } from '../version.js';
-import { cryptoFund, getBalance } from './commands.js';
+import { cryptoFund, getBalance, topUp } from './commands.js';
import {
applyOptions,
configFromOptions,
@@ -53,14 +53,12 @@ applyOptions(
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,
- );
+ [...walletOptions, optionMap.address, optionMap.value, optionMap.currency],
+).action(async (_commandOptions, command: Command) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const options: any = command.optsWithGlobals();
+
+ return topUp(options);
});
applyOptions(
diff --git a/src/cli/commands.ts b/src/cli/commands.ts
index f071acea..1dc16467 100644
--- a/src/cli/commands.ts
+++ b/src/cli/commands.ts
@@ -14,37 +14,57 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
+import { exec } from 'node:child_process';
+
import {
TokenType,
TurboFactory,
TurboUnauthenticatedConfiguration,
TurboWallet,
+ currencyMap,
+ fiatCurrencyTypes,
+ isCurrency,
tokenToBaseMap,
} from '../node/index.js';
-import { AddressOptions } from './types.js';
+import { sleep } from '../utils/common.js';
+import { AddressOptions, TopUpOptions } from './types.js';
import { configFromOptions, optionalPrivateKeyFromOptions } from './utils.js';
+export async function addressOrPrivateKeyFromOptions(
+ options: AddressOptions,
+): Promise<{
+ address: string | undefined;
+ privateKey: string | undefined;
+}> {
+ if (options.address !== undefined) {
+ return { address: options.address, privateKey: undefined };
+ }
+
+ return {
+ address: undefined,
+ privateKey: await optionalPrivateKeyFromOptions(options),
+ };
+}
+
export async function getBalance(options: AddressOptions) {
const config = configFromOptions(options);
- if (options.address !== undefined) {
+ const { address, privateKey } = await addressOrPrivateKeyFromOptions(options);
+
+ if (address !== undefined) {
const turbo = TurboFactory.unauthenticated(config);
- const { winc } = await turbo.getBalance(options.address);
+ const { winc } = await turbo.getBalance(address);
console.log(
- `Turbo Balance for Native Address "${options.address}"\nCredits: ${
+ `Turbo Balance for Native Address "${address}"\nCredits: ${
+winc / 1_000_000_000_000
}`,
);
return;
}
- const privateKey = await optionalPrivateKeyFromOptions(options);
-
if (privateKey === undefined) {
- throw new Error(
- 'Must provide an address (--address) or use a valid wallet',
- );
+ throw new Error('Must provide an (--address) or use a valid wallet');
}
const turbo = TurboFactory.authenticated({
@@ -88,3 +108,96 @@ export async function cryptoFund({
JSON.stringify(result, null, 2),
);
}
+
+export async function topUp(options: TopUpOptions) {
+ const config = configFromOptions(options);
+
+ const { address, privateKey } = await addressOrPrivateKeyFromOptions(options);
+
+ const value = options.value;
+ if (value === undefined) {
+ throw new Error('Must provide a --value to top up');
+ }
+
+ const currency = options.currency ?? 'usd';
+
+ if (!isCurrency(currency)) {
+ throw new Error(
+ `Invalid fiat currency type ${currency}!\nPlease use one of these:\n${JSON.stringify(
+ fiatCurrencyTypes,
+ null,
+ 2,
+ )}`,
+ );
+ }
+
+ // TODO: Pay in CLI prompts via --cli options
+
+ // if (address !== undefined) {
+ // const turbo = TurboFactory.unauthenticated(config);
+ // const { url, paymentAmount, winc } = await turbo.createCheckoutSession({
+ // amount: currencyMap[currency](+options.value),
+ // owner: address,
+ // });
+ // }
+
+ // if (privateKey === undefined) {
+ // throw new Error('Must provide a wallet to top up');
+ // }
+
+ // const turbo = TurboFactory.authenticated({
+ // ...config,
+ // privateKey,
+ // });
+
+ // const { url, paymentAmount, winc } = await turbo.createCheckoutSession({
+ // amount: currencyMap[currency](+options.value),
+ // owner: await turbo.signer.getNativeAddress(),
+ // });
+
+ const { url, paymentAmount, winc } = await (async () => {
+ const amount = currencyMap[currency](+value);
+
+ if (address !== undefined) {
+ const turbo = TurboFactory.unauthenticated(config);
+ return turbo.createCheckoutSession({
+ amount,
+ owner: address,
+ });
+ }
+
+ if (privateKey === undefined) {
+ throw new Error('Must provide a wallet to top up');
+ }
+
+ const turbo = TurboFactory.authenticated({
+ ...config,
+ privateKey,
+ });
+ return turbo.createCheckoutSession({
+ amount,
+ owner: await turbo.signer.getNativeAddress(),
+ });
+ })();
+
+ console.log(
+ 'Got Checkout Session\n' + JSON.stringify({ url, paymentAmount, winc }),
+ );
+ console.log('Opening checkout session in browser...');
+ await sleep(2000);
+
+ openUrl(url);
+}
+
+export function openUrl(url: string) {
+ if (process.platform === 'darwin') {
+ // macOS
+ exec(`open ${url}`);
+ } else if (process.platform === 'win32') {
+ // Windows
+ exec(`start "" "${url}"`, { windowsHide: true });
+ } else {
+ // Linux/Unix
+ open(url);
+ }
+}
diff --git a/src/cli/types.ts b/src/cli/types.ts
index 064264ab..eef90a65 100644
--- a/src/cli/types.ts
+++ b/src/cli/types.ts
@@ -33,3 +33,8 @@ export type WalletOptions = GlobalOptions & {
export type AddressOptions = WalletOptions & {
address: string | undefined;
};
+
+export type TopUpOptions = AddressOptions & {
+ value: string | undefined;
+ currency: string | undefined;
+};
diff --git a/src/common/currency.ts b/src/common/currency.ts
index dc8b1cd7..d4b128ea 100644
--- a/src/common/currency.ts
+++ b/src/common/currency.ts
@@ -52,3 +52,16 @@ export const BRL = (brl: number) => new TwoDecimalCurrency(brl, 'brl');
// Zero decimal currencies that are supported by the Turbo API
export const JPY = (jpy: number) => new ZeroDecimalCurrency(jpy, 'jpy');
+
+export const currencyMap: Record CurrencyMap> = {
+ usd: USD,
+ eur: EUR,
+ gbp: GBP,
+ cad: CAD,
+ aud: AUD,
+ inr: INR,
+ sgd: SGD,
+ hkd: HKD,
+ brl: BRL,
+ jpy: JPY,
+};
diff --git a/src/common/payment.ts b/src/common/payment.ts
index 9aa66000..db7a9315 100644
--- a/src/common/payment.ts
+++ b/src/common/payment.ts
@@ -21,10 +21,10 @@ import {
Currency,
TokenTools,
TokenType,
- TopUpRawResponse,
TurboAuthenticatedPaymentServiceConfiguration,
TurboAuthenticatedPaymentServiceInterface,
TurboBalanceResponse,
+ TurboCheckoutRawResponse,
TurboCheckoutSessionParams,
TurboCheckoutSessionResponse,
TurboCountriesResponse,
@@ -157,7 +157,7 @@ export class TurboUnauthenticatedPaymentService
}&token=${this.token}`;
const { adjustments, paymentSession, topUpQuote } =
- await this.httpService.get({
+ await this.httpService.get({
endpoint,
headers,
});
@@ -165,9 +165,8 @@ export class TurboUnauthenticatedPaymentService
return {
winc: topUpQuote.winstonCreditAmount,
adjustments,
- url: paymentSession.url ?? undefined,
+ url: paymentSession.url,
id: paymentSession.id,
- client_secret: paymentSession.client_secret ?? undefined,
paymentAmount: topUpQuote.paymentAmount,
quotedPaymentAmount: topUpQuote.quotedPaymentAmount,
};
diff --git a/src/types.ts b/src/types.ts
index f5f1515d..001afa64 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -35,17 +35,24 @@ export type NativeAddress = string;
export type PublicArweaveAddress = Base64String;
export type TransactionId = Base64String;
export type UserAddress = string | PublicArweaveAddress;
-export type Currency =
- | 'usd'
- | 'eur'
- | 'gbp'
- | 'cad'
- | 'aud'
- | 'jpy'
- | 'inr'
- | 'sgd'
- | 'hkd'
- | 'brl';
+
+export const fiatCurrencyTypes = [
+ 'usd',
+ 'eur',
+ 'gbp',
+ 'cad',
+ 'aud',
+ 'jpy',
+ 'inr',
+ 'sgd',
+ 'hkd',
+ 'brl',
+] as const;
+export type Currency = (typeof fiatCurrencyTypes)[number];
+export function isCurrency(currency: string): currency is Currency {
+ return fiatCurrencyTypes.includes(currency as Currency);
+}
+
export type Country = 'United States' | 'United Kingdom' | 'Canada'; // TODO: add full list
export const tokenTypes = ['arweave', 'solana', 'ethereum', 'kyve'] as const;
@@ -94,17 +101,26 @@ export type TopUpRawResponse = {
winstonCreditAmount: string;
};
paymentSession: {
- url: string | null;
id: string;
- client_secret: string | null;
};
adjustments: Adjustment[];
};
+export type TurboPaymentIntentRawResponse = TopUpRawResponse & {
+ paymentSession: {
+ client_secret: string;
+ };
+};
+
+export type TurboCheckoutRawResponse = TopUpRawResponse & {
+ paymentSession: {
+ url: string;
+ };
+};
+
export type TurboCheckoutSessionResponse = TurboWincForFiatResponse & {
id: string;
- client_secret?: string;
- url?: string;
+ url: string;
};
export type TurboBalanceResponse = Omit;