From 6221e528c548e69516594dbdbbe587405dcb4538 Mon Sep 17 00:00:00 2001 From: flaque Date: Tue, 10 Sep 2024 15:32:04 -0700 Subject: [PATCH 01/10] wip --- src/index.ts | 2 + src/lib/buy.ts | 4 +- src/lib/updown.ts | 67 +++ src/schema.ts | 1328 +++++++++++++++++++++++++++++++-------------- 4 files changed, 984 insertions(+), 417 deletions(-) create mode 100644 src/lib/updown.ts diff --git a/src/index.ts b/src/index.ts index 899c5da..a2ce708 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ import { registerOrders } from "./lib/orders"; import { registerSell } from "./lib/sell"; import { registerSSH } from "./lib/ssh"; import { registerTokens } from "./lib/tokens"; +import { registerUp } from "./lib/updown"; import { registerUpgrade } from "./lib/upgrade"; const program = new Command(); @@ -32,6 +33,7 @@ registerSell(program); registerBalance(program); registerTokens(program); registerUpgrade(program); +registerUp(program); // (development commands) registerDev(program); diff --git a/src/lib/buy.ts b/src/lib/buy.ts index a69af12..06ce899 100644 --- a/src/lib/buy.ts +++ b/src/lib/buy.ts @@ -37,7 +37,7 @@ export function registerBuy(program: Command) { program .command("buy") .description("Place a buy order") - .requiredOption("-t, --type ", "Specify the type of node") + .option("-t, --type ", "Specify the type of node", "h100i") .option("-n, --nodes ", "Specify the number of nodes") .requiredOption("-d, --duration ", "Specify the duration", "1h") .option("-p, --price ", "Specify the price") @@ -150,7 +150,7 @@ async function placeBuyOrderAction(options: SfBuyParamsNormalized) { options.startsAt = { iso: new Date().toISOString(), date: new Date(), - wasSetByUser: false + wasSetByUser: false, }; } const { data: pendingOrder, err } = await placeBuyOrderRequest({ diff --git a/src/lib/updown.ts b/src/lib/updown.ts new file mode 100644 index 0000000..45f9654 --- /dev/null +++ b/src/lib/updown.ts @@ -0,0 +1,67 @@ +import type { Command } from "commander"; +import { apiClient } from "../apiClient"; + +export function registerUp(program: Command) { + const cmd = program + .command("up") + .description("Automatically buy nodes until you have the desired quantity") + .option( + "-n ", + "The number of nodes to purchase continuously", + "1", + ) + .option("-t, --type ", "Specify the type of node", "h100i"); + + cmd.action(async (options) => { + up(options); + }); +} + +async function up(props: { + n: string; + type: string; +}) { + const client = await apiClient(); + + // check if there's already a procurement like this + const procurements = await client.GET("/v0/procurements"); + if (!procurements.response.ok) { + console.error(procurements.error?.message, procurements.error?.details); + throw new Error("Failed to list procurements"); + } + + for (const procurement of procurements.data?.data ?? []) { + // Currently instance groups are the same name as the instance type + // in the future they might be different. + if (procurement.instance_group === props.type) { + const res = await client.PUT("/v0/procurements/{id}", { + params: { + path: { + id: procurement.id, + }, + }, + body: { + quantity: Number.parseInt(props.n), + }, + }); + return res.data + } + } + + const res = await client.POST("/v0/procurements", { + body: { + instance_type: props.type, + quantity: Number.parseInt(props.n), + max_price_per_node_hour: 0.1, + block_duration_in_hours: 1, + }, + }); + + if (!res.response.ok) { + console.error(res.error?.message, res.error?.details); + throw new Error("Failed to purchase nodes"); + } + + return res.data; +} + diff --git a/src/schema.ts b/src/schema.ts index 6c09b3b..847d7b9 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -4,6 +4,38 @@ */ export interface paths { + "/v0/prices": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getV0Prices"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/quote": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getV0Quote"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/v0/orders": { parameters: { query?: never; @@ -116,14 +148,14 @@ export interface paths { patch?: never; trace?: never; }; - "/v0/prices": { + "/v0/balance": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getV0Prices"]; + get: operations["getV0Balance"]; put?: never; post?: never; delete?: never; @@ -132,31 +164,31 @@ export interface paths { patch?: never; trace?: never; }; - "/v0/balance": { + "/v0/procurements": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getV0Balance"]; + get: operations["getV0Procurements"]; put?: never; - post?: never; + post: operations["postV0Procurements"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/v0/quote": { + "/v0/procurements/{id}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["getV0Quote"]; - put?: never; + get: operations["getV0ProcurementsById"]; + put: operations["putV0ProcurementsById"]; post?: never; delete?: never; options?: never; @@ -176,187 +208,27 @@ export interface components { } export type $defs = Record; export interface operations { - getV0Orders: { + getV0Prices: { parameters: { - query?: { - instance_type?: string; - limit?: string; - offset?: string; - min_start_date?: string; - max_start_date?: string; - min_duration?: string; - max_duration?: string; - min_quantity?: string; - max_quantity?: string; - side?: string; - include_public?: string | boolean; - }; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; + query: { + /** @description The instance type. */ + instance_type: string; + /** @description The minimum quantity of nodes filled blocks included in the price calculation contain. */ + min_quantity?: number; + /** @description The maximum quantity of nodes filled blocks included in the price calculation contain. */ + max_quantity?: number; + /** @description The minimum duration, in seconds, of filled blocks. */ + min_duration?: number; + /** @description The maximum duration, in seconds, of filled blocks. */ + max_duration?: number; + /** @description The number of days to go back, starting from today. If you provide 0, you will only see prices for today. If you provide 1, you will see prices over all of yesterday, and today. */ + since_n_days_ago?: number; }; + header?: never; path?: never; cookie?: never; }; requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - }; - }; - postV0Orders: { - parameters: { - query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": - | { - /** @constant */ - side: "buy"; - /** @description The instance type of the order */ - instance_type: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The number of nodes */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ - start_at: string; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags?: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - colocate_with?: string[]; - } - | { - /** @constant */ - side: "sell"; - contract_id: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The number of nodes */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ - start_at: string; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags?: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - }; - "multipart/form-data": - | { - /** @constant */ - side: "buy"; - /** @description The instance type of the order */ - instance_type: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The number of nodes */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ - start_at: string; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags?: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - colocate_with?: string[]; - } - | { - /** @constant */ - side: "sell"; - contract_id: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The number of nodes */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ - start_at: string; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags?: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - }; - "text/plain": - | { - /** @constant */ - side: "buy"; - /** @description The instance type of the order */ - instance_type: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The number of nodes */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ - start_at: string; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags?: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - colocate_with?: string[]; - } - | { - /** @constant */ - side: "sell"; - contract_id: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The number of nodes */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ - start_at: string; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags?: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - }; - }; - }; responses: { 200: { headers: { @@ -364,25 +236,70 @@ export interface operations { }; content: { "application/json": { + data: { + /** @constant */ + object: "price-history-item"; + gpu_hour?: { + /** @description The minimum price per GPU hour for the period (in centicents, 1/100th of a cent). */ + min: number; + /** @description The maximum price per GPU hour for the period (in centicents, 1/100th of a cent). */ + max: number; + /** @description The average price per GPU hour for the period (in centicents, 1/100th of a cent). */ + avg: number; + }; + /** @description ISO 8601 datetime marking the start of the period. */ + period_start: string; + /** @description ISO 8601 datetime marking the end of the period. */ + period_end: string; + /** @description Whether there was no price data for this period. */ + no_data: boolean; + }[]; /** @constant */ - object: "order"; - id: string; - /** @constant */ - status: "pending"; + object: "list"; }; "multipart/form-data": { + data: { + /** @constant */ + object: "price-history-item"; + gpu_hour?: { + /** @description The minimum price per GPU hour for the period (in centicents, 1/100th of a cent). */ + min: number; + /** @description The maximum price per GPU hour for the period (in centicents, 1/100th of a cent). */ + max: number; + /** @description The average price per GPU hour for the period (in centicents, 1/100th of a cent). */ + avg: number; + }; + /** @description ISO 8601 datetime marking the start of the period. */ + period_start: string; + /** @description ISO 8601 datetime marking the end of the period. */ + period_end: string; + /** @description Whether there was no price data for this period. */ + no_data: boolean; + }[]; /** @constant */ - object: "order"; - id: string; - /** @constant */ - status: "pending"; + object: "list"; }; "text/plain": { + data: { + /** @constant */ + object: "price-history-item"; + gpu_hour?: { + /** @description The minimum price per GPU hour for the period (in centicents, 1/100th of a cent). */ + min: number; + /** @description The maximum price per GPU hour for the period (in centicents, 1/100th of a cent). */ + max: number; + /** @description The average price per GPU hour for the period (in centicents, 1/100th of a cent). */ + avg: number; + }; + /** @description ISO 8601 datetime marking the start of the period. */ + period_start: string; + /** @description ISO 8601 datetime marking the end of the period. */ + period_end: string; + /** @description Whether there was no price data for this period. */ + no_data: boolean; + }[]; /** @constant */ - object: "order"; - id: string; - /** @constant */ - status: "pending"; + object: "list"; }; }; }; @@ -419,16 +336,27 @@ export interface operations { }; }; }; - getV0OrdersById: { + getV0Quote: { parameters: { - query?: never; + query: { + side: "buy" | "sell"; + /** @description Inclusive lower bound for the start time, as an ISO 8601 string. The query will consider all valid start times at or after this time. The difference between this and `max_start_time` can be at most 24 hours. */ + min_start_date: string; + /** @description Inclusive upper bound for the start time, as an ISO 8601 string. The query will consider all valid start times on or before this time. The difference between this and `min_start_time` can be at most 24 hours. */ + max_start_date: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The instance type. */ + instance_type?: string; + contract_id?: string; + }; header?: { /** @description Generate a bearer token with `$ sf tokens create`. */ authorization?: string; }; - path: { - id: string; - }; + path?: never; cookie?: never; }; requestBody?: never; @@ -438,11 +366,404 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": { - /** @constant */ - object: "order"; - id: string; - side: "buy" | "sell"; + "application/json": + | { + /** @constant */ + object: "quote"; + /** @constant */ + side: "buy"; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ + start_at: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The instance type. */ + instance_type: string; + } + | { + /** @constant */ + object: "quote"; + /** @constant */ + side: "sell"; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ + start_at: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + contract_id: string; + }; + "multipart/form-data": + | { + /** @constant */ + object: "quote"; + /** @constant */ + side: "buy"; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ + start_at: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The instance type. */ + instance_type: string; + } + | { + /** @constant */ + object: "quote"; + /** @constant */ + side: "sell"; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ + start_at: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + contract_id: string; + }; + "text/plain": + | { + /** @constant */ + object: "quote"; + /** @constant */ + side: "buy"; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ + start_at: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The instance type. */ + instance_type: string; + } + | { + /** @constant */ + object: "quote"; + /** @constant */ + side: "sell"; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ + start_at: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + contract_id: string; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; + }; + }; + getV0Orders: { + parameters: { + query?: { + instance_type?: string; + limit?: string; + offset?: string; + min_start_date?: string; + max_start_date?: string; + min_duration?: string; + max_duration?: string; + min_quantity?: string; + max_quantity?: string; + side?: string; + include_public?: boolean; + }; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + postV0Orders: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": + | { + /** @constant */ + side: "buy"; + /** @description The instance type. */ + instance_type: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ + start_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags?: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + colocate_with?: string[]; + } + | { + /** @constant */ + side: "sell"; + contract_id: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ + start_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags?: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + }; + "multipart/form-data": + | { + /** @constant */ + side: "buy"; + /** @description The instance type. */ + instance_type: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ + start_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags?: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + colocate_with?: string[]; + } + | { + /** @constant */ + side: "sell"; + contract_id: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ + start_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags?: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + }; + "text/plain": + | { + /** @constant */ + side: "buy"; + /** @description The instance type. */ + instance_type: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ + start_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags?: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + colocate_with?: string[]; + } + | { + /** @constant */ + side: "sell"; + contract_id: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ + start_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags?: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "order"; + id: string; + /** @constant */ + status: "pending"; + }; + "multipart/form-data": { + /** @constant */ + object: "order"; + id: string; + /** @constant */ + status: "pending"; + }; + "text/plain": { + /** @constant */ + object: "order"; + id: string; + /** @constant */ + status: "pending"; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; + }; + }; + getV0OrdersById: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "order"; + id: string; + side: "buy" | "sell"; status: | "pending" | "rejected" @@ -450,15 +771,15 @@ export interface operations { | "cancelled" | "filled" | "expired"; - /** @description The instance type of the order */ + /** @description The instance type. */ instance_type: string; /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ duration: number; - /** @description The number of nodes */ + /** @description The number of nodes. */ quantity: number; /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ start_at: string; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ price: number; flags: { /** @description If true, this will be a market order. */ @@ -488,15 +809,15 @@ export interface operations { | "cancelled" | "filled" | "expired"; - /** @description The instance type of the order */ + /** @description The instance type. */ instance_type: string; /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ duration: number; - /** @description The number of nodes */ + /** @description The number of nodes. */ quantity: number; /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ start_at: string; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ price: number; flags: { /** @description If true, this will be a market order. */ @@ -526,15 +847,15 @@ export interface operations { | "cancelled" | "filled" | "expired"; - /** @description The instance type of the order */ + /** @description The instance type. */ instance_type: string; /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ duration: number; - /** @description The number of nodes */ + /** @description The number of nodes. */ quantity: number; /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ start_at: string; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ price: number; flags: { /** @description If true, this will be a market order. */ @@ -1029,7 +1350,7 @@ export interface operations { id: string; /** Format: date-time */ created_at: string; - /** @description The instance type of the order */ + /** @description The instance type. */ instance_type: string; /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ shape: { @@ -1060,7 +1381,7 @@ export interface operations { id: string; /** Format: date-time */ created_at: string; - /** @description The instance type of the order */ + /** @description The instance type. */ instance_type: string; /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ shape: { @@ -1091,7 +1412,7 @@ export interface operations { id: string; /** Format: date-time */ created_at: string; - /** @description The instance type of the order */ + /** @description The instance type. */ instance_type: string; /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ shape: { @@ -1175,7 +1496,7 @@ export interface operations { id: string; /** Format: date-time */ created_at: string; - /** @description The instance type of the order */ + /** @description The instance type. */ instance_type: string; /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ shape: { @@ -1201,7 +1522,7 @@ export interface operations { id: string; /** Format: date-time */ created_at: string; - /** @description The instance type of the order */ + /** @description The instance type. */ instance_type: string; /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ shape: { @@ -1227,7 +1548,7 @@ export interface operations { id: string; /** Format: date-time */ created_at: string; - /** @description The instance type of the order */ + /** @description The instance type. */ instance_type: string; /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ shape: { @@ -1279,19 +1600,244 @@ export interface operations { }; }; }; - getV0Prices: { + getV0Balance: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "balance"; + available: { + /** @description Funds available to spend or withdraw. */ + amount: number; + /** @constant */ + currency: "usd"; + }; + reserved: { + /** @description Funds held in reserve for pending withdrawals & open buy orders. */ + amount: number; + /** @constant */ + currency: "usd"; + }; + }; + "multipart/form-data": { + /** @constant */ + object: "balance"; + available: { + /** @description Funds available to spend or withdraw. */ + amount: number; + /** @constant */ + currency: "usd"; + }; + reserved: { + /** @description Funds held in reserve for pending withdrawals & open buy orders. */ + amount: number; + /** @constant */ + currency: "usd"; + }; + }; + "text/plain": { + /** @constant */ + object: "balance"; + available: { + /** @description Funds available to spend or withdraw. */ + amount: number; + /** @constant */ + currency: "usd"; + }; + reserved: { + /** @description Funds held in reserve for pending withdrawals & open buy orders. */ + amount: number; + /** @constant */ + currency: "usd"; + }; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; + }; + }; + getV0Procurements: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }[]; + /** @constant */ + object: "list"; + }; + "multipart/form-data": { + data: { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }[]; + /** @constant */ + object: "list"; + }; + "text/plain": { + data: { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }[]; + /** @constant */ + object: "list"; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; + }; + }; + postV0Procurements: { parameters: { - query: { - instance_type: string; - quantity?: string | number; - duration?: string | number; - since?: string | number; + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; }; - header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + /** @description The instance type. */ + instance_type: string; + quantity: number; + max_price_per_node_hour: number; + block_duration_in_hours: number; + }; + "multipart/form-data": { + /** @description The instance type. */ + instance_type: string; + quantity: number; + max_price_per_node_hour: number; + block_duration_in_hours: number; + }; + "text/plain": { + /** @description The instance type. */ + instance_type: string; + quantity: number; + max_price_per_node_hour: number; + block_duration_in_hours: number; + }; + }; + }; responses: { 200: { headers: { @@ -1299,34 +1845,43 @@ export interface operations { }; content: { "application/json": { - data: { - expected: number; - min: number; - max: number; - timestamp: string; - }[]; - /** @constant */ - object: "list"; + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; }; "multipart/form-data": { - data: { - expected: number; - min: number; - max: number; - timestamp: string; - }[]; - /** @constant */ - object: "list"; + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; }; "text/plain": { - data: { - expected: number; - min: number; - max: number; - timestamp: string; - }[]; - /** @constant */ - object: "list"; + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; }; }; }; @@ -1363,14 +1918,13 @@ export interface operations { }; }; }; - getV0Balance: { + getV0ProcurementsById: { parameters: { query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; + header?: never; + path: { + id: string; }; - path?: never; cookie?: never; }; requestBody?: never; @@ -1381,52 +1935,43 @@ export interface operations { }; content: { "application/json": { - /** @constant */ - object: "balance"; - available: { - /** @description Funds available to spend or withdraw. */ - amount: number; - /** @constant */ - currency: "usd"; - }; - reserved: { - /** @description Funds held in reserve for pending withdrawals & open buy orders. */ - amount: number; - /** @constant */ - currency: "usd"; - }; + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; }; "multipart/form-data": { - /** @constant */ - object: "balance"; - available: { - /** @description Funds available to spend or withdraw. */ - amount: number; - /** @constant */ - currency: "usd"; - }; - reserved: { - /** @description Funds held in reserve for pending withdrawals & open buy orders. */ - amount: number; - /** @constant */ - currency: "usd"; - }; + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; }; "text/plain": { - /** @constant */ - object: "balance"; - available: { - /** @description Funds available to spend or withdraw. */ - amount: number; - /** @constant */ - currency: "usd"; - }; - reserved: { - /** @description Funds held in reserve for pending withdrawals & open buy orders. */ - amount: number; - /** @constant */ - currency: "usd"; - }; + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; }; }; }; @@ -1463,132 +2008,85 @@ export interface operations { }; }; }; - getV0Quote: { + putV0ProcurementsById: { parameters: { - query: { - side: "buy" | "sell"; - /** @description Inclusive lower bound for the start time, as an ISO 8601 string. The query will consider all valid start times at or after this time. The difference between this and `max_start_time` can be at most 24 hours. */ - min_start_date: string; - /** @description Inclusive upper bound for the start time, as an ISO 8601 string. The query will consider all valid start times on or before this time. The difference between this and `min_start_time` can be at most 24 hours. */ - max_start_date: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The number of nodes */ - quantity: number; - /** @description The instance type of the order */ - instance_type?: string; - contract_id?: string; - }; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; + query?: never; + header?: never; + path: { + id: string; }; - path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + quantity?: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price?: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours?: number; + }; + "multipart/form-data": { + quantity?: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price?: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours?: number; + }; + "text/plain": { + quantity?: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price?: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours?: number; + }; + }; + }; responses: { 200: { headers: { [name: string]: unknown; }; content: { - "application/json": - | { - /** @constant */ - object: "quote"; - /** @constant */ - side: "buy"; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - /** @description The number of nodes */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ - start_at: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The instance type of the order */ - instance_type: string; - } - | { - /** @constant */ - object: "quote"; - /** @constant */ - side: "sell"; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - /** @description The number of nodes */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ - start_at: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - contract_id: string; - }; - "multipart/form-data": - | { - /** @constant */ - object: "quote"; - /** @constant */ - side: "buy"; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - /** @description The number of nodes */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ - start_at: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The instance type of the order */ - instance_type: string; - } - | { - /** @constant */ - object: "quote"; - /** @constant */ - side: "sell"; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - /** @description The number of nodes */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ - start_at: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - contract_id: string; - }; - "text/plain": - | { - /** @constant */ - object: "quote"; - /** @constant */ - side: "buy"; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - /** @description The number of nodes */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ - start_at: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The instance type of the order */ - instance_type: string; - } - | { - /** @constant */ - object: "quote"; - /** @constant */ - side: "sell"; - /** @description Amount in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - /** @description The number of nodes */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start that aren't "right now" will be rounded up to the nearest the hour. For example, if it's 16:00, you put in 17:10, the start time will be rounded up to 18:00. However, if it's 17:10, and you put in 17:10, the start time will be 17:10. */ - start_at: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - contract_id: string; - }; + "application/json": { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }; + "multipart/form-data": { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }; + "text/plain": { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }; }; }; 500: { From 592f46699976d4ac17bd05ae1765ec6a188d3a62 Mon Sep 17 00:00:00 2001 From: flaque Date: Wed, 11 Sep 2024 13:32:50 -0700 Subject: [PATCH 02/10] fix up down --- src/lib/updown.ts | 65 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/src/lib/updown.ts b/src/lib/updown.ts index 45f9654..03e8b28 100644 --- a/src/lib/updown.ts +++ b/src/lib/updown.ts @@ -1,5 +1,8 @@ import type { Command } from "commander"; import { apiClient } from "../apiClient"; +import parseDuration from "parse-duration"; +import { logAndQuit } from "../helpers/errors"; +import { getBalance } from "./balance"; export function registerUp(program: Command) { const cmd = program @@ -10,19 +13,65 @@ export function registerUp(program: Command) { "The number of nodes to purchase continuously", "1", ) - .option("-t, --type ", "Specify the type of node", "h100i"); + .option("-t, --type ", "Specify the type of node", "h100i") + .option("-d, --duration ", "Specify the minimum duration") + .option("-p, --price ", "Specify the maximum price per node per hour"); cmd.action(async (options) => { up(options); }); } +function getDefaultProcurementOptions(props: { + duration?: string; + n?: string; + pricePerNodeHour?: string; + type?: string; +}) { + // Minimum block duration is 2 hours + // which is a bit of a smoother experience (we might want to increase this) + const duration = props.duration ?? "2h"; + const durationHours = parseDuration(duration, "h"); + if (!durationHours) { + logAndQuit(`Failed to parse duration: ${duration}`); + } + + const defaultPrice = 2.65 * 8; + const pricePerNodeHourInDollars = props.pricePerNodeHour ? Number.parseInt(props.pricePerNodeHour) : defaultPrice; + const pricePerNodeHourInCenticents = Math.ceil(pricePerNodeHourInDollars * 10_000); + + const totalPriceInCenticents = pricePerNodeHourInCenticents * Number.parseInt(props.n ?? "1") * durationHours; + + return { + durationHours: Math.ceil(durationHours), + pricePerNodeHourInCenticents: pricePerNodeHourInCenticents, + n: Number.parseInt(props.n ?? "1"), + type: props.type ?? "h100i", + totalPriceInCenticents + }; +} + async function up(props: { n: string; type: string; + duration?: string; + price?: string; }) { const client = await apiClient(); + const { durationHours, n, type, pricePerNodeHourInCenticents, totalPriceInCenticents } = getDefaultProcurementOptions(props); + + if (durationHours && durationHours < 1) { + console.error("Minimum duration is 1 hour"); + return; + } + + const balance = await getBalance(); + if (balance.available.centicents < totalPriceInCenticents) { + console.error(`Insufficient balance to purchase nodes. Available balance: $${(balance.available.centicents / 1000000).toFixed(2)}, Total price: $${(totalPriceInCenticents / 1000000).toFixed(2)}`); + return; + } + // check if there's already a procurement like this const procurements = await client.GET("/v0/procurements"); if (!procurements.response.ok) { @@ -41,7 +90,11 @@ async function up(props: { }, }, body: { - quantity: Number.parseInt(props.n), + quantity: n, + + // we only update the duration & price if it's set + block_duration_in_hours: props.duration ? durationHours : undefined, + max_price_per_node_hour: props.price ? pricePerNodeHourInCenticents : undefined, }, }); return res.data @@ -50,10 +103,10 @@ async function up(props: { const res = await client.POST("/v0/procurements", { body: { - instance_type: props.type, - quantity: Number.parseInt(props.n), - max_price_per_node_hour: 0.1, - block_duration_in_hours: 1, + instance_type: type, + quantity: n, + max_price_per_node_hour: pricePerNodeHourInCenticents, + block_duration_in_hours: Math.max(durationHours, 1), }, }); From 3c4a65698dced4c10abc03d4bb7a1ab51982d016 Mon Sep 17 00:00:00 2001 From: flaque Date: Wed, 11 Sep 2024 14:28:08 -0700 Subject: [PATCH 03/10] add up --- src/lib/buy.ts | 11 +---- src/lib/updown.ts | 116 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 106 insertions(+), 21 deletions(-) diff --git a/src/lib/buy.ts b/src/lib/buy.ts index e751d50..6720cb5 100644 --- a/src/lib/buy.ts +++ b/src/lib/buy.ts @@ -92,12 +92,9 @@ async function buyOrderAction(options: SfBuyOptions) { if (options.quote) { const quote = await getQuote({ instanceType: options.type, - priceCenticents, quantity: quantity, startsAt: startDate, durationSeconds, - confirmWithUser, - quoteOnly: isQuoteOnly, }); if (!quote) { @@ -112,12 +109,9 @@ async function buyOrderAction(options: SfBuyOptions) { if (!priceCenticents) { const quote = await getQuote({ instanceType: options.type, - priceCenticents, quantity: quantity, startsAt: startDate, durationSeconds, - confirmWithUser, - quoteOnly: isQuoteOnly, }); if (!quote) { @@ -335,14 +329,11 @@ export async function placeBuyOrder(options: BuyOptions) { type QuoteOptions = { instanceType: string; - priceCenticents: Nullable; quantity: number; startsAt: Date; durationSeconds: number; - confirmWithUser: boolean; - quoteOnly: boolean; }; -async function getQuote(options: QuoteOptions) { +export async function getQuote(options: QuoteOptions) { const api = await apiClient(); const { data, error, response } = await api.GET("/v0/quote", { diff --git a/src/lib/updown.ts b/src/lib/updown.ts index 03e8b28..d315f9d 100644 --- a/src/lib/updown.ts +++ b/src/lib/updown.ts @@ -1,8 +1,13 @@ +import { confirm } from "@inquirer/prompts"; import type { Command } from "commander"; import { apiClient } from "../apiClient"; import parseDuration from "parse-duration"; import { logAndQuit } from "../helpers/errors"; import { getBalance } from "./balance"; +import c from "chalk"; +import { formatDuration } from "./orders"; +import { centicentsToDollarsFormatted } from "../helpers/units"; +import { getQuote } from "./buy"; export function registerUp(program: Command) { const cmd = program @@ -22,7 +27,9 @@ export function registerUp(program: Command) { }); } -function getDefaultProcurementOptions(props: { +const DEFAULT_PRICE_PER_NODE_HOUR_IN_CENTICENTS = 2.65 * 8 * 10_000; + +async function getDefaultProcurementOptions(props: { duration?: string; n?: string; pricePerNodeHour?: string; @@ -31,44 +38,131 @@ function getDefaultProcurementOptions(props: { // Minimum block duration is 2 hours // which is a bit of a smoother experience (we might want to increase this) const duration = props.duration ?? "2h"; - const durationHours = parseDuration(duration, "h"); + let durationHours = parseDuration(duration, "h"); if (!durationHours) { logAndQuit(`Failed to parse duration: ${duration}`); } + durationHours = Math.ceil(durationHours); + const n = Number.parseInt(props.n ?? "1"); + const type = props.type ?? "h100i"; + + const quote = await getQuote({ + instanceType: type, + quantity: n, + // Start immediately + startsAt: new Date(), + durationSeconds: durationHours * 60 * 60, + }); + + // Eventually we should replace this price with yesterday's index price + let quotePrice = DEFAULT_PRICE_PER_NODE_HOUR_IN_CENTICENTS; + if (quote) { + // per hour price + quotePrice = quote.price / durationHours; + } - const defaultPrice = 2.65 * 8; - const pricePerNodeHourInDollars = props.pricePerNodeHour ? Number.parseInt(props.pricePerNodeHour) : defaultPrice; - const pricePerNodeHourInCenticents = Math.ceil(pricePerNodeHourInDollars * 10_000); + const pricePerNodeHourInDollars = props.pricePerNodeHour ? Number.parseInt(props.pricePerNodeHour) : quotePrice; + const pricePerNodeHourInCenticents = Math.ceil(pricePerNodeHourInDollars); const totalPriceInCenticents = pricePerNodeHourInCenticents * Number.parseInt(props.n ?? "1") * durationHours; return { - durationHours: Math.ceil(durationHours), - pricePerNodeHourInCenticents: pricePerNodeHourInCenticents, - n: Number.parseInt(props.n ?? "1"), - type: props.type ?? "h100i", + durationHours, + pricePerNodeHourInCenticents, + n, + type, totalPriceInCenticents }; } + +// Instruct the user to set a price that's lower +function getSuggestedCommandWhenBalanceLow(props: { + durationHours: number; + pricePerNodeHourInCenticents: number; + n: number; + totalPriceInCenticents: number; + balance: number; +}) { + const affordablePrice = (props.balance / 100) / (props.n * props.durationHours); + + const cmd = `sf up -n ${props.n} -d ${props.durationHours}h -p ${affordablePrice.toFixed(2)}`; + return `You could try setting a lower price and your nodes will turn on\nif the market price dips this low:\n\n\t${cmd}\n`; +} + + +function confirmPlaceOrderMessage(options: { + durationHours: number; + pricePerNodeHourInCenticents: number; + n: number; + totalPriceInCenticents: number; + type: string; +}) { + + const totalNodesLabel = c.green(options.n); + const instanceTypeLabel = c.green(options.type); + const nodesLabel = options.n > 1 ? "nodes" : "node"; + const durationInMilliseconds = options.durationHours * 60 * 60 * 1000; + + const timeDescription = `starting ${c.green("ASAP")} until you turn it off`; + + const topLine = `Turn on ${totalNodesLabel} ${instanceTypeLabel} ${nodesLabel} continuously for ${c.green(formatDuration(durationInMilliseconds))} ${timeDescription}`; + + const dollarsLabel = c.green( + centicentsToDollarsFormatted(options.pricePerNodeHourInCenticents * options.durationHours * options.n), + ); + + const priceLine = `\n Pay a minimum of ${dollarsLabel}?`; + + return `${topLine}\n${priceLine} `; +} + async function up(props: { n: string; type: string; duration?: string; price?: string; + y: boolean; }) { const client = await apiClient(); - const { durationHours, n, type, pricePerNodeHourInCenticents, totalPriceInCenticents } = getDefaultProcurementOptions(props); + const { durationHours, n, type, pricePerNodeHourInCenticents, totalPriceInCenticents } = await getDefaultProcurementOptions(props); if (durationHours && durationHours < 1) { console.error("Minimum duration is 1 hour"); return; } + if (!props.y) { + const confirmationMessage = confirmPlaceOrderMessage({ + durationHours, + pricePerNodeHourInCenticents, + n, + totalPriceInCenticents, + type, + }) + const confirmed = await confirm({ + message: confirmationMessage, + default: false, + }); + + if (!confirmed) { + logAndQuit("Order cancelled"); + } + } + const balance = await getBalance(); + if (balance.available.centicents < totalPriceInCenticents) { - console.error(`Insufficient balance to purchase nodes. Available balance: $${(balance.available.centicents / 1000000).toFixed(2)}, Total price: $${(totalPriceInCenticents / 1000000).toFixed(2)}`); + console.log(`You can't afford this. Available balance: $${(balance.available.centicents / 1000000).toFixed(2)}, Minimum price: $${(totalPriceInCenticents / 1000000).toFixed(2)}\n`); + const cmd = getSuggestedCommandWhenBalanceLow({ + durationHours, + pricePerNodeHourInCenticents, + n, + totalPriceInCenticents, + balance: balance.available.whole, + }); + console.log(cmd); return; } From c3079932819e3afebea06ad7c22d49010cc4b5ec Mon Sep 17 00:00:00 2001 From: flaque Date: Wed, 11 Sep 2024 17:29:26 -0700 Subject: [PATCH 04/10] use new api correctly --- src/index.ts | 3 ++- src/lib/orders.ts | 1 + src/lib/updown.ts | 59 +++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index a2ce708..95fc00c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ import { registerOrders } from "./lib/orders"; import { registerSell } from "./lib/sell"; import { registerSSH } from "./lib/ssh"; import { registerTokens } from "./lib/tokens"; -import { registerUp } from "./lib/updown"; +import { registerDown, registerUp } from "./lib/updown"; import { registerUpgrade } from "./lib/upgrade"; const program = new Command(); @@ -34,6 +34,7 @@ registerBalance(program); registerTokens(program); registerUpgrade(program); registerUp(program); +registerDown(program); // (development commands) registerDev(program); diff --git a/src/lib/orders.ts b/src/lib/orders.ts index b379765..4e11c18 100644 --- a/src/lib/orders.ts +++ b/src/lib/orders.ts @@ -135,6 +135,7 @@ function printAsTable(orders: Array) { const duration = formatDuration( dayjs(order.end_at).diff(dayjs(startDate), "ms"), ); + console.log(order.price); table.push([ order.id, order.side, diff --git a/src/lib/updown.ts b/src/lib/updown.ts index d315f9d..d6d0151 100644 --- a/src/lib/updown.ts +++ b/src/lib/updown.ts @@ -27,6 +27,17 @@ export function registerUp(program: Command) { }); } +export function registerDown(program: Command) { + const cmd = program + .command("down") + .description("Turn off nodes") + .option("-t, --type ", "Specify the type of node", "h100i"); + + cmd.action(async (options) => { + down(options); + }); +} + const DEFAULT_PRICE_PER_NODE_HOUR_IN_CENTICENTS = 2.65 * 8 * 10_000; async function getDefaultProcurementOptions(props: { @@ -106,13 +117,13 @@ function confirmPlaceOrderMessage(options: { const timeDescription = `starting ${c.green("ASAP")} until you turn it off`; - const topLine = `Turn on ${totalNodesLabel} ${instanceTypeLabel} ${nodesLabel} continuously for ${c.green(formatDuration(durationInMilliseconds))} ${timeDescription}`; + const topLine = `Turning on ${totalNodesLabel} ${instanceTypeLabel} ${nodesLabel} continuously for ${c.green(formatDuration(durationInMilliseconds))} ${timeDescription}`; const dollarsLabel = c.green( - centicentsToDollarsFormatted(options.pricePerNodeHourInCenticents * options.durationHours * options.n), + centicentsToDollarsFormatted(options.pricePerNodeHourInCenticents), ); - const priceLine = `\n Pay a minimum of ${dollarsLabel}?`; + const priceLine = `\n Pay ${dollarsLabel} per node hour?`; return `${topLine}\n${priceLine} `; } @@ -187,7 +198,7 @@ async function up(props: { quantity: n, // we only update the duration & price if it's set - block_duration_in_hours: props.duration ? durationHours : undefined, + min_duration_in_hours: props.duration ? durationHours : undefined, max_price_per_node_hour: props.price ? pricePerNodeHourInCenticents : undefined, }, }); @@ -200,7 +211,7 @@ async function up(props: { instance_type: type, quantity: n, max_price_per_node_hour: pricePerNodeHourInCenticents, - block_duration_in_hours: Math.max(durationHours, 1), + min_duration_in_hours: Math.max(durationHours, 1), }, }); @@ -212,3 +223,41 @@ async function up(props: { return res.data; } +async function down(props: { + type: string; +}) { + const client = await apiClient(); + + // check if there's already a procurement like this + const procurements = await client.GET("/v0/procurements"); + if (!procurements.response.ok) { + console.error(procurements.error?.message, procurements.error?.details); + throw new Error("Failed to list procurements"); + } + + const procurement = procurements.data?.data.find((p: any) => p.instance_group === props.type); + + if (!procurement) { + console.error(`No procurement found for ${props.type}`); + return; + } + + const res = await client.PUT("/v0/procurements/{id}", { + params: { + path: { + id: procurement.id, + }, + }, + body: { + quantity: 0, + block_duration_in_hours: 0, + }, + }); + + if (!res.response.ok) { + console.error(res.error?.message, res.error?.details); + throw new Error("Failed to turn off nodes"); + } + + return res.data; +} \ No newline at end of file From 5f0bfb4eb53b9244a0a45ca57a72b91672ebc19f Mon Sep 17 00:00:00 2001 From: flaque Date: Wed, 11 Sep 2024 18:30:53 -0700 Subject: [PATCH 05/10] fix orders --- src/lib/orders.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/orders.ts b/src/lib/orders.ts index 4e11c18..b379765 100644 --- a/src/lib/orders.ts +++ b/src/lib/orders.ts @@ -135,7 +135,6 @@ function printAsTable(orders: Array) { const duration = formatDuration( dayjs(order.end_at).diff(dayjs(startDate), "ms"), ); - console.log(order.price); table.push([ order.id, order.side, From 1f801bbbca622139bd17ea2ae913bd24ade1a7fd Mon Sep 17 00:00:00 2001 From: flaque Date: Thu, 12 Sep 2024 15:55:55 -0700 Subject: [PATCH 06/10] add more --- src/lib/updown.ts | 449 +++-- src/schema.ts | 4775 +++++++++++++++++++++++---------------------- 2 files changed, 2643 insertions(+), 2581 deletions(-) diff --git a/src/lib/updown.ts b/src/lib/updown.ts index d6d0151..5bc834e 100644 --- a/src/lib/updown.ts +++ b/src/lib/updown.ts @@ -1,263 +1,280 @@ import { confirm } from "@inquirer/prompts"; +import c from "chalk"; import type { Command } from "commander"; -import { apiClient } from "../apiClient"; import parseDuration from "parse-duration"; +import { apiClient } from "../apiClient"; import { logAndQuit } from "../helpers/errors"; -import { getBalance } from "./balance"; -import c from "chalk"; -import { formatDuration } from "./orders"; import { centicentsToDollarsFormatted } from "../helpers/units"; +import { getBalance } from "./balance"; import { getQuote } from "./buy"; +import { formatDuration } from "./orders"; export function registerUp(program: Command) { - const cmd = program - .command("up") - .description("Automatically buy nodes until you have the desired quantity") - .option( - "-n ", - "The number of nodes to purchase continuously", - "1", - ) - .option("-t, --type ", "Specify the type of node", "h100i") - .option("-d, --duration ", "Specify the minimum duration") - .option("-p, --price ", "Specify the maximum price per node per hour"); - - cmd.action(async (options) => { - up(options); - }); + const cmd = program + .command("up") + .description("Automatically buy nodes until you have the desired quantity") + .option( + "-n ", + "The number of nodes to purchase continuously", + "1", + ) + .option("-t, --type ", "Specify the type of node", "h100i") + .option("-d, --duration ", "Specify the minimum duration") + .option( + "-p, --price ", + "Specify the maximum price per node per hour", + ); + + cmd.action(async (options) => { + up(options); + }); } export function registerDown(program: Command) { - const cmd = program - .command("down") - .description("Turn off nodes") - .option("-t, --type ", "Specify the type of node", "h100i"); - - cmd.action(async (options) => { - down(options); - }); + const cmd = program + .command("down") + .description("Turn off nodes") + .option("-t, --type ", "Specify the type of node", "h100i"); + + cmd.action(async (options) => { + down(options); + }); } const DEFAULT_PRICE_PER_NODE_HOUR_IN_CENTICENTS = 2.65 * 8 * 10_000; async function getDefaultProcurementOptions(props: { - duration?: string; - n?: string; - pricePerNodeHour?: string; - type?: string; + duration?: string; + n?: string; + pricePerNodeHour?: string; + type?: string; }) { - // Minimum block duration is 2 hours - // which is a bit of a smoother experience (we might want to increase this) - const duration = props.duration ?? "2h"; - let durationHours = parseDuration(duration, "h"); - if (!durationHours) { - logAndQuit(`Failed to parse duration: ${duration}`); - } - durationHours = Math.ceil(durationHours); - const n = Number.parseInt(props.n ?? "1"); - const type = props.type ?? "h100i"; - - const quote = await getQuote({ - instanceType: type, - quantity: n, - // Start immediately - startsAt: new Date(), - durationSeconds: durationHours * 60 * 60, - }); - - // Eventually we should replace this price with yesterday's index price - let quotePrice = DEFAULT_PRICE_PER_NODE_HOUR_IN_CENTICENTS; - if (quote) { - // per hour price - quotePrice = quote.price / durationHours; - } - - const pricePerNodeHourInDollars = props.pricePerNodeHour ? Number.parseInt(props.pricePerNodeHour) : quotePrice; - const pricePerNodeHourInCenticents = Math.ceil(pricePerNodeHourInDollars); - - const totalPriceInCenticents = pricePerNodeHourInCenticents * Number.parseInt(props.n ?? "1") * durationHours; - - return { - durationHours, - pricePerNodeHourInCenticents, - n, - type, - totalPriceInCenticents - }; + // Minimum block duration is 2 hours + // which is a bit of a smoother experience (we might want to increase this) + const duration = props.duration ?? "2h"; + let durationHours = parseDuration(duration, "h"); + if (!durationHours) { + logAndQuit(`Failed to parse duration: ${duration}`); + } + durationHours = Math.ceil(durationHours); + const n = Number.parseInt(props.n ?? "1"); + const type = props.type ?? "h100i"; + + const quote = await getQuote({ + instanceType: type, + quantity: n, + // Start immediately + startsAt: new Date(), + durationSeconds: durationHours * 60 * 60, + }); + + // Eventually we should replace this price with yesterday's index price + let quotePrice = DEFAULT_PRICE_PER_NODE_HOUR_IN_CENTICENTS; + if (quote) { + // per hour price + quotePrice = quote.price / durationHours; + } + + const pricePerNodeHourInDollars = props.pricePerNodeHour + ? Number.parseInt(props.pricePerNodeHour) + : quotePrice; + const pricePerNodeHourInCenticents = Math.ceil(pricePerNodeHourInDollars); + + const totalPriceInCenticents = + pricePerNodeHourInCenticents * + Number.parseInt(props.n ?? "1") * + durationHours; + + return { + durationHours, + pricePerNodeHourInCenticents, + n, + type, + totalPriceInCenticents, + }; } - // Instruct the user to set a price that's lower function getSuggestedCommandWhenBalanceLow(props: { - durationHours: number; - pricePerNodeHourInCenticents: number; - n: number; - totalPriceInCenticents: number; - balance: number; + durationHours: number; + pricePerNodeHourInCenticents: number; + n: number; + totalPriceInCenticents: number; + balance: number; }) { - const affordablePrice = (props.balance / 100) / (props.n * props.durationHours); + const affordablePrice = props.balance / 100 / (props.n * props.durationHours); - const cmd = `sf up -n ${props.n} -d ${props.durationHours}h -p ${affordablePrice.toFixed(2)}`; - return `You could try setting a lower price and your nodes will turn on\nif the market price dips this low:\n\n\t${cmd}\n`; + const cmd = `sf up -n ${props.n} -d ${props.durationHours}h -p ${affordablePrice.toFixed(2)}`; + return `You could try setting a lower price and your nodes will turn on\nif the market price dips this low:\n\n\t${cmd}\n`; } - function confirmPlaceOrderMessage(options: { - durationHours: number; - pricePerNodeHourInCenticents: number; - n: number; - totalPriceInCenticents: number; - type: string; + durationHours: number; + pricePerNodeHourInCenticents: number; + n: number; + totalPriceInCenticents: number; + type: string; }) { + const totalNodesLabel = c.green(options.n); + const instanceTypeLabel = c.green(options.type); + const nodesLabel = options.n > 1 ? "nodes" : "node"; + const durationInMilliseconds = options.durationHours * 60 * 60 * 1000; - const totalNodesLabel = c.green(options.n); - const instanceTypeLabel = c.green(options.type); - const nodesLabel = options.n > 1 ? "nodes" : "node"; - const durationInMilliseconds = options.durationHours * 60 * 60 * 1000; + const timeDescription = `starting ${c.green("ASAP")} until you turn it off`; - const timeDescription = `starting ${c.green("ASAP")} until you turn it off`; + const topLine = `Turning on ${totalNodesLabel} ${instanceTypeLabel} ${nodesLabel} continuously for ${c.green(formatDuration(durationInMilliseconds))} ${timeDescription}`; - const topLine = `Turning on ${totalNodesLabel} ${instanceTypeLabel} ${nodesLabel} continuously for ${c.green(formatDuration(durationInMilliseconds))} ${timeDescription}`; + const dollarsLabel = c.green( + centicentsToDollarsFormatted(options.pricePerNodeHourInCenticents), + ); - const dollarsLabel = c.green( - centicentsToDollarsFormatted(options.pricePerNodeHourInCenticents), - ); - - const priceLine = `\n Pay ${dollarsLabel} per node hour?`; + const priceLine = `\n Pay ${dollarsLabel} per node hour?`; - return `${topLine}\n${priceLine} `; + return `${topLine}\n${priceLine} `; } async function up(props: { - n: string; - type: string; - duration?: string; - price?: string; - y: boolean; + n: string; + type: string; + duration?: string; + price?: string; + y: boolean; }) { - const client = await apiClient(); - - const { durationHours, n, type, pricePerNodeHourInCenticents, totalPriceInCenticents } = await getDefaultProcurementOptions(props); - - if (durationHours && durationHours < 1) { - console.error("Minimum duration is 1 hour"); - return; - } - - if (!props.y) { - const confirmationMessage = confirmPlaceOrderMessage({ - durationHours, - pricePerNodeHourInCenticents, - n, - totalPriceInCenticents, - type, - }) - const confirmed = await confirm({ - message: confirmationMessage, - default: false, - }); - - if (!confirmed) { - logAndQuit("Order cancelled"); - } - } - - const balance = await getBalance(); - - if (balance.available.centicents < totalPriceInCenticents) { - console.log(`You can't afford this. Available balance: $${(balance.available.centicents / 1000000).toFixed(2)}, Minimum price: $${(totalPriceInCenticents / 1000000).toFixed(2)}\n`); - const cmd = getSuggestedCommandWhenBalanceLow({ - durationHours, - pricePerNodeHourInCenticents, - n, - totalPriceInCenticents, - balance: balance.available.whole, - }); - console.log(cmd); - return; - } - - // check if there's already a procurement like this - const procurements = await client.GET("/v0/procurements"); - if (!procurements.response.ok) { - console.error(procurements.error?.message, procurements.error?.details); - throw new Error("Failed to list procurements"); - } - - for (const procurement of procurements.data?.data ?? []) { - // Currently instance groups are the same name as the instance type - // in the future they might be different. - if (procurement.instance_group === props.type) { - const res = await client.PUT("/v0/procurements/{id}", { - params: { - path: { - id: procurement.id, - }, - }, - body: { - quantity: n, - - // we only update the duration & price if it's set - min_duration_in_hours: props.duration ? durationHours : undefined, - max_price_per_node_hour: props.price ? pricePerNodeHourInCenticents : undefined, - }, - }); - return res.data - } - } - - const res = await client.POST("/v0/procurements", { - body: { - instance_type: type, - quantity: n, - max_price_per_node_hour: pricePerNodeHourInCenticents, - min_duration_in_hours: Math.max(durationHours, 1), - }, + const client = await apiClient(); + + const { + durationHours, + n, + type, + pricePerNodeHourInCenticents, + totalPriceInCenticents, + } = await getDefaultProcurementOptions(props); + + if (durationHours && durationHours < 1) { + console.error("Minimum duration is 1 hour"); + return; + } + + if (!props.y) { + const confirmationMessage = confirmPlaceOrderMessage({ + durationHours, + pricePerNodeHourInCenticents, + n, + totalPriceInCenticents, + type, + }); + const confirmed = await confirm({ + message: confirmationMessage, + default: false, }); - if (!res.response.ok) { - console.error(res.error?.message, res.error?.details); - throw new Error("Failed to purchase nodes"); - } - - return res.data; -} - -async function down(props: { - type: string; -}) { - const client = await apiClient(); - - // check if there's already a procurement like this - const procurements = await client.GET("/v0/procurements"); - if (!procurements.response.ok) { - console.error(procurements.error?.message, procurements.error?.details); - throw new Error("Failed to list procurements"); + if (!confirmed) { + logAndQuit("Order cancelled"); } + } - const procurement = procurements.data?.data.find((p: any) => p.instance_group === props.type); + const balance = await getBalance(); - if (!procurement) { - console.error(`No procurement found for ${props.type}`); - return; - } - - const res = await client.PUT("/v0/procurements/{id}", { + if (balance.available.centicents < totalPriceInCenticents) { + console.log( + `You can't afford this. Available balance: $${(balance.available.centicents / 1000000).toFixed(2)}, Minimum price: $${(totalPriceInCenticents / 1000000).toFixed(2)}\n`, + ); + const cmd = getSuggestedCommandWhenBalanceLow({ + durationHours, + pricePerNodeHourInCenticents, + n, + totalPriceInCenticents, + balance: balance.available.whole, + }); + console.log(cmd); + return; + } + + // check if there's already a procurement like this + const procurements = await client.GET("/v0/procurements"); + if (!procurements.response.ok) { + console.error(procurements.error?.message, procurements.error?.details); + throw new Error("Failed to list procurements"); + } + + for (const procurement of procurements.data?.data ?? []) { + // Currently instance groups are the same name as the instance type + // in the future they might be different. + if (procurement.instance_group === props.type) { + const res = await client.PUT("/v0/procurements/{id}", { params: { - path: { - id: procurement.id, - }, + path: { + id: procurement.id, + }, }, body: { - quantity: 0, - block_duration_in_hours: 0, - }, - }); + quantity: n, - if (!res.response.ok) { - console.error(res.error?.message, res.error?.details); - throw new Error("Failed to turn off nodes"); + // we only update the duration & price if it's set + min_duration_in_hours: props.duration ? durationHours : undefined, + max_price_per_node_hour: props.price + ? pricePerNodeHourInCenticents + : undefined, + }, + }); + return res.data; } + } + + const res = await client.POST("/v0/procurements", { + body: { + instance_type: type, + quantity: n, + max_price_per_node_hour: pricePerNodeHourInCenticents, + min_duration_in_hours: Math.max(durationHours, 1), + }, + }); + + if (!res.response.ok) { + console.error(res.error?.message, res.error?.details); + throw new Error("Failed to purchase nodes"); + } + + return res.data; +} - return res.data; -} \ No newline at end of file +async function down(props: { + type: string; +}) { + const client = await apiClient(); + + // check if there's already a procurement like this + const procurements = await client.GET("/v0/procurements"); + if (!procurements.response.ok) { + console.error(procurements.error?.message, procurements.error?.details); + throw new Error("Failed to list procurements"); + } + + const procurement = procurements.data?.data.find( + (p: any) => p.instance_group === props.type, + ); + + if (!procurement) { + console.error(`No procurement found for ${props.type}`); + return; + } + + const res = await client.PUT("/v0/procurements/{id}", { + params: { + path: { + id: procurement.id, + }, + }, + body: { + quantity: 0, + block_duration_in_hours: 0, + }, + }); + + if (!res.response.ok) { + console.error(res.error?.message, res.error?.details); + throw new Error("Failed to turn off nodes"); + } + + return res.data; +} diff --git a/src/schema.ts b/src/schema.ts index d37c444..1fd22fc 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -4,2431 +4,2476 @@ */ export interface paths { - "/v0/prices": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getV0Prices"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + "/v0/prices": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/v0/quote": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getV0Quote"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + get: operations["getV0Prices"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/quote": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/v0/orders": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getV0Orders"]; - put?: never; - post: operations["postV0Orders"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + get: operations["getV0Quote"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/orders": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/v0/orders/{id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getV0OrdersById"]; - put?: never; - post?: never; - delete: operations["deleteV0OrdersById"]; - options?: never; - head?: never; - patch?: never; - trace?: never; + get: operations["getV0Orders"]; + put?: never; + post: operations["postV0Orders"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/orders/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/v0/instances": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getV0Instances"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + get: operations["getV0OrdersById"]; + put?: never; + post?: never; + delete: operations["deleteV0OrdersById"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/instances": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/v0/instances/{id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getV0InstancesById"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + get: operations["getV0Instances"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/instances/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/v0/credentials": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getV0Credentials"]; - put?: never; - post: operations["postV0Credentials"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + get: operations["getV0InstancesById"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/credentials": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/v0/contracts": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getV0Contracts"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + get: operations["getV0Credentials"]; + put?: never; + post: operations["postV0Credentials"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/contracts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/v0/contracts/{id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getV0ContractsById"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + get: operations["getV0Contracts"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/contracts/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/v0/balance": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getV0Balance"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + get: operations["getV0ContractsById"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/balance": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/v0/procurements": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getV0Procurements"]; - put?: never; - post: operations["postV0Procurements"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + get: operations["getV0Balance"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/procurements": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/v0/procurements/{id}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["getV0ProcurementsById"]; - put: operations["putV0ProcurementsById"]; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + get: operations["getV0Procurements"]; + put?: never; + post: operations["postV0Procurements"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v0/procurements/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; + get: operations["getV0ProcurementsById"]; + put: operations["putV0ProcurementsById"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record; export interface components { - schemas: never; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + schemas: never; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; } export type $defs = Record; export interface operations { - getV0Prices: { - parameters: { - query: { - /** @description The instance type. */ - instance_type: string; - /** @description The minimum quantity of nodes filled blocks included in the price calculation contain. */ - min_quantity?: number; - /** @description The maximum quantity of nodes filled blocks included in the price calculation contain. */ - max_quantity?: number; - /** @description The minimum duration, in seconds, of filled blocks. */ - min_duration?: number; - /** @description The maximum duration, in seconds, of filled blocks. */ - max_duration?: number; - /** @description The number of days to go back, starting from today. If you provide 0, you will only see prices for today. If you provide 1, you will see prices over all of yesterday, and today. */ - since_n_days_ago?: number; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - data: { - /** @constant */ - object: "price-history-item"; - gpu_hour?: { - /** @description The minimum price per GPU hour for the period (in centicents, 1/100th of a cent). */ - min: number; - /** @description The maximum price per GPU hour for the period (in centicents, 1/100th of a cent). */ - max: number; - /** @description The average price per GPU hour for the period (in centicents, 1/100th of a cent). */ - avg: number; - }; - /** @description ISO 8601 datetime marking the start of the period. */ - period_start: string; - /** @description ISO 8601 datetime marking the end of the period. */ - period_end: string; - /** @description Whether there was no price data for this period. */ - no_data: boolean; - }[]; - /** @constant */ - object: "list"; - }; - "multipart/form-data": { - data: { - /** @constant */ - object: "price-history-item"; - gpu_hour?: { - /** @description The minimum price per GPU hour for the period (in centicents, 1/100th of a cent). */ - min: number; - /** @description The maximum price per GPU hour for the period (in centicents, 1/100th of a cent). */ - max: number; - /** @description The average price per GPU hour for the period (in centicents, 1/100th of a cent). */ - avg: number; - }; - /** @description ISO 8601 datetime marking the start of the period. */ - period_start: string; - /** @description ISO 8601 datetime marking the end of the period. */ - period_end: string; - /** @description Whether there was no price data for this period. */ - no_data: boolean; - }[]; - /** @constant */ - object: "list"; - }; - "text/plain": { - data: { - /** @constant */ - object: "price-history-item"; - gpu_hour?: { - /** @description The minimum price per GPU hour for the period (in centicents, 1/100th of a cent). */ - min: number; - /** @description The maximum price per GPU hour for the period (in centicents, 1/100th of a cent). */ - max: number; - /** @description The average price per GPU hour for the period (in centicents, 1/100th of a cent). */ - avg: number; - }; - /** @description ISO 8601 datetime marking the start of the period. */ - period_start: string; - /** @description ISO 8601 datetime marking the end of the period. */ - period_end: string; - /** @description Whether there was no price data for this period. */ - no_data: boolean; - }[]; - /** @constant */ - object: "list"; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + getV0Prices: { + parameters: { + query: { + /** @description The instance type. */ + instance_type: string; + /** @description The minimum quantity of nodes filled blocks included in the price calculation contain. */ + min_quantity?: number; + /** @description The maximum quantity of nodes filled blocks included in the price calculation contain. */ + max_quantity?: number; + /** @description The minimum duration, in seconds, of filled blocks. */ + min_duration?: number; + /** @description The maximum duration, in seconds, of filled blocks. */ + max_duration?: number; + /** @description The number of days to go back, starting from today. If you provide 0, you will only see prices for today. If you provide 1, you will see prices over all of yesterday, and today. */ + since_n_days_ago?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: { + /** @constant */ + object: "price-history-item"; + gpu_hour?: { + /** @description The minimum price per GPU hour for the period (in centicents, 1/100th of a cent). */ + min: number; + /** @description The maximum price per GPU hour for the period (in centicents, 1/100th of a cent). */ + max: number; + /** @description The average price per GPU hour for the period (in centicents, 1/100th of a cent). */ + avg: number; + }; + /** @description ISO 8601 datetime marking the start of the period. */ + period_start: string; + /** @description ISO 8601 datetime marking the end of the period. */ + period_end: string; + /** @description Whether there was no price data for this period. */ + no_data: boolean; + }[]; + /** @constant */ + object: "list"; + }; + "multipart/form-data": { + data: { + /** @constant */ + object: "price-history-item"; + gpu_hour?: { + /** @description The minimum price per GPU hour for the period (in centicents, 1/100th of a cent). */ + min: number; + /** @description The maximum price per GPU hour for the period (in centicents, 1/100th of a cent). */ + max: number; + /** @description The average price per GPU hour for the period (in centicents, 1/100th of a cent). */ + avg: number; + }; + /** @description ISO 8601 datetime marking the start of the period. */ + period_start: string; + /** @description ISO 8601 datetime marking the end of the period. */ + period_end: string; + /** @description Whether there was no price data for this period. */ + no_data: boolean; + }[]; + /** @constant */ + object: "list"; + }; + "text/plain": { + data: { + /** @constant */ + object: "price-history-item"; + gpu_hour?: { + /** @description The minimum price per GPU hour for the period (in centicents, 1/100th of a cent). */ + min: number; + /** @description The maximum price per GPU hour for the period (in centicents, 1/100th of a cent). */ + max: number; + /** @description The average price per GPU hour for the period (in centicents, 1/100th of a cent). */ + avg: number; + }; + /** @description ISO 8601 datetime marking the start of the period. */ + period_start: string; + /** @description ISO 8601 datetime marking the end of the period. */ + period_end: string; + /** @description Whether there was no price data for this period. */ + no_data: boolean; + }[]; + /** @constant */ + object: "list"; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; }; - getV0Quote: { - parameters: { - query: { - side: "buy" | "sell"; - /** @description Inclusive lower bound for the start time, as an ISO 8601 string. The query will consider all valid start times at or after this time. The difference between this and `max_start_time` can be at most 24 hours. */ - min_start_date: string; - /** @description Inclusive upper bound for the start time, as an ISO 8601 string. The query will consider all valid start times on or before this time. The difference between this and `min_start_time` can be at most 24 hours. */ - max_start_date: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The number of nodes. */ - quantity: number; - /** @description The instance type. */ - instance_type?: string; - contract_id?: string; - }; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "quote"; - /** @constant */ - side: "buy"; - quote: { - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The instance type. */ - instance_type: string; - } | null; - } | { - /** @constant */ - object: "quote"; - /** @constant */ - side: "sell"; - quote: { - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - contract_id: string; - } | null; - }; - "multipart/form-data": { - /** @constant */ - object: "quote"; - /** @constant */ - side: "buy"; - quote: { - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The instance type. */ - instance_type: string; - } | null; - } | { - /** @constant */ - object: "quote"; - /** @constant */ - side: "sell"; - quote: { - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - contract_id: string; - } | null; - }; - "text/plain": { - /** @constant */ - object: "quote"; - /** @constant */ - side: "buy"; - quote: { - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - /** @description The instance type. */ - instance_type: string; - } | null; - } | { - /** @constant */ - object: "quote"; - /** @constant */ - side: "sell"; - quote: { - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ - duration: number; - contract_id: string; - } | null; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + }; + getV0Quote: { + parameters: { + query: { + side: "buy" | "sell"; + /** @description Inclusive lower bound for the start time, as an ISO 8601 string. The query will consider all valid start times at or after this time. The difference between this and `max_start_time` can be at most 24 hours. */ + min_start_date: string; + /** @description Inclusive upper bound for the start time, as an ISO 8601 string. The query will consider all valid start times on or before this time. The difference between this and `min_start_time` can be at most 24 hours. */ + max_start_date: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The instance type. */ + instance_type?: string; + contract_id?: string; + }; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": + | { + /** @constant */ + object: "quote"; + /** @constant */ + side: "buy"; + quote: { + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The instance type. */ + instance_type: string; + } | null; + } + | { + /** @constant */ + object: "quote"; + /** @constant */ + side: "sell"; + quote: { + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + contract_id: string; + } | null; + }; + "multipart/form-data": + | { + /** @constant */ + object: "quote"; + /** @constant */ + side: "buy"; + quote: { + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The instance type. */ + instance_type: string; + } | null; + } + | { + /** @constant */ + object: "quote"; + /** @constant */ + side: "sell"; + quote: { + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + contract_id: string; + } | null; + }; + "text/plain": + | { + /** @constant */ + object: "quote"; + /** @constant */ + side: "buy"; + quote: { + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + /** @description The instance type. */ + instance_type: string; + } | null; + } + | { + /** @constant */ + object: "quote"; + /** @constant */ + side: "sell"; + quote: { + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The duration, in seconds. Duration will be rounded such that the contract ends on the hour. For example if `start_time` is 17:10 and you put in 30m, the duration will be rounded up to 50m. Similarly, if `start_time` is 18:00 and you put 50m, the duration will be rounded up to 1h. */ + duration: number; + contract_id: string; + } | null; + }; }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; }; - getV0Orders: { - parameters: { - query?: { - instance_type?: string; - limit?: string; - offset?: string; - min_start_date?: string; - max_start_date?: string; - min_duration?: string; - max_duration?: string; - min_quantity?: string; - max_quantity?: string; - side?: string; - include_public?: boolean; - }; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; + }; + getV0Orders: { + parameters: { + query?: { + instance_type?: string; + limit?: string; + offset?: string; + min_start_date?: string; + max_start_date?: string; + min_duration?: string; + max_duration?: string; + min_quantity?: string; + max_quantity?: string; + side?: string; + include_public?: boolean; + }; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; }; + content?: never; + }; }; - postV0Orders: { - parameters: { - query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** @constant */ - side: "buy"; - /** @description The instance type. */ - instance_type: string; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ - end_at: string; - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags?: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - colocate_with?: string[]; - } | { - /** @constant */ - side: "sell"; - contract_id: string; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ - end_at: string; - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags?: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - }; - "multipart/form-data": { - /** @constant */ - side: "buy"; - /** @description The instance type. */ - instance_type: string; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ - end_at: string; - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags?: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - colocate_with?: string[]; - } | { - /** @constant */ - side: "sell"; - contract_id: string; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ - end_at: string; - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags?: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - }; - "text/plain": { - /** @constant */ - side: "buy"; - /** @description The instance type. */ - instance_type: string; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ - end_at: string; - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags?: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - colocate_with?: string[]; - } | { - /** @constant */ - side: "sell"; - contract_id: string; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ - end_at: string; - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags?: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - }; - }; + }; + postV0Orders: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": + | { + /** @constant */ + side: "buy"; + /** @description The instance type. */ + instance_type: string; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ + end_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags?: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + colocate_with?: string[]; + } + | { + /** @constant */ + side: "sell"; + contract_id: string; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ + end_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags?: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + }; + "multipart/form-data": + | { + /** @constant */ + side: "buy"; + /** @description The instance type. */ + instance_type: string; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ + end_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags?: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + colocate_with?: string[]; + } + | { + /** @constant */ + side: "sell"; + contract_id: string; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ + end_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags?: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + }; + "text/plain": + | { + /** @constant */ + side: "buy"; + /** @description The instance type. */ + instance_type: string; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ + end_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags?: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + colocate_with?: string[]; + } + | { + /** @constant */ + side: "sell"; + contract_id: string; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ + end_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags?: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; }; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "order"; - id: string; - /** @constant */ - status: "pending"; - }; - "multipart/form-data": { - /** @constant */ - object: "order"; - id: string; - /** @constant */ - status: "pending"; - }; - "text/plain": { - /** @constant */ - object: "order"; - id: string; - /** @constant */ - status: "pending"; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + content: { + "application/json": { + /** @constant */ + object: "order"; + id: string; + /** @constant */ + status: "pending"; + }; + "multipart/form-data": { + /** @constant */ + object: "order"; + id: string; + /** @constant */ + status: "pending"; + }; + "text/plain": { + /** @constant */ + object: "order"; + id: string; + /** @constant */ + status: "pending"; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; }; - getV0OrdersById: { - parameters: { - query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path: { - id: string; - }; - cookie?: never; + }; + getV0OrdersById: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "order"; - id: string; - side: "buy" | "sell"; - status: "pending" | "rejected" | "open" | "cancelled" | "filled" | "expired"; - /** @description The instance type. */ - instance_type: string; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ - end_at: string; - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - executed: boolean; - cancelled: boolean; - executed_at: string | null; - execution_price: number | null; - cancelled_at: string | null; - colocate_with?: string[]; - created_at: string; - }; - "multipart/form-data": { - /** @constant */ - object: "order"; - id: string; - side: "buy" | "sell"; - status: "pending" | "rejected" | "open" | "cancelled" | "filled" | "expired"; - /** @description The instance type. */ - instance_type: string; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ - end_at: string; - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - executed: boolean; - cancelled: boolean; - executed_at: string | null; - execution_price: number | null; - cancelled_at: string | null; - colocate_with?: string[]; - created_at: string; - }; - "text/plain": { - /** @constant */ - object: "order"; - id: string; - side: "buy" | "sell"; - status: "pending" | "rejected" | "open" | "cancelled" | "filled" | "expired"; - /** @description The instance type. */ - instance_type: string; - /** @description The number of nodes. */ - quantity: number; - /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ - start_at: string; - /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ - end_at: string; - /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ - price: number; - flags: { - /** @description If true, this will be a market order. */ - market?: boolean; - /** @description If true, this is a post-only order. */ - post_only?: boolean; - /** @description If true, this is an immediate-or-cancel order. */ - ioc?: boolean; - }; - executed: boolean; - cancelled: boolean; - executed_at: string | null; - execution_price: number | null; - cancelled_at: string | null; - colocate_with?: string[]; - created_at: string; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + content: { + "application/json": { + /** @constant */ + object: "order"; + id: string; + side: "buy" | "sell"; + status: + | "pending" + | "rejected" + | "open" + | "cancelled" + | "filled" + | "expired"; + /** @description The instance type. */ + instance_type: string; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ + end_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + executed: boolean; + cancelled: boolean; + executed_at: string | null; + execution_price: number | null; + cancelled_at: string | null; + colocate_with?: string[]; + created_at: string; + }; + "multipart/form-data": { + /** @constant */ + object: "order"; + id: string; + side: "buy" | "sell"; + status: + | "pending" + | "rejected" + | "open" + | "cancelled" + | "filled" + | "expired"; + /** @description The instance type. */ + instance_type: string; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ + end_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + executed: boolean; + cancelled: boolean; + executed_at: string | null; + execution_price: number | null; + cancelled_at: string | null; + colocate_with?: string[]; + created_at: string; + }; + "text/plain": { + /** @constant */ + object: "order"; + id: string; + side: "buy" | "sell"; + status: + | "pending" + | "rejected" + | "open" + | "cancelled" + | "filled" + | "expired"; + /** @description The instance type. */ + instance_type: string; + /** @description The number of nodes. */ + quantity: number; + /** @description The start time, as an ISO 8601 string. Start times must be either "right now" or on the hour. Order start times must be in the future, and can be either the next minute from now or on the hour. For example, if it's 16:00, valid start times include 16:01, 17:00, and 18:00, but not 16:30. Dates are always rounded up to the nearest minute. */ + start_at: string; + /** @description The end time, as an ISO 8601 string. End times must be on the hour, i.e. 16:00, 17:00, 18:00, etc. 17:30, 17:01, etc are not valid end times. Dates are always rounded up to the nearest minute. */ + end_at: string; + /** @description Price in Centicents (1/100th of a cent, One Centicent = $0.0001) */ + price: number; + flags: { + /** @description If true, this will be a market order. */ + market?: boolean; + /** @description If true, this is a post-only order. */ + post_only?: boolean; + /** @description If true, this is an immediate-or-cancel order. */ + ioc?: boolean; + }; + executed: boolean; + cancelled: boolean; + executed_at: string | null; + execution_price: number | null; + cancelled_at: string | null; + colocate_with?: string[]; + created_at: string; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; }; - deleteV0OrdersById: { - parameters: { - query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path: { - id: string; - }; - cookie?: never; + }; + deleteV0OrdersById: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "pending"; - }; - "multipart/form-data": { - /** @constant */ - object: "pending"; - }; - "text/plain": { - /** @constant */ - object: "pending"; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + content: { + "application/json": { + /** @constant */ + object: "pending"; + }; + "multipart/form-data": { + /** @constant */ + object: "pending"; + }; + "text/plain": { + /** @constant */ + object: "pending"; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; }; - getV0Instances: { - parameters: { - query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - data: { - /** @constant */ - object: "instance"; - id: string; - name: string; - type: string; - ip: string; - status: "healthy" | "starting" | "unreachable" | "unhealthy"; - }[]; - /** @constant */ - object: "list"; - }; - "multipart/form-data": { - data: { - /** @constant */ - object: "instance"; - id: string; - name: string; - type: string; - ip: string; - status: "healthy" | "starting" | "unreachable" | "unhealthy"; - }[]; - /** @constant */ - object: "list"; - }; - "text/plain": { - data: { - /** @constant */ - object: "instance"; - id: string; - name: string; - type: string; - ip: string; - status: "healthy" | "starting" | "unreachable" | "unhealthy"; - }[]; - /** @constant */ - object: "list"; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + }; + getV0Instances: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: { + /** @constant */ + object: "instance"; + id: string; + name: string; + type: string; + ip: string; + status: "healthy" | "starting" | "unreachable" | "unhealthy"; + }[]; + /** @constant */ + object: "list"; + }; + "multipart/form-data": { + data: { + /** @constant */ + object: "instance"; + id: string; + name: string; + type: string; + ip: string; + status: "healthy" | "starting" | "unreachable" | "unhealthy"; + }[]; + /** @constant */ + object: "list"; + }; + "text/plain": { + data: { + /** @constant */ + object: "instance"; + id: string; + name: string; + type: string; + ip: string; + status: "healthy" | "starting" | "unreachable" | "unhealthy"; + }[]; + /** @constant */ + object: "list"; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; }; + }; }; - getV0InstancesById: { - parameters: { - query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path: { - id: string; - }; - cookie?: never; + }; + getV0InstancesById: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "instance"; - id: string; - name: string; - type: string; - ip: string; - status: "healthy" | "starting" | "unreachable" | "unhealthy"; - }; - "multipart/form-data": { - /** @constant */ - object: "instance"; - id: string; - name: string; - type: string; - ip: string; - status: "healthy" | "starting" | "unreachable" | "unhealthy"; - }; - "text/plain": { - /** @constant */ - object: "instance"; - id: string; - name: string; - type: string; - ip: string; - status: "healthy" | "starting" | "unreachable" | "unhealthy"; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + content: { + "application/json": { + /** @constant */ + object: "instance"; + id: string; + name: string; + type: string; + ip: string; + status: "healthy" | "starting" | "unreachable" | "unhealthy"; + }; + "multipart/form-data": { + /** @constant */ + object: "instance"; + id: string; + name: string; + type: string; + ip: string; + status: "healthy" | "starting" | "unreachable" | "unhealthy"; + }; + "text/plain": { + /** @constant */ + object: "instance"; + id: string; + name: string; + type: string; + ip: string; + status: "healthy" | "starting" | "unreachable" | "unhealthy"; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; }; - getV0Credentials: { - parameters: { - query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - data: { - /** @constant */ - object: "ssh_credential"; - id: string; - pubkey: string; - username: string; - }[]; - /** @constant */ - object: "list"; - }; - "multipart/form-data": { - data: { - /** @constant */ - object: "ssh_credential"; - id: string; - pubkey: string; - username: string; - }[]; - /** @constant */ - object: "list"; - }; - "text/plain": { - data: { - /** @constant */ - object: "ssh_credential"; - id: string; - pubkey: string; - username: string; - }[]; - /** @constant */ - object: "list"; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + }; + getV0Credentials: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: { + /** @constant */ + object: "ssh_credential"; + id: string; + pubkey: string; + username: string; + }[]; + /** @constant */ + object: "list"; + }; + "multipart/form-data": { + data: { + /** @constant */ + object: "ssh_credential"; + id: string; + pubkey: string; + username: string; + }[]; + /** @constant */ + object: "list"; + }; + "text/plain": { + data: { + /** @constant */ + object: "ssh_credential"; + id: string; + pubkey: string; + username: string; + }[]; + /** @constant */ + object: "list"; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; }; - postV0Credentials: { - parameters: { - query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - pubkey: string; - username: string; - }; - "multipart/form-data": { - pubkey: string; - username: string; - }; - "text/plain": { - pubkey: string; - username: string; - }; - }; + }; + postV0Credentials: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + pubkey: string; + username: string; }; - responses: { - /** @description SSH credentials and associated Linux user to be set up on the VM when it spins up. */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "ssh_credential"; - id: string; - pubkey: string; - username: string; - }; - "multipart/form-data": { - /** @constant */ - object: "ssh_credential"; - id: string; - pubkey: string; - username: string; - }; - "text/plain": { - /** @constant */ - object: "ssh_credential"; - id: string; - pubkey: string; - username: string; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + "multipart/form-data": { + pubkey: string; + username: string; + }; + "text/plain": { + pubkey: string; + username: string; }; + }; }; - getV0Contracts: { - parameters: { - query?: { - active_within_interval_start?: string; - active_within_interval_end?: string; - instance_type?: string; - }; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - data: ({ - /** @constant */ - object: "contract"; - /** @constant */ - status: "active"; - id: string; - /** Format: date-time */ - created_at: string; - /** @description The instance type. */ - instance_type: string; - /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ - shape: { - intervals: string[]; - quantities: number[]; - }; - colocate_with?: string[]; - cluster_id?: string; - } | { - /** @constant */ - object: "contract"; - /** @constant */ - status: "pending"; - id: string; - })[]; - /** @constant */ - object: "list"; - }; - "multipart/form-data": { - data: ({ - /** @constant */ - object: "contract"; - /** @constant */ - status: "active"; - id: string; - /** Format: date-time */ - created_at: string; - /** @description The instance type. */ - instance_type: string; - /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ - shape: { - intervals: string[]; - quantities: number[]; - }; - colocate_with?: string[]; - cluster_id?: string; - } | { - /** @constant */ - object: "contract"; - /** @constant */ - status: "pending"; - id: string; - })[]; - /** @constant */ - object: "list"; - }; - "text/plain": { - data: ({ - /** @constant */ - object: "contract"; - /** @constant */ - status: "active"; - id: string; - /** Format: date-time */ - created_at: string; - /** @description The instance type. */ - instance_type: string; - /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ - shape: { - intervals: string[]; - quantities: number[]; - }; - colocate_with?: string[]; - cluster_id?: string; - } | { - /** @constant */ - object: "contract"; - /** @constant */ - status: "pending"; - id: string; - })[]; - /** @constant */ - object: "list"; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + responses: { + /** @description SSH credentials and associated Linux user to be set up on the VM when it spins up. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "ssh_credential"; + id: string; + pubkey: string; + username: string; + }; + "multipart/form-data": { + /** @constant */ + object: "ssh_credential"; + id: string; + pubkey: string; + username: string; + }; + "text/plain": { + /** @constant */ + object: "ssh_credential"; + id: string; + pubkey: string; + username: string; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; }; - getV0ContractsById: { - parameters: { - query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path: { + }; + getV0Contracts: { + parameters: { + query?: { + active_within_interval_start?: string; + active_within_interval_end?: string; + instance_type?: string; + }; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: ( + | { + /** @constant */ + object: "contract"; + /** @constant */ + status: "active"; + id: string; + /** Format: date-time */ + created_at: string; + /** @description The instance type. */ + instance_type: string; + /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ + shape: { + intervals: string[]; + quantities: number[]; + }; + colocate_with?: string[]; + cluster_id?: string; + } + | { + /** @constant */ + object: "contract"; + /** @constant */ + status: "pending"; + id: string; + } + )[]; + /** @constant */ + object: "list"; + }; + "multipart/form-data": { + data: ( + | { + /** @constant */ + object: "contract"; + /** @constant */ + status: "active"; + id: string; + /** Format: date-time */ + created_at: string; + /** @description The instance type. */ + instance_type: string; + /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ + shape: { + intervals: string[]; + quantities: number[]; + }; + colocate_with?: string[]; + cluster_id?: string; + } + | { + /** @constant */ + object: "contract"; + /** @constant */ + status: "pending"; + id: string; + } + )[]; + /** @constant */ + object: "list"; + }; + "text/plain": { + data: ( + | { + /** @constant */ + object: "contract"; + /** @constant */ + status: "active"; + id: string; + /** Format: date-time */ + created_at: string; + /** @description The instance type. */ + instance_type: string; + /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ + shape: { + intervals: string[]; + quantities: number[]; + }; + colocate_with?: string[]; + cluster_id?: string; + } + | { + /** @constant */ + object: "contract"; + /** @constant */ + status: "pending"; + id: string; + } + )[]; + /** @constant */ + object: "list"; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; + }; + }; + getV0ContractsById: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": + | { + /** @constant */ + object: "contract"; + /** @constant */ + status: "active"; id: string; - }; - cookie?: never; + /** Format: date-time */ + created_at: string; + /** @description The instance type. */ + instance_type: string; + /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ + shape: { + intervals: string[]; + quantities: number[]; + }; + colocate_with?: string[]; + cluster_id?: string; + } + | { + /** @constant */ + object: "contract"; + /** @constant */ + status: "pending"; + id: string; + }; + "multipart/form-data": + | { + /** @constant */ + object: "contract"; + /** @constant */ + status: "active"; + id: string; + /** Format: date-time */ + created_at: string; + /** @description The instance type. */ + instance_type: string; + /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ + shape: { + intervals: string[]; + quantities: number[]; + }; + colocate_with?: string[]; + cluster_id?: string; + } + | { + /** @constant */ + object: "contract"; + /** @constant */ + status: "pending"; + id: string; + }; + "text/plain": + | { + /** @constant */ + object: "contract"; + /** @constant */ + status: "active"; + id: string; + /** Format: date-time */ + created_at: string; + /** @description The instance type. */ + instance_type: string; + /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ + shape: { + intervals: string[]; + quantities: number[]; + }; + colocate_with?: string[]; + cluster_id?: string; + } + | { + /** @constant */ + object: "contract"; + /** @constant */ + status: "pending"; + id: string; + }; }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "contract"; - /** @constant */ - status: "active"; - id: string; - /** Format: date-time */ - created_at: string; - /** @description The instance type. */ - instance_type: string; - /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ - shape: { - intervals: string[]; - quantities: number[]; - }; - colocate_with?: string[]; - cluster_id?: string; - } | { - /** @constant */ - object: "contract"; - /** @constant */ - status: "pending"; - id: string; - }; - "multipart/form-data": { - /** @constant */ - object: "contract"; - /** @constant */ - status: "active"; - id: string; - /** Format: date-time */ - created_at: string; - /** @description The instance type. */ - instance_type: string; - /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ - shape: { - intervals: string[]; - quantities: number[]; - }; - colocate_with?: string[]; - cluster_id?: string; - } | { - /** @constant */ - object: "contract"; - /** @constant */ - status: "pending"; - id: string; - }; - "text/plain": { - /** @constant */ - object: "contract"; - /** @constant */ - status: "active"; - id: string; - /** Format: date-time */ - created_at: string; - /** @description The instance type. */ - instance_type: string; - /** @description A shape that describes the distribution of the contract's size over time. Must end with a quantity of 0. */ - shape: { - intervals: string[]; - quantities: number[]; - }; - colocate_with?: string[]; - cluster_id?: string; - } | { - /** @constant */ - object: "contract"; - /** @constant */ - status: "pending"; - id: string; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + }; + 401: { + headers: { + [name: string]: unknown; }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; }; - getV0Balance: { - parameters: { - query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "balance"; - available: { - /** @description Funds available to spend or withdraw. */ - amount: number; - /** @constant */ - currency: "usd"; - }; - reserved: { - /** @description Funds held in reserve for pending withdrawals & open buy orders. */ - amount: number; - /** @constant */ - currency: "usd"; - }; - }; - "multipart/form-data": { - /** @constant */ - object: "balance"; - available: { - /** @description Funds available to spend or withdraw. */ - amount: number; - /** @constant */ - currency: "usd"; - }; - reserved: { - /** @description Funds held in reserve for pending withdrawals & open buy orders. */ - amount: number; - /** @constant */ - currency: "usd"; - }; - }; - "text/plain": { - /** @constant */ - object: "balance"; - available: { - /** @description Funds available to spend or withdraw. */ - amount: number; - /** @constant */ - currency: "usd"; - }; - reserved: { - /** @description Funds held in reserve for pending withdrawals & open buy orders. */ - amount: number; - /** @constant */ - currency: "usd"; - }; - }; - }; - }; - 401: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "not_authenticated"; - message: string; - details?: Record; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + }; + getV0Balance: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "balance"; + available: { + /** @description Funds available to spend or withdraw. */ + amount: number; + /** @constant */ + currency: "usd"; + }; + reserved: { + /** @description Funds held in reserve for pending withdrawals & open buy orders. */ + amount: number; + /** @constant */ + currency: "usd"; + }; + }; + "multipart/form-data": { + /** @constant */ + object: "balance"; + available: { + /** @description Funds available to spend or withdraw. */ + amount: number; + /** @constant */ + currency: "usd"; + }; + reserved: { + /** @description Funds held in reserve for pending withdrawals & open buy orders. */ + amount: number; + /** @constant */ + currency: "usd"; + }; + }; + "text/plain": { + /** @constant */ + object: "balance"; + available: { + /** @description Funds available to spend or withdraw. */ + amount: number; + /** @constant */ + currency: "usd"; + }; + reserved: { + /** @description Funds held in reserve for pending withdrawals & open buy orders. */ + amount: number; + /** @constant */ + currency: "usd"; + }; + }; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "not_authenticated"; + message: string; + details?: Record; + }; }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; }; - getV0Procurements: { - parameters: { - query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - data: { - id: string; - /** @description The instance group of the procurement */ - instance_group: string; - /** @description The quantity of the procurement */ - quantity: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours: number; - /** @description The instance type. */ - instance_type: string; - }[]; - /** @constant */ - object: "list"; - }; - "multipart/form-data": { - data: { - id: string; - /** @description The instance group of the procurement */ - instance_group: string; - /** @description The quantity of the procurement */ - quantity: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours: number; - /** @description The instance type. */ - instance_type: string; - }[]; - /** @constant */ - object: "list"; - }; - "text/plain": { - data: { - id: string; - /** @description The instance group of the procurement */ - instance_group: string; - /** @description The quantity of the procurement */ - quantity: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours: number; - /** @description The instance type. */ - instance_type: string; - }[]; - /** @constant */ - object: "list"; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + }; + getV0Procurements: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + data: { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }[]; + /** @constant */ + object: "list"; + }; + "multipart/form-data": { + data: { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }[]; + /** @constant */ + object: "list"; + }; + "text/plain": { + data: { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }[]; + /** @constant */ + object: "list"; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; }; - postV0Procurements: { - parameters: { - query?: never; - header?: { - /** @description Generate a bearer token with `$ sf tokens create`. */ - authorization?: string; - }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** @description The instance type. */ - instance_type: string; - quantity: number; - max_price_per_node_hour: number; - block_duration_in_hours: number; - }; - "multipart/form-data": { - /** @description The instance type. */ - instance_type: string; - quantity: number; - max_price_per_node_hour: number; - block_duration_in_hours: number; - }; - "text/plain": { - /** @description The instance type. */ - instance_type: string; - quantity: number; - max_price_per_node_hour: number; - block_duration_in_hours: number; - }; - }; + }; + postV0Procurements: { + parameters: { + query?: never; + header?: { + /** @description Generate a bearer token with `$ sf tokens create`. */ + authorization?: string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description The instance type. */ + instance_type: string; + quantity: number; + max_price_per_node_hour: number; + block_duration_in_hours: number; }; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - id: string; - /** @description The instance group of the procurement */ - instance_group: string; - /** @description The quantity of the procurement */ - quantity: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours: number; - /** @description The instance type. */ - instance_type: string; - }; - "multipart/form-data": { - id: string; - /** @description The instance group of the procurement */ - instance_group: string; - /** @description The quantity of the procurement */ - quantity: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours: number; - /** @description The instance type. */ - instance_type: string; - }; - "text/plain": { - id: string; - /** @description The instance group of the procurement */ - instance_group: string; - /** @description The quantity of the procurement */ - quantity: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours: number; - /** @description The instance type. */ - instance_type: string; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + "multipart/form-data": { + /** @description The instance type. */ + instance_type: string; + quantity: number; + max_price_per_node_hour: number; + block_duration_in_hours: number; + }; + "text/plain": { + /** @description The instance type. */ + instance_type: string; + quantity: number; + max_price_per_node_hour: number; + block_duration_in_hours: number; }; + }; }; - getV0ProcurementsById: { - parameters: { - query?: never; - header?: never; - path: { - id: string; - }; - cookie?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - id: string; - /** @description The instance group of the procurement */ - instance_group: string; - /** @description The quantity of the procurement */ - quantity: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours: number; - /** @description The instance type. */ - instance_type: string; - }; - "multipart/form-data": { - id: string; - /** @description The instance group of the procurement */ - instance_group: string; - /** @description The quantity of the procurement */ - quantity: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours: number; - /** @description The instance type. */ - instance_type: string; - }; - "text/plain": { - id: string; - /** @description The instance group of the procurement */ - instance_group: string; - /** @description The quantity of the procurement */ - quantity: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours: number; - /** @description The instance type. */ - instance_type: string; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + content: { + "application/json": { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }; + "multipart/form-data": { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }; + "text/plain": { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; }; - putV0ProcurementsById: { - parameters: { - query?: never; - header?: never; - path: { - id: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - quantity?: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price?: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours?: number; - }; - "multipart/form-data": { - quantity?: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price?: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours?: number; - }; - "text/plain": { - quantity?: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price?: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours?: number; - }; - }; + }; + getV0ProcurementsById: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; }; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - id: string; - /** @description The instance group of the procurement */ - instance_group: string; - /** @description The quantity of the procurement */ - quantity: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours: number; - /** @description The instance type. */ - instance_type: string; - }; - "multipart/form-data": { - id: string; - /** @description The instance group of the procurement */ - instance_group: string; - /** @description The quantity of the procurement */ - quantity: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours: number; - /** @description The instance type. */ - instance_type: string; - }; - "text/plain": { - id: string; - /** @description The instance group of the procurement */ - instance_group: string; - /** @description The quantity of the procurement */ - quantity: number; - /** @description The TOTAL price (in centicents) to buy the duration */ - max_price: number; - /** @description The block duration of the procurement in hours */ - min_duration_in_hours: number; - /** @description The instance type. */ - instance_type: string; - }; - }; - }; - 500: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "multipart/form-data": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - "text/plain": { - /** @constant */ - object: "error"; - /** @constant */ - code: "internal_server"; - message: string; - details?: Record; - }; - }; - }; + content: { + "application/json": { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }; + "multipart/form-data": { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }; + "text/plain": { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + }; + }; + }; + }; + putV0ProcurementsById: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + quantity?: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price?: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours?: number; + }; + "multipart/form-data": { + quantity?: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price?: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours?: number; + }; + "text/plain": { + quantity?: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price?: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours?: number; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }; + "multipart/form-data": { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }; + "text/plain": { + id: string; + /** @description The instance group of the procurement */ + instance_group: string; + /** @description The quantity of the procurement */ + quantity: number; + /** @description The TOTAL price (in centicents) to buy the duration */ + max_price: number; + /** @description The block duration of the procurement in hours */ + min_duration_in_hours: number; + /** @description The instance type. */ + instance_type: string; + }; + }; + }; + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "multipart/form-data": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; + "text/plain": { + /** @constant */ + object: "error"; + /** @constant */ + code: "internal_server"; + message: string; + details?: Record; + }; }; + }; }; + }; } From d6505c091dd6d4ab158f214a48be228d188991a4 Mon Sep 17 00:00:00 2001 From: flaque Date: Thu, 12 Sep 2024 17:08:32 -0700 Subject: [PATCH 07/10] wip --- src/helpers/price.ts | 7 +++++++ src/lib/buy.ts | 45 +++++++++++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 src/helpers/price.ts diff --git a/src/helpers/price.ts b/src/helpers/price.ts new file mode 100644 index 0000000..374aae6 --- /dev/null +++ b/src/helpers/price.ts @@ -0,0 +1,7 @@ +export function pricePerGPUHourToTotalPrice(pricePerGPUHourInCenticents: number, durationSeconds: number, nodes: number, gpusPerNode: number) { + return (pricePerGPUHourInCenticents) * durationSeconds / 3600 * nodes * gpusPerNode; +} + +export function totalPriceToPricePerGPUHour(totalPriceInCenticents: number, durationSeconds: number, nodes: number, gpusPerNode: number) { + return totalPriceInCenticents / nodes / gpusPerNode / (durationSeconds / 3600); +} \ No newline at end of file diff --git a/src/lib/buy.ts b/src/lib/buy.ts index 6720cb5..aab31c4 100644 --- a/src/lib/buy.ts +++ b/src/lib/buy.ts @@ -22,13 +22,14 @@ import { } from "../helpers/units"; import type { Nullable } from "../types/empty"; import { formatDuration } from "./orders"; +import { pricePerGPUHourToTotalPrice, totalPriceToPricePerGPUHour } from "../helpers/price"; dayjs.extend(relativeTime); dayjs.extend(duration); interface SfBuyOptions { type: string; - nodes?: string; + accelerators?: string; duration: string; price: string; start?: string; @@ -36,14 +37,16 @@ interface SfBuyOptions { quote?: boolean; } +const GPUS_PER_NODE = 8; + export function registerBuy(program: Command) { program .command("buy") .description("Place a buy order") - .option("-t, --type ", "Specify the type of node", "h100i") - .option("-n, --nodes ", "Specify the number of nodes") + .requiredOption("-t, --type ", "Specify the type of node", "h100i") + .option("-n, --accelerators ", "Specify the number of GPUs", "8") .requiredOption("-d, --duration ", "Specify the duration", "1h") - .option("-p, --price ", "Specify the price") + .option("-p, --price ", "The price in dollars, per GPU hour") .option("-s, --start ", "Specify the start date") .option("-y, --yes", "Automatically confirm the order") .option("--quote", "Only provide a quote for the order") @@ -66,6 +69,15 @@ async function buyOrderAction(options: SfBuyOptions) { return logAndQuit(`Invalid duration: ${options.duration}`); } + // default to 1 node if not specified + const accelerators = options.accelerators ? Number(options.accelerators) : 1; + + if (accelerators % GPUS_PER_NODE !== 0) { + const exampleCommand = `sf buy -n ${GPUS_PER_NODE} -d "${options.duration}"`; + 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 quantity = Math.ceil(accelerators / GPUS_PER_NODE); + // parse price let priceCenticents: Nullable = null; if (options.price) { @@ -77,6 +89,12 @@ async function buyOrderAction(options: SfBuyOptions) { priceCenticents = priceParsed; } + // Convert the price to the total price of the contract + // (price per gpu hour * gpus per node * quantity * duration in hours) + if (priceCenticents) { + priceCenticents = pricePerGPUHourToTotalPrice(priceCenticents, durationSeconds, quantity, GPUS_PER_NODE); + } + const yesFlagOmitted = options.yes === undefined || options.yes === null; const confirmWithUser = yesFlagOmitted || !options.yes; @@ -86,9 +104,6 @@ async function buyOrderAction(options: SfBuyOptions) { return logAndQuit("Invalid start date"); } - // default to 1 node if not specified - const quantity = options.nodes ? Number(options.nodes) : 1; - if (options.quote) { const quote = await getQuote({ instanceType: options.type, @@ -102,8 +117,9 @@ async function buyOrderAction(options: SfBuyOptions) { } const priceLabelUsd = c.green(centicentsToDollarsFormatted(quote.price)); + const priceLabelPerGPUHour = c.green(centicentsToDollarsFormatted(totalPriceToPricePerGPUHour(quote.price, durationSeconds, quantity, GPUS_PER_NODE))); - console.log(`This order is projected to cost ${priceLabelUsd}`); + console.log(`This order is projected to cost ${priceLabelUsd} total or ${priceLabelPerGPUHour} per GPU hour`); } else { // quote if no price was provided if (!priceCenticents) { @@ -271,13 +287,20 @@ function confirmPlaceOrderMessage(options: BuyOptions) { timeDescription = `from ${startAtLabel} (${c.green(fromNowTime)}) until ${endsAtLabel}`; } - const topLine = `${totalNodesLabel} ${instanceTypeLabel} ${nodesLabel} for ${c.green(durationHumanReadable)} ${timeDescription}`; + const durationInSeconds = options.endsAt.getTime() / 1000 - options.startsAt.getTime() / 1000; + const pricePerGPUHour = totalPriceToPricePerGPUHour(options.priceCenticents, durationInSeconds, options.quantity, GPUS_PER_NODE); + const pricePerHourLabel = c.green(centicentsToDollarsFormatted(pricePerGPUHour)); + + const topLine = `${totalNodesLabel} ${instanceTypeLabel} ${nodesLabel} (${GPUS_PER_NODE * options.quantity} GPUs) at ${pricePerHourLabel} per GPU hour for ${c.green(durationHumanReadable)} ${timeDescription}`; const dollarsLabel = c.green( - centicentsToDollarsFormatted(options.priceCenticents), + centicentsToDollarsFormatted( + pricePerGPUHour * options.quantity * GPUS_PER_NODE, + ), ); - const priceLine = `\nBuy for ${dollarsLabel}?`; + + const priceLine = `\nBuy at ${dollarsLabel} per hour?`; return `${topLine}\n${priceLine} `; } From 3fc8dc28d66db3d48f08577d15adad011b0aca05 Mon Sep 17 00:00:00 2001 From: flaque Date: Fri, 13 Sep 2024 11:24:54 -0700 Subject: [PATCH 08/10] fix --- src/lib/buy.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/buy.ts b/src/lib/buy.ts index aab31c4..c591ea9 100644 --- a/src/lib/buy.ts +++ b/src/lib/buy.ts @@ -295,12 +295,13 @@ function confirmPlaceOrderMessage(options: BuyOptions) { const dollarsLabel = c.green( centicentsToDollarsFormatted( - pricePerGPUHour * options.quantity * GPUS_PER_NODE, + pricePerGPUHour, ), ); + const gpusLabel = c.green(options.quantity * GPUS_PER_NODE); - const priceLine = `\nBuy at ${dollarsLabel} per hour?`; + const priceLine = `\nBuy ${gpusLabel} GPUs at ${dollarsLabel} per GPU hour?`; return `${topLine}\n${priceLine} `; } From b264acadc438c69e550b352e957d2febd8494388 Mon Sep 17 00:00:00 2001 From: flaque Date: Fri, 13 Sep 2024 13:22:07 -0700 Subject: [PATCH 09/10] wip --- bun.lockb | Bin 45987 -> 45671 bytes package.json | 2 +- src/helpers/price.ts | 2 +- src/lib/buy.ts | 3 +- src/lib/constants.ts | 1 + src/lib/sell.ts | 136 +++++++++++++++++++++++++++++++++++-------- 6 files changed, 117 insertions(+), 27 deletions(-) create mode 100644 src/lib/constants.ts diff --git a/bun.lockb b/bun.lockb index 292ff99152e1a54c9587edc8a27f9b6645d00e9f..1ad7e697269a1c5ea99fe0d8f7db2b0bb865d4f6 100755 GIT binary patch delta 4932 zcmbVQe_T|>7Qb_0g^R2T%8%>ff~ct#$WIsI$7KanSn;>62x^5WT8KYitf+vdiNXsy zJdM;RYUQsiZTrD$mOQQ0RI++z_ER&_SEiMkt7-2$clRniUw^!}AI_ZfJu`D=&Y3ea zci7Y_ww@JhBl}kUVtX#vr{HVfv;974zvoQw^WUFL{pf`;56n+(ojj#idEuwK1obz& z&KfG-KdY{^(Y#qF2pt5WA`caM@Z@%a&=q_Hun%~9FG27FXW$&a2HpjHlUsj@a#`p! zqa+CUtLTiPJ-8RRFSr?9xc;iQAoznH2Ilo0QSka!==td37>$qe7?hpBPl9&@e;=Id z9pJp(Av(=M%Ud7@0r`tAYreqTY7@F}AG~0K&vhRFAAKh{x2lbv=N={7Z9M^;&#sSK zFN5>ZB{z1$IPd=!8bKdZF}HZ88prI;t^xeO`G6w(UDL81Ubas zY!96lL!jL?tD_rT6mR5ef9Sl_7Ow-jT9~~}%t;Pkg^eXAN;@U$6&6A^y&_FTo+hJ@2bm=k zJ4jCGf{}IE&@t7{qd-G|JPCHBmw^(2e8}u;V!g@PL19)(?5IdpSfz(lOMHI&fl}|- zU>vMI5X!j_T^`IP9VNUIQX-d0Cuv@IhCbgjsmfzXNSs1cOt zuSmtnT0WjcW`C3PksGz+en>&c9PSkm<~+)cU=fjcnrxjF=^S)p(ZWOZF&UY}XX~w^ z`EKM%m9X_L&;Zn7Y((0x6YcJjD&-*G`QV;B{L)iEX3ZFa=m=1lNKTY#lo+V!Dv{*r zloFVvI{+z_Qv8#6J!(>sT#8z=P?Mw&AaQ3sxNRPqAVoI{JLe&~6_CVg$QGODn*v_bW@Xt zy+yWeitY+@X_Vrd#Nx==O_Amx^SKd5mEBBi2ic5@bkVJohy|(%WmI%UU3t828I#2A zUFk+Zs(7I*?G8=V^}>!)xL!9|lcY_MTwb$KlkO}KdUi|F^~TPOr@dX0SRo~LSJ+mv zbyvi1LuhyRR2D|g?usrHdo`E#_DJ@GFote*PtqNQG)$F3LIuI1N>d??P^BhFIjW=! z)7p)Oq_$(*?TI~qVfG_^PcXYrpWR$)5YuGVv%mTG;5i$T$9%DMjVY}EhWw0>bc26& zmd`ia5BT-G{^pxC$CmVXH~Fjb)AIF(8NQi|GPi|)Ik~^&oSON`-`3F!4<1Z=<2?!= zuL`hu+B34_`F& zK*Oh-_kM26?Yre4?VhwRjG5VEC__(2fwD->v{G=Uo)%_Wn1v1j9R!LPXkj^I8)&7{fqME9 zXc+Y!WTifX^i(s*!bZ?%KqrAx23y!Dsv2yi<%9Kf5oio0W?3mA3;tzUaF|~JIuDeS zZDG08kPZK`;U7>QWm({#1^!tqYyvrft^-XNVqueL>k#-i1peh%*dvsi1OIa1A5Z~F zL*d_0_&3yo{pbK{0_rx*!lu%+VeoGl`~#Xs@^CAgPP0%xMu$*7PTfaXSuxpA&YV}TyYZ?t#HKNsLLbCxR`>gQ4P>@@q_ z+2h3WMyPPc3d=yq909vp*Z|_cr$Ag626_U7Q&dV}5Vh1dNXpHHf%-g?LaH$d8=!mjoz~aC`8JR`tLPo=$aTJ$hLn<$y+jhJ%KI zaB-_XjRYSB8be=}g_iSZj05F?#)EP}e93rJ!a$m?2V}k+|8+Ldb7vm}@qXMEx6@YU zHZ^@4KH#2)+^IGV?o2?NR3Yl8f_Mt3sX%)dgm#z)biE7(ev!3wZb6b4n;0M2KQhMN zXJH}p;){wO)i26o@$}WArE&~zRp$D#zV$fUIWs)tl*lHUB4cPl`B6iBWK?97Ae77~ zoQqFq!TQklNi}|KHE%&tHDLzrg`<6YqQc!Jnbudx{V%n1eFB@BSH7_-{d5cJ<6I`S zH;?k-h2LU~Gp?d0HZnRg4r$>K)a;*hb<&V$KEN?A!6RhtTze+KQ{P}gsK z&3&(@B#Laa-gRW{q_QVf|39pb3DI6=f%Un*bDo-e1S7^_W>`i<##l21-)zxKW!EU8 zZwH#P^f}f-Y?;iO$goTf(O!7R$8GyC>&p7?M79Wi@s)7}i`_GE_l#Y7*NV7&Xrs}b zYol@-a&>5{zo#=k39ZXxnIEl0nN9C3mkru;(5Vl9Js_>|vd9kO_Qt1vMO#ZQxlJgw zY49Op@oL;(6>HF5iBC5SD*D;5t)Ch&HSb>~zZEjuM-QxsWvgitjLD9|%j5L#*PU$sr*UVZmg z;qkfK#FcIAFs-SU@v;7YW)>EgkFE}SCqM3Jv}I`9=+9&=8}4*Axc^sB(Xn&dY~Ob} z<6Wzs9mw!|ZT!(UZCur+L3@yw{V?_Da{uqcMaC078nJ4mj?;vly{DftUbZk9&TO(3Lo$OOus~+k?Q!PGIy3pjh{I2W6 zTzlckg!m(@w8QQ5?j|j}?5FCymH6+}##H=|(gw2~R*x6Np5gZoAF`(uNMc#3Z9VZX6@w2!@d IW02p!0hvZbRsaA1 delta 5256 zcmbVQ3wTV|+TLr+Fq4=_LM~(Eo{A}nOfC|V%+3vo5fS7JaVa905G18d6dBbbM@iDJ zO>hXRRa1@28SPUoJuW>@QKD28t)fR8=@F`>t#c~QyY|c`zn(t-^Z)yqcYT+&_FCWf zt#7Tp*0fv`x3-A2(S~gkzQ{kBbN!ccm9)*Y@=$K)o&&u)KX`Zlr90Df1>b!W(cK}) z&(n35U@@}1u3~d~skPKv6g7R$cvNTw!4}q85PE{P% zwBq?fGhXCw?590qB)d!(#NeuHe$GzMfpXVQf^s8|!!zCoCZ;%Q+En2K$b76d0nT<) zp01ozG|M{w@sKH@d^}`-;9QmM54^KIG!{nlXkc8~AX_h|flBl~a>+1OcA$5VAOxbl z2dEzOCkS}gb_o=|***f*fi{8qW4>&!x*Ajwb9+<0YhqToN5h8i9lPDsO#R^~>k+a2 zvs%Y{szoC0Rmamz^-FZ8OT43xCR=1~w8uSBbInZ<9J+wQZABA-F zVk+9>nW%XaRY|B4>2{Yy#NXakVky*$avd485);X)l{C3X=@i}p-Bu^Eozx0lJ9NXK zWAcF6NY~+zFd$mILUvDyX{i-uDjB*-nz@+3L2?h=JUb+Akn`OZNNMuBMr|UCA%mC1 zW|0-;2V_Uds1;=f8N4OUB7~l|RS|DHh3wFsL_!ULPUIf3?qu+hG^3Hwd^`;qeG)bA zxunkAe(}DR-cp-11Cd+YDFgy0FCc4oNpk|aKS9??b}9_n#m!X9(qxz9MrE2r%??Nz zsKcP>@|SM(Ru7XH=SAOpNNTJIKDHZ=sb&YHbfrsngRH(1>rHl))2P)~QoVun&!jfr zWYu+WCW`V&=JkG(YASL}l54gooaPod?ki>j^CgkpUs64bH8qHue3RK8G6YDP&e(-` zjGXZpLU!mDLFcsMwr`^90Hh3R@=j(C$f}cAJlS=UW~Q$oB;&oVa^G9Y&{JYQCQyF=>`!Avzt#~lF!;$vP0JZ9q)*dF))#xCPR><@$Tu=X=smcA~TacNK(B7T_!aJ zC5xwe(p$bJQLCqtV3R5bt4rc~)k=lad<@PN|DZ&b8cP#z1}3Y_;1Z~*M>4CX))0xE zBtxhqb_t}CP!r1{d#I$!#Cjb^O`*xE4d4o7?l!nQnM(^61hdSo0+%myUxCY&IYS6< z*A|-M21ahiPB~h`e`0jv3AN*^Cu%#%%xuFtF-DK)m|#Er3JR7(~bS=h8sU?oN(xi*FFgK8av(?+E`BqVgjndzc@89Z*s-0 z@xA`J`1P{p7g_@r{b6ieda<92x7&(reWxEFr7{d=D?cfam_=I+0TN6$R|=U0Eb zFZ{jF<-@a&?p-=Abb>B0Fn?$CyHO1Z7k~Wcxz4|=ne@#e_2k2~dL5M-1FGuYFU$$8 z&Yh7~^m0wXKRgZdcbG2z=5g?Y!z*%1KM8CZd%AIQyY4Uhiw4gez4^Ou8~Ya?P%pcm z`{cKEhW3AayKU{T4%T7eJ$&2-ZEL3WZs}Kaw~NmmyD;ZYdC{rQU!9-PzobYktK4ZH z5k5Az`Sq_cea7UUU0AgL(_xuQT4t{eI8m~^+z}W%&x4|jW;&m$rPZls zmPi*;Ews|4rBsud4Ww$5g_0yKwLwazfs%!;L)s{rvCFnXTAQY&+%z-xg>`8b%1+nP zeMsq)lWw73AvL9&Sti|sv^_&h6En=6Sb6&(%^a+yz`=ord!N9d@zYTDhU2h|pK zWyy|>g{%F@Hp=KeuiTjo+n=HMvNT6o*?6%E`+@u{M~TD{xV#y_HZH6HxGn_P0`O-g zz;&U(vj8?~p&Af@Fkk_IZCY3b@XvWTPzvA}A*=zoE&?b4uq#&ybuQ%-pfdq}W_iV> z>kT>`z^*GaxO9=AMF4hKVZBS&2Xr!komAKe@Ief~1OU6J><~BbNgxft=|y-A5CxnO z`hpolr{^CDN&uG!@Mb#!?&tYuP~dr`k{TEEb{t#~AZobp1;7|$70EAGL{Lgaiu=Jz z-b@g_T7-sS1|DiFPz;O(c)vIr2)Te6z{y0&0kY|I#jeny;PBNHBmf_DAr(jgl7T_M zK-y9nslz!)NCf!!I0py=fDj;ou2wGdO#?R=$OO`X3?Pdt7ls>f?h!@+qkw#1Bwzu! z52JxG01g$xG&-~}xN00Y9=P#9A;3rD!3+TuT{w6?i~rRc=%avsfHDcZpN>55laImm zkI{_((J<|?28scSQ9L_%*31BS260+OTb>Jc_gV}V&yYH5elEESpME?JuSLbI3!9V_ z9TRP&MT>J(@!(>pc~vj^ZgH`{5%Ffu8^Hb9Z1=pdtcxO>lt{;ygwXR#b_L3<1!3-- z;`un_3zk7Uo?7kA8qg*I{asn+pAv`uVv+N%%b9XEMvK^JV{{BwNoVIZ?ea@UEeG4* ze-kyZAleu!d`?@cbYe?q`k~4nYq8ha*Giavl4U%4|BZO-)Od1&H_5G+}7~PT_yE<+4c9F4gr?ZUuGy z3s?7kCv~96_yXY1U3cnNqr9WvkNm~9VB0vSy6v-{r{0oXis#NYQw{oY-LY(@W74jT z)9-u8^~PvpqHxQD+N=HC5`_mI^fgBJB;%j`+_2n&$oqL6PSky$*XfljUFqwSUmJ1c zoYCbBy3ePl&}tLqzo=tP^l$L8V=kB2hjbOH4i!$1^}qLPHICUDcu-FpSGgh*%eK*; zWjY?06U&C{cez>=e0C#Z;asnAFk(P-ERU!>iuj|JM!c|ofO3s}d#-Ws?3+c6XvXIq z&6JDm)Ut@wG>>jcau6_Qg3#8Df?f;|oiY8rH!e4vUOFev=~xGi;&BI+t0U(N9reF^ z8OK^_MI1|@r&q)!DA(Q#MV@ajYo5ASWJ`GFAlS+U_?|ZG9pALgKZxw6>q^|QcPHN= z=89?ZuvELUC+=!HR`L|Qyi%uEZpv%k*S4(Ry4hQvTX{8HqwdvldgZ2^_w19K5>D@G zm+NDqvEd!3DX3R&&^;=a=p!~?+2Ar4@!n6ns&$Mfpnp)j@JA^a?@Yk}y>fF-c{d`g zceQv#o*pbrY^cad9pee<9~dtvSMS1`H}Xaw^YWD2A(!Kkj8$mI{r3w(xRg74$;fX` zMV!5n;IceU5KhvTmy~7aoLtx9)Bi8;{9*b|ibW#DM-Tjer?1m&H<&FO*CFNn9x9cI zls$Z?ef-VGY9A%rqmJZ!b4BX0e*Uk4V>Y_9MyFT)w%Gc~+R9BOXROGAq-gBBF~V;0 zuF<)c)_+;|%6?WwPu1uWls_*{4{&7st@+1EZp;{+#8Y2z{=XsEy8YBpA8b0c`BBYB z)KsI>EB}XldoOO*weU{jqxzrdDh%)lqTgx)B9y;V3To1x-#P4?@JG#R>*Re^__U6a zYUf2B32}aEUPwwf!74o4g_oVx9u6mUPg#&;3c+{fIUQZEz1j)W?>JEx$(Ye`=jB5p zuEp=Zn%@cE1ahw*qT)MHB6&9k)BJiL72*;dOQ*Mo`N)3~_K)hXe3GJKDZf6#ald}F OI}NBeIYw^r_x=w>cB2mf diff --git a/package.json b/package.json index 9a39f93..3246292 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "inquirer": "^10.1.2", "node-fetch": "^3.3.2", "openapi-fetch": "^0.11.1", - "ora": "^8.0.1", + "ora": "^8.1.0", "parse-duration": "^1.1.0" }, "devDependencies": { diff --git a/src/helpers/price.ts b/src/helpers/price.ts index 374aae6..eb634e4 100644 --- a/src/helpers/price.ts +++ b/src/helpers/price.ts @@ -1,5 +1,5 @@ export function pricePerGPUHourToTotalPrice(pricePerGPUHourInCenticents: number, durationSeconds: number, nodes: number, gpusPerNode: number) { - return (pricePerGPUHourInCenticents) * durationSeconds / 3600 * nodes * gpusPerNode; + return Math.ceil(pricePerGPUHourInCenticents * durationSeconds / 3600 * nodes * gpusPerNode); } export function totalPriceToPricePerGPUHour(totalPriceInCenticents: number, durationSeconds: number, nodes: number, gpusPerNode: number) { diff --git a/src/lib/buy.ts b/src/lib/buy.ts index c591ea9..1874d96 100644 --- a/src/lib/buy.ts +++ b/src/lib/buy.ts @@ -23,6 +23,7 @@ import { import type { Nullable } from "../types/empty"; import { formatDuration } from "./orders"; import { pricePerGPUHourToTotalPrice, totalPriceToPricePerGPUHour } from "../helpers/price"; +import { GPUS_PER_NODE } from "./constants"; dayjs.extend(relativeTime); dayjs.extend(duration); @@ -37,8 +38,6 @@ interface SfBuyOptions { quote?: boolean; } -const GPUS_PER_NODE = 8; - export function registerBuy(program: Command) { program .command("buy") diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 0000000..bb10428 --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1 @@ +export const GPUS_PER_NODE = 8; \ No newline at end of file diff --git a/src/lib/sell.ts b/src/lib/sell.ts index f7ad3d4..f0def42 100644 --- a/src/lib/sell.ts +++ b/src/lib/sell.ts @@ -15,19 +15,22 @@ import { roundStartDate, } from "../helpers/units"; import type { PlaceSellOrderParameters } from "./orders"; +import { GPUS_PER_NODE } from "./constants"; +import { pricePerGPUHourToTotalPrice } from "../helpers/price"; +import ora from "ora"; export function registerSell(program: Command) { program .command("sell") .description("Place a sell order") - .requiredOption("-p, --price ", "Specify the price in centicents") + .requiredOption("-p, --price ", "The price in dollars, per GPU hour") .requiredOption("-c, --contract-id ", "Specify the contract ID") - .option("-n, --nodes ", "Specify the number of nodes") - .requiredOption( + .option("-n, --accelerators ", "Specify the number of GPUs", "8") + .option( "-s, --start ", "Specify the start time (ISO 8601 format)", ) - .requiredOption( + .option( "-d, --duration ", "Specify the duration in seconds", ) @@ -48,50 +51,133 @@ 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: { + intervals: string[] // date strings + quantities: number[] + } +}) { + const startDate = dayjs(contract.shape.intervals[0]).toDate(); + const endDate = dayjs(contract.shape.intervals[contract.shape.intervals.length - 1]).toDate(); + + 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; - nodes: number; + accelerators: number; start?: string; - duration: string; - flags?: Record; + duration?: string; }) { const loggedIn = await isLoggedIn(); if (!loggedIn) { return logLoginMessageAndQuit(); } - const flags = options.flags || {}; - const durationSecs = parseDuration(options.duration, "s"); - if (!durationSecs) { - return logAndQuit("Invalid duration"); + const { centicents: priceCenticents, invalid } = priceWholeToCenticents( + options.price, + ); + if (invalid || !priceCenticents) { + return logAndQuit(`Invalid price: ${options.price}`); } - let startDate = options.start ? chrono.parseDate(options.start) : new Date(); + const contract = await getContract(options.contractId); + if (!contract) { + return logAndQuit(`Contract ${options.contractId} not found`); + } + + if (contract?.status === "pending") { + return logAndQuit(`Contract ${options.contractId} is currently pending. Please try again in a few seconds.`); + } + + const { startDate: contractStartDate, endDate: contractEndDate } = contractStartAndEnd({ + shape: { + intervals: contract.shape.intervals, + quantities: contract.shape.quantities, + } + }); + + let startDate = options.start ? chrono.parseDate(options.start) : contractStartDate; if (!startDate) { return logAndQuit("Invalid start date"); } startDate = roundStartDate(startDate); - let endDate = dayjs(startDate).add(durationSecs, "s").toDate(); - endDate = roundEndDate(endDate); + let endDate = contractEndDate; + if (options.duration) { + const durationSecs = parseDuration(options.duration, "s"); + if (!durationSecs) { + return logAndQuit("Invalid duration"); + } + endDate = dayjs(startDate).add(durationSecs, "s").toDate(); + } - const { centicents: priceCenticents, invalid } = priceWholeToCenticents( - options.price, - ); - if (invalid || !priceCenticents) { - return logAndQuit(`Invalid price: ${options.price}`); + endDate = roundEndDate(endDate); + // if the end date is longer than the contract, use the contract end date + if (endDate > contractEndDate) { + endDate = contractEndDate; } + const totalDurationSecs = dayjs(endDate).diff(startDate, "s"); + + const totalPrice = pricePerGPUHourToTotalPrice(priceCenticents, totalDurationSecs, options.accelerators, GPUS_PER_NODE); const params: PlaceSellOrderParameters = { side: "sell", - quantity: forceAsNumber(options.nodes), - price: priceCenticents, + quantity: forceAsNumber(options.accelerators) * GPUS_PER_NODE, + price: totalPrice, contract_id: options.contractId, start_at: startDate.toISOString(), end_at: endDate.toISOString(), - ...flags, }; const api = await apiClient(); @@ -113,6 +199,10 @@ async function placeSellOrder(options: { } } - console.log(data); + if (!data?.id) { + return logAndQuit("Order ID not found"); + } + + await waitForOrderToNotBePending(data.id); process.exit(0); } From 8a2100632cee7d3e49f4c664428349c3c4d64f44 Mon Sep 17 00:00:00 2001 From: flaque Date: Fri, 13 Sep 2024 14:03:14 -0700 Subject: [PATCH 10/10] add a smoother experience --- src/helpers/fetchers.ts | 32 ++++++++++++++++++ src/helpers/waitingForOrder.ts | 23 +++++++++++++ src/lib/buy.ts | 8 ++++- src/lib/sell.ts | 62 +++++++--------------------------- 4 files changed, 74 insertions(+), 51 deletions(-) create mode 100644 src/helpers/fetchers.ts create mode 100644 src/helpers/waitingForOrder.ts diff --git a/src/helpers/fetchers.ts b/src/helpers/fetchers.ts new file mode 100644 index 0000000..f13f3c7 --- /dev/null +++ b/src/helpers/fetchers.ts @@ -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; +} \ No newline at end of file diff --git a/src/helpers/waitingForOrder.ts b/src/helpers/waitingForOrder.ts new file mode 100644 index 0000000..a100bfd --- /dev/null +++ b/src/helpers/waitingForOrder.ts @@ -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`); +} diff --git a/src/lib/buy.ts b/src/lib/buy.ts index 1874d96..913c5a7 100644 --- a/src/lib/buy.ts +++ b/src/lib/buy.ts @@ -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); @@ -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) => diff --git a/src/lib/sell.ts b/src/lib/sell.ts index f0def42..a404f8b 100644 --- a/src/lib/sell.ts +++ b/src/lib/sell.ts @@ -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 @@ -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: { @@ -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; @@ -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, @@ -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(),