Skip to content

Commit

Permalink
add a smoother experience
Browse files Browse the repository at this point in the history
  • Loading branch information
Flaque committed Sep 13, 2024
1 parent b264aca commit 8a21006
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 51 deletions.
32 changes: 32 additions & 0 deletions src/helpers/fetchers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { apiClient } from "../apiClient";
import { logAndQuit } from "./errors";

export async function getContract(contractId: string) {
const api = await apiClient();
const { data, response } = await api.GET("/v0/contracts/{id}", {
params: {
path: { id: contractId },
},
});
if (!response.ok) {
return logAndQuit(`Failed to get contract: ${response.statusText}`);
}
return data;
}

export async function getOrder(orderId: string) {
const api = await apiClient();
const { data, response, error } = await api.GET("/v0/orders/{id}", {
params: {
path: { id: orderId },
},
});
if (!response.ok) {
// @ts-ignore
if (error?.code === "order.not_found") {
return null;
}
return logAndQuit(`Failed to get order: ${response.statusText}`);
}
return data;
}
23 changes: 23 additions & 0 deletions src/helpers/waitingForOrder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import ora from "ora";
import chalk from "chalk";
import { getOrder } from "./fetchers";
import { logAndQuit } from "./errors";

export async function waitForOrderToNotBePending(orderId: string) {
const spinner = ora(`Order ${orderId} - pending (this can take a moment)`).start();
const maxTries = 25;
for (let i = 0; i < maxTries; i++) {
const order = await getOrder(orderId);

if (order && order?.status !== "pending") {
spinner.text = `Order ${orderId} - ${order?.status}`;
spinner.succeed();
console.log(chalk.green("Order placed successfully"));
return order;
}
await new Promise((resolve) => setTimeout(resolve, 500));
}

spinner.fail();
logAndQuit(`Order ${orderId} - possibly failed`);
}
8 changes: 7 additions & 1 deletion src/lib/buy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type { Nullable } from "../types/empty";
import { formatDuration } from "./orders";
import { pricePerGPUHourToTotalPrice, totalPriceToPricePerGPUHour } from "../helpers/price";
import { GPUS_PER_NODE } from "./constants";
import { waitForOrderToNotBePending } from "../helpers/waitingForOrder";

dayjs.extend(relativeTime);
dayjs.extend(duration);
Expand Down Expand Up @@ -197,7 +198,12 @@ async function buyOrderAction(options: SfBuyOptions) {
quoteOnly: isQuoteOnly,
});

switch (res.status) {
const order = await waitForOrderToNotBePending(res.id);
if (!order) {
return;
}

switch (order.status) {
case "pending": {
const orderId = res.id;
const printOrderNumber = (status: string) =>
Expand Down
62 changes: 12 additions & 50 deletions src/lib/sell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import type { PlaceSellOrderParameters } from "./orders";
import { GPUS_PER_NODE } from "./constants";
import { pricePerGPUHourToTotalPrice } from "../helpers/price";
import ora from "ora";
import chalk from "chalk";
import { getContract, getOrder } from "../helpers/fetchers";
import { waitForOrderToNotBePending } from "../helpers/waitingForOrder";

export function registerSell(program: Command) {
program
Expand Down Expand Up @@ -51,18 +54,6 @@ function forceAsNumber(value: string | number): number {
return Number.parseFloat(value);
}

async function getContract(contractId: string) {
const api = await apiClient();
const { data, response } = await api.GET("/v0/contracts/{id}", {
params: {
path: { id: contractId },
},
});
if (!response.ok) {
return logAndQuit(`Failed to get contract: ${response.statusText}`);
}
return data;
}

function contractStartAndEnd(contract: {
shape: {
Expand All @@ -76,41 +67,6 @@ function contractStartAndEnd(contract: {
return { startDate, endDate };
}

async function getOrder(orderId: string) {
const api = await apiClient();
const { data, response, error } = await api.GET("/v0/orders/{id}", {
params: {
path: { id: orderId },
},
});
if (!response.ok) {
// @ts-ignore
if (error?.code === "order.not_found") {
return null;
}
return logAndQuit(`Failed to get order: ${response.statusText}`);
}
return data;
}

async function waitForOrderToNotBePending(orderId: string) {
const spinner = ora(`Order ${orderId} - pending`).start();
const maxTries = 10;
for (let i = 0; i < maxTries; i++) {
const order = await getOrder(orderId);

if (order && order?.status !== "pending") {
spinner.text = `Order ${orderId} - ${order?.status}`;
spinner.succeed();
return order;
}
await new Promise((resolve) => setTimeout(resolve, 500));
}

spinner.fail();
return logAndQuit(`Order ${orderId} - possibly failed`);
}

async function placeSellOrder(options: {
price: number;
contractId: string;
Expand Down Expand Up @@ -139,6 +95,11 @@ async function placeSellOrder(options: {
return logAndQuit(`Contract ${options.contractId} is currently pending. Please try again in a few seconds.`);
}

if (options.accelerators % GPUS_PER_NODE !== 0) {
const exampleCommand = `sf sell -n ${GPUS_PER_NODE} -c ${options.contractId}`;
return logAndQuit(`At the moment, only entire-nodes are available, so you must have a multiple of ${GPUS_PER_NODE} GPUs. Example command:\n\n${exampleCommand}`);
}

const { startDate: contractStartDate, endDate: contractEndDate } = contractStartAndEnd({
shape: {
intervals: contract.shape.intervals,
Expand All @@ -165,15 +126,16 @@ async function placeSellOrder(options: {
endDate = roundEndDate(endDate);
// if the end date is longer than the contract, use the contract end date
if (endDate > contractEndDate) {
endDate = contractEndDate;
endDate = roundEndDate(contractEndDate);
}
const totalDurationSecs = dayjs(endDate).diff(startDate, "s");
const nodes = Math.ceil(options.accelerators / GPUS_PER_NODE);

const totalPrice = pricePerGPUHourToTotalPrice(priceCenticents, totalDurationSecs, options.accelerators, GPUS_PER_NODE);
const totalPrice = pricePerGPUHourToTotalPrice(priceCenticents, totalDurationSecs, nodes, GPUS_PER_NODE);

const params: PlaceSellOrderParameters = {
side: "sell",
quantity: forceAsNumber(options.accelerators) * GPUS_PER_NODE,
quantity: forceAsNumber(options.accelerators) / GPUS_PER_NODE,
price: totalPrice,
contract_id: options.contractId,
start_at: startDate.toISOString(),
Expand Down

0 comments on commit 8a21006

Please sign in to comment.