mirror of
https://github.com/logos-storage/logos-storage-js.git
synced 2026-01-04 06:23:06 +00:00
Add marketplace endpoints
This commit is contained in:
parent
8bd91eb41b
commit
34bf6008ce
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
2
.npmignore
Normal file
2
.npmignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
src
|
||||||
|
.editorconfig
|
||||||
170
README.md
Normal file
170
README.md
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
# Codex SDK
|
||||||
|
|
||||||
|
The Codex SDK provides an API for interacting with the Codex decentralized storage network.
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { Codex } from "@codex/sdk-js";
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { Codex } = require("@codex/sdk-js");
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
To create a Codex instance, provide the REST API url to interact with the Codex client:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const codex = new Codex("http://localhost:3000")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error handling
|
||||||
|
|
||||||
|
The SDK provides a type called `SafeValue` for error handling instead of throwing errors. It is inspired by Go's "error as value" concept.
|
||||||
|
If the value represents an error, `error` is true and `data` will contain the error.
|
||||||
|
If the value is not an error, `error` is false and `data` will contain the requested data.
|
||||||
|
|
||||||
|
The error type is a [CodexError](./src/errors/errors.ts#L15) which can be error object of 3 types:
|
||||||
|
|
||||||
|
* `error`: Object containing the error message
|
||||||
|
* `api`: Object containing the api error message and the status code
|
||||||
|
* `validation`: Object containing the error message and a field `errors` of type [ValidationError](./src/errors/errors.ts#L3) containing the error message for each fields.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const slots = await codex.marketplace.activeSlots();
|
||||||
|
|
||||||
|
if (slots.error) {
|
||||||
|
// Do something to handle the error in slots.data
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access the slots within slots.data.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Marketplace
|
||||||
|
|
||||||
|
#### activeSlots()
|
||||||
|
|
||||||
|
Returns active slots.
|
||||||
|
|
||||||
|
- returns Promise<[CodexSlot](./src/marketplace/types.ts#L86)[]>
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const slots = await codex.marketplace.activeSlots();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### activeSlot(slotId)
|
||||||
|
|
||||||
|
Returns active slot with id {slotId} for the host.
|
||||||
|
|
||||||
|
- slotId (string, required)
|
||||||
|
- returns Promise<[CodexSlot](./src/marketplace/types.ts#L86)[]>
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const slotId= "AB9........"
|
||||||
|
const slot = await codex.marketplace.activeSlot(slotId);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### availabilities
|
||||||
|
|
||||||
|
Returns storage that is for sale.
|
||||||
|
|
||||||
|
- returns Promise<[CodexAvailability](./src/marketplace/types.ts#L100)>
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const availabilities = await codex.marketplace.availabilities();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### createAvailability
|
||||||
|
|
||||||
|
Offers storage for sale.
|
||||||
|
|
||||||
|
- input ([CodexCreateAvailabilityInput](./src/marketplace/types.ts#L133), required)
|
||||||
|
- returns Promise<[CodexAvailabilityCreateResponse](./src/marketplace/types.ts#L124)[]>
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const response = await codex.marketplace.createAvailability({
|
||||||
|
maxCollateral: 1,
|
||||||
|
totalSize: 3000,
|
||||||
|
minPrice: 100,
|
||||||
|
duration: 100,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### reservations
|
||||||
|
|
||||||
|
Return list of reservations for ongoing Storage Requests that the node hosts.
|
||||||
|
|
||||||
|
- availabilityId (string, required)
|
||||||
|
- returns Promise<[CodexReservation](./src/marketplace/types.ts#L152)[]>
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const reservations = await codex.marketplace.reservations("Ox...");
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### createStorageRequest
|
||||||
|
|
||||||
|
Creates a new Request for storage
|
||||||
|
|
||||||
|
- input ([CodexCreateStorageRequestInput](./src/marketplace/types.ts#L182), required)
|
||||||
|
- returns Promise<[CodexCreateStorageRequestResponse](./src/marketplace/types.ts#L195)[]>
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const request = await codex.marketplace.createStorageRequest({
|
||||||
|
duration: 3000,
|
||||||
|
reward: 100,
|
||||||
|
proofProbability: 1,
|
||||||
|
nodes: 1,
|
||||||
|
tolerance: 0,
|
||||||
|
collateral: 100,
|
||||||
|
expiry: 3000
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### purchaseIds
|
||||||
|
|
||||||
|
Returns list of purchase IDs
|
||||||
|
|
||||||
|
- returns Promise<string[]>
|
||||||
|
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const ids = await codex.marketplace.purchaseIds();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### purchaseDetail
|
||||||
|
|
||||||
|
Returns list of purchase IDs
|
||||||
|
|
||||||
|
- purchaseId (string, required)
|
||||||
|
- returns Promise<[CodexPurchase](./src/marketplace/types.ts#L168)[]>
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const purchaseId = "Ox........"
|
||||||
|
const purchase = await codex.marketplace.purchaseDetail(purchaseId);
|
||||||
|
```
|
||||||
64
package-lock.json
generated
Normal file
64
package-lock.json
generated
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"name": "@codex/sdk-js",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "@codex/sdk-js",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sinclair/typebox": "^0.32.35"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@faker-js/faker": "^8.4.1",
|
||||||
|
"@tsconfig/strictest": "^2.0.5",
|
||||||
|
"typescript": "^5.5.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@faker-js/faker": {
|
||||||
|
"version": "8.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz",
|
||||||
|
"integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fakerjs"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||||
|
"npm": ">=6.14.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sinclair/typebox": {
|
||||||
|
"version": "0.32.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.32.35.tgz",
|
||||||
|
"integrity": "sha512-Ul3YyOTU++to8cgNkttakC0dWvpERr6RYoHO2W47DLbFvrwBDJUY31B1sImH6JZSYc4Kt4PyHtoPNu+vL2r2dA=="
|
||||||
|
},
|
||||||
|
"node_modules/@tsconfig/strictest": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tsconfig/strictest/-/strictest-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-ec4tjL2Rr0pkZ5hww65c+EEPYwxOi4Ryv+0MtjeaSQRJyq322Q27eOQiFbuNgw2hpL4hB1/W/HBGk3VKS43osg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||||
|
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
package.json
Normal file
42
package.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "@codex/sdk-js",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Codex SDK to interact with the Codex decentralized storage network.",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"prepack": "npm run build",
|
||||||
|
"prebuild": "rm -Rf dist/*",
|
||||||
|
"build": "tsc",
|
||||||
|
"compile": "tsc --noEmit",
|
||||||
|
"pretest": "npm run build",
|
||||||
|
"test": "node --test",
|
||||||
|
"watch": "tsc --watch"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Codex",
|
||||||
|
"Javascript",
|
||||||
|
"SDK",
|
||||||
|
"storage"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"dist/**"
|
||||||
|
],
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"author": "Codex team",
|
||||||
|
"readme": "README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@faker-js/faker": "^8.4.1",
|
||||||
|
"@tsconfig/strictest": "^2.0.5",
|
||||||
|
"typescript": "^5.5.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@sinclair/typebox": "^0.32.35"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/api/config.ts
Normal file
5
src/api/config.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const Api = {
|
||||||
|
config: {
|
||||||
|
prefix: "/api/codex/v1"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/disk/disk.ts
Normal file
19
src/disk/disk.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { SafeValue } from "../values/values"
|
||||||
|
|
||||||
|
export class Disk {
|
||||||
|
readonly url: string
|
||||||
|
|
||||||
|
constructor(url: string) {
|
||||||
|
this.url = url
|
||||||
|
}
|
||||||
|
|
||||||
|
async available(): Promise<SafeValue<{ full: number, used: number }>> {
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
data: {
|
||||||
|
full: 500,
|
||||||
|
used: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/errors/errors.ts
Normal file
51
src/errors/errors.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { ValueErrorIterator } from "@sinclair/typebox/build/cjs/errors"
|
||||||
|
|
||||||
|
type ValidationError = {
|
||||||
|
path: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CodexError which can be error object of 3 types:
|
||||||
|
* `error`: Object containing the error message
|
||||||
|
* `api`: Object containing the api error message and the status code
|
||||||
|
* `validation`: Object containing the error message and a field `errors` of type ValidationError
|
||||||
|
* containing the error message for each fields.
|
||||||
|
*/
|
||||||
|
export type CodexError = {
|
||||||
|
type: "error"
|
||||||
|
message: string
|
||||||
|
} | {
|
||||||
|
type: "api"
|
||||||
|
message: string
|
||||||
|
status: number
|
||||||
|
} | {
|
||||||
|
type: "validation"
|
||||||
|
message: string
|
||||||
|
errors: ValidationError[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CodexValidationErrors = {
|
||||||
|
map(iterator: ValueErrorIterator) {
|
||||||
|
let error
|
||||||
|
const errors = []
|
||||||
|
|
||||||
|
while (error = iterator.First()) {
|
||||||
|
errors.push({
|
||||||
|
path: error.path,
|
||||||
|
message: error.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// export class CodexError extends Error {
|
||||||
|
// readonly status: number | null
|
||||||
|
|
||||||
|
// constructor(message: string, status: number | null = null) {
|
||||||
|
// super(message)
|
||||||
|
// this.status = status
|
||||||
|
// }
|
||||||
|
// }
|
||||||
88
src/fetch-safe/fetch-safe.test.ts
Normal file
88
src/fetch-safe/fetch-safe.test.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import assert from "assert";
|
||||||
|
import { describe, it } from "node:test";
|
||||||
|
import { Fetch } from '../fetch-safe/fetch-safe';
|
||||||
|
|
||||||
|
class MockResponse implements Response {
|
||||||
|
headers: Headers = new Headers()
|
||||||
|
ok: boolean;
|
||||||
|
redirected = false
|
||||||
|
status: number;
|
||||||
|
statusText = "";
|
||||||
|
type = "basic" as "basic"
|
||||||
|
url = ""
|
||||||
|
body = null;
|
||||||
|
bodyUsed = false;
|
||||||
|
_text: string
|
||||||
|
|
||||||
|
constructor(ok: boolean, status: number, text: string) {
|
||||||
|
this.ok = ok
|
||||||
|
this.status = status
|
||||||
|
this._text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
clone(): Response {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayBuffer(): Promise<ArrayBuffer> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
blob(): Promise<Blob> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
formData(): Promise<FormData> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
json(): Promise<any> {
|
||||||
|
return Promise.resolve(JSON.parse(this._text))
|
||||||
|
}
|
||||||
|
|
||||||
|
text(): Promise<string> {
|
||||||
|
return Promise.resolve(this._text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("fetch", () => {
|
||||||
|
it("returns an error when the http call failed", async (t) => {
|
||||||
|
global.fetch = t.mock.fn(() =>
|
||||||
|
Promise.resolve(new MockResponse(false, 500, "error")),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await Fetch.safe("http://localhost:3000/some-url", { method: "GET" })
|
||||||
|
const error = {
|
||||||
|
type: "api",
|
||||||
|
message: "error",
|
||||||
|
status: 500
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result, { error: true, data: error });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when the json parsing failed", async (t) => {
|
||||||
|
global.fetch = t.mock.fn(() =>
|
||||||
|
Promise.resolve(new MockResponse(true, 200, "")),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await Fetch.safe("http://localhost:3000/some-url", { method: "GET" })
|
||||||
|
const error = {
|
||||||
|
type: "error",
|
||||||
|
message: "Unexpected end of JSON input"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result, { error: true, data: error });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("returns the data when the fetch succeed", async (t) => {
|
||||||
|
global.fetch = t.mock.fn(() =>
|
||||||
|
Promise.resolve(new MockResponse(true, 200, JSON.stringify({ hello: "world" }))),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await Fetch.safe("http://localhost:3000/some-url", { method: "GET" })
|
||||||
|
|
||||||
|
assert.deepStrictEqual(result, { error: false, data: { hello: "world" } });
|
||||||
|
});
|
||||||
|
});
|
||||||
34
src/fetch-safe/fetch-safe.ts
Normal file
34
src/fetch-safe/fetch-safe.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { SafeValue } from "../values/values"
|
||||||
|
|
||||||
|
export const Fetch = {
|
||||||
|
async safe<T extends Object>(url: string, init: RequestInit): Promise<SafeValue<T>> {
|
||||||
|
const res = await fetch(url, init)
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const message = await res.text()
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
data: {
|
||||||
|
type: "api",
|
||||||
|
message,
|
||||||
|
status: res.status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const json = await res.json()
|
||||||
|
|
||||||
|
return { error: false, data: json }
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
data: {
|
||||||
|
type: "error",
|
||||||
|
message: e instanceof Error ? e.message : "JSON parsing error :" + e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/index.ts
Normal file
18
src/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Disk } from "./disk/disk";
|
||||||
|
import { Marketplace } from "./marketplace/marketplace";
|
||||||
|
|
||||||
|
export * from "./fetch-safe/fetch-safe";
|
||||||
|
export * from "./marketplace/types";
|
||||||
|
|
||||||
|
export class Codex {
|
||||||
|
readonly url: string
|
||||||
|
readonly marketplace: Marketplace
|
||||||
|
readonly disk: Disk
|
||||||
|
|
||||||
|
constructor(url: string) {
|
||||||
|
this.url = url
|
||||||
|
this.marketplace = new Marketplace(url)
|
||||||
|
this.disk = new Disk(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
347
src/marketplace/marketplace.test.ts
Normal file
347
src/marketplace/marketplace.test.ts
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import assert from "assert";
|
||||||
|
import { describe, it } from "node:test";
|
||||||
|
import { Fetch } from "../fetch-safe/fetch-safe";
|
||||||
|
import { Marketplace } from "./marketplace";
|
||||||
|
|
||||||
|
// function createSlot() {
|
||||||
|
// return {
|
||||||
|
// "id": faker.string.alphanumeric(64),
|
||||||
|
// "request": {
|
||||||
|
|
||||||
|
// "id": faker.string.alphanumeric(64),
|
||||||
|
// "client": faker.finance.ethereumAddress(),
|
||||||
|
// "ask":
|
||||||
|
// {
|
||||||
|
// "slots": faker.number.int({ min: 0, max: 9 }),
|
||||||
|
// "slotSize": faker.number.float({ max: 10000 }).toString(),
|
||||||
|
// "duration": faker.number.int({ max: 300000 }).toString(),
|
||||||
|
// "proofProbability": faker.number.int({ max: 9 }),
|
||||||
|
// "reward": faker.number.float({ max: 1000 }).toString(),
|
||||||
|
// "maxSlotLoss": faker.number.int({ max: 9 })
|
||||||
|
// },
|
||||||
|
// "content": {
|
||||||
|
// "cid": faker.string.alphanumeric(64),
|
||||||
|
// "por": {
|
||||||
|
// "u": faker.string.alphanumeric(16),
|
||||||
|
// "publicKey": faker.string.alphanumeric(64),
|
||||||
|
// "name": faker.string.alphanumeric(16)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "expiry": faker.number.int({ min: 2, max: 59 }) + " minutes",
|
||||||
|
// "nonce": faker.string.alphanumeric(64)
|
||||||
|
// },
|
||||||
|
// "slotIndex": faker.number.int({ min: 0, max: 9 })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
function createStorageRequest() {
|
||||||
|
return {
|
||||||
|
cid: faker.string.alphanumeric(64),
|
||||||
|
duration: faker.number.int({ min: 1 }),
|
||||||
|
reward: faker.number.int(),
|
||||||
|
proofProbability: faker.number.int(),
|
||||||
|
nodes: faker.number.int(),
|
||||||
|
tolerance: faker.number.int(),
|
||||||
|
expiry: faker.number.int({ min: 1 }),
|
||||||
|
collateral: faker.number.int()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function missingNumberValidationError(field: string) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
data: {
|
||||||
|
type: "validation",
|
||||||
|
message: "Cannot validate the input",
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
path: "/" + field,
|
||||||
|
message: "Expected required property"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/" + field,
|
||||||
|
message: "Expected number"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function missingStringValidationError(field: string) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
data: {
|
||||||
|
type: "validation",
|
||||||
|
message: "Cannot validate the input",
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
path: "/" + field,
|
||||||
|
message: "Expected required property"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/" + field,
|
||||||
|
message: "Expected string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mistypeNumberValidationError(field: string) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
data: {
|
||||||
|
type: "validation",
|
||||||
|
message: "Cannot validate the input",
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
path: "/" + field,
|
||||||
|
message: "Expected number"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function minNumberValidationError(field: string, min: number) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
data: {
|
||||||
|
type: "validation",
|
||||||
|
message: "Cannot validate the input",
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
path: "/" + field,
|
||||||
|
message: "Expected number to be greater or equal to " + min
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAvailability() {
|
||||||
|
return {
|
||||||
|
"id": faker.finance.ethereumAddress(),
|
||||||
|
"availabilityId": faker.finance.ethereumAddress(),
|
||||||
|
"size": faker.number.int({ min: 3000, max: 300000 }),
|
||||||
|
"requestId": faker.finance.ethereumAddress(),
|
||||||
|
"slotIndex": faker.number.int({ min: 0, max: 9 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("marketplace", () => {
|
||||||
|
const marketplace = new Marketplace("http://localhost:3000")
|
||||||
|
|
||||||
|
it("returns an error when trying to create an availability without total size", async () => {
|
||||||
|
const response = await marketplace.createAvailability({
|
||||||
|
duration: 3000,
|
||||||
|
maxCollateral: 1,
|
||||||
|
minPrice: 100,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, missingNumberValidationError("totalSize"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create an availability with an invalid number valid", async () => {
|
||||||
|
const response = await marketplace.createAvailability({
|
||||||
|
duration: 3000,
|
||||||
|
maxCollateral: 1,
|
||||||
|
minPrice: 100,
|
||||||
|
totalSize: "abc"
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, mistypeNumberValidationError("totalSize"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create an availability with zero total size", async () => {
|
||||||
|
const response = await marketplace.createAvailability({
|
||||||
|
duration: 3000,
|
||||||
|
maxCollateral: 1,
|
||||||
|
minPrice: 100,
|
||||||
|
totalSize: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, minNumberValidationError("totalSize", 1))
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create an availability without duration", async () => {
|
||||||
|
const response = await marketplace.createAvailability({
|
||||||
|
totalSize: 3000,
|
||||||
|
maxCollateral: 1,
|
||||||
|
minPrice: 100,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, missingNumberValidationError("duration"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create an availability with zero duration", async () => {
|
||||||
|
const response = await marketplace.createAvailability({
|
||||||
|
duration: 0,
|
||||||
|
maxCollateral: 1,
|
||||||
|
minPrice: 100,
|
||||||
|
totalSize: 3000
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, minNumberValidationError("duration", 1))
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create an availability without min price", async () => {
|
||||||
|
const response = await marketplace.createAvailability({
|
||||||
|
totalSize: 3000,
|
||||||
|
maxCollateral: 1,
|
||||||
|
duration: 100,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, missingNumberValidationError("minPrice"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create an availability without max collateral", async () => {
|
||||||
|
const response = await marketplace.createAvailability({
|
||||||
|
totalSize: 3000,
|
||||||
|
minPrice: 100,
|
||||||
|
duration: 100,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, missingNumberValidationError("maxCollateral"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns a response when the request succeed", async (t) => {
|
||||||
|
const data = { ...createAvailability(), freeSize: 1000 }
|
||||||
|
|
||||||
|
t.mock.method(Fetch, "safe", () =>
|
||||||
|
Promise.resolve({ error: false, data }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await marketplace.createAvailability({
|
||||||
|
maxCollateral: 1,
|
||||||
|
totalSize: 3000,
|
||||||
|
minPrice: 100,
|
||||||
|
duration: 100,
|
||||||
|
hello: "world"
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, { error: false, data });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns a response when the create availability succeed", async (t) => {
|
||||||
|
const data = { ...createAvailability(), freeSize: 1000 }
|
||||||
|
|
||||||
|
t.mock.method(Fetch, "safe", () =>
|
||||||
|
Promise.resolve({ error: false, data }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await marketplace.createAvailability({
|
||||||
|
maxCollateral: 1,
|
||||||
|
totalSize: 3000,
|
||||||
|
minPrice: 100,
|
||||||
|
duration: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, { error: false, data });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to update an availability without id", async () => {
|
||||||
|
const response = await marketplace.updateAvailability({
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, missingStringValidationError("id"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to update an availability with zero total size", async () => {
|
||||||
|
const response = await marketplace.updateAvailability({
|
||||||
|
id: faker.string.alphanumeric(64),
|
||||||
|
totalSize: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, minNumberValidationError("totalSize", 1))
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to update an availability with zero duration", async () => {
|
||||||
|
const response = await marketplace.updateAvailability({
|
||||||
|
id: faker.string.alphanumeric(64),
|
||||||
|
duration: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, minNumberValidationError("duration", 1))
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns a response when the update availability succeed", async (t) => {
|
||||||
|
const data = createAvailability()
|
||||||
|
|
||||||
|
t.mock.method(Fetch, "safe", () =>
|
||||||
|
Promise.resolve({ error: false, data }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await marketplace.updateAvailability({
|
||||||
|
id: faker.string.alphanumeric(64),
|
||||||
|
totalSize: 3000,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, { error: false, data });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create a storage request without cid", async () => {
|
||||||
|
const { cid, ...rest } = createStorageRequest()
|
||||||
|
|
||||||
|
const response = await marketplace.createStorageRequest(rest as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, missingStringValidationError("cid"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create a storage request without duration", async () => {
|
||||||
|
const { duration, ...rest } = createStorageRequest()
|
||||||
|
|
||||||
|
const response = await marketplace.createStorageRequest(rest as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, missingNumberValidationError("duration"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create a storage request with zero duration", async () => {
|
||||||
|
const { duration, ...rest } = createStorageRequest()
|
||||||
|
|
||||||
|
const response = await marketplace.createStorageRequest({ ...rest, duration: 0 })
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, minNumberValidationError("duration", 1));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create a storage request without reward", async () => {
|
||||||
|
const { reward, ...rest } = createStorageRequest()
|
||||||
|
|
||||||
|
const response = await marketplace.createStorageRequest(rest as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, missingNumberValidationError("reward"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create a storage request without proof probability", async () => {
|
||||||
|
const { proofProbability, ...rest } = createStorageRequest()
|
||||||
|
|
||||||
|
const response = await marketplace.createStorageRequest(rest as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, missingNumberValidationError("proofProbability"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create a storage request without expiry", async () => {
|
||||||
|
const { expiry, ...rest } = createStorageRequest()
|
||||||
|
|
||||||
|
const response = await marketplace.createStorageRequest(rest as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, missingNumberValidationError("expiry"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create a storage request with zero expiry", async () => {
|
||||||
|
const { expiry, ...rest } = createStorageRequest()
|
||||||
|
|
||||||
|
const response = await marketplace.createStorageRequest({ ...rest, expiry: 0 })
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, minNumberValidationError("expiry", 1));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an error when trying to create a storage request without collateral", async () => {
|
||||||
|
const { collateral, ...rest } = createStorageRequest()
|
||||||
|
|
||||||
|
const response = await marketplace.createStorageRequest(rest as any)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(response, missingNumberValidationError("collateral"));
|
||||||
|
});
|
||||||
|
});
|
||||||
173
src/marketplace/marketplace.ts
Normal file
173
src/marketplace/marketplace.ts
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import { Value } from '@sinclair/typebox/value'
|
||||||
|
import { Api } from "../api/config"
|
||||||
|
import { CodexValidationErrors } from '../errors/errors'
|
||||||
|
import { Fetch } from "../fetch-safe/fetch-safe"
|
||||||
|
import { SafeValue } from "../values/values"
|
||||||
|
import { CodexAvailability, CodexAvailabilityCreateResponse, CodexCreateAvailabilityInput, CodexCreateStorageRequestInput, CodexCreateStorageRequestResponse, CodexPurchase, CodexReservation, CodexSlot, CodexUpdateAvailabilityInput } from "./types"
|
||||||
|
|
||||||
|
export class Marketplace {
|
||||||
|
readonly url: string
|
||||||
|
|
||||||
|
constructor(url: string) {
|
||||||
|
this.url = url
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns active slots
|
||||||
|
*/
|
||||||
|
async activeSlots(): Promise<SafeValue<CodexSlot[]>> {
|
||||||
|
const url = this.url + Api.config.prefix + "/sales/slots"
|
||||||
|
|
||||||
|
return Fetch.safe<CodexSlot[]>(url, {
|
||||||
|
method: "GET"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns active slot with id {slotId} for the host
|
||||||
|
*/
|
||||||
|
async activeSlot(slotId: string): Promise<SafeValue<CodexSlot>> {
|
||||||
|
const url = this.url + Api.config.prefix + "/sales/slots/" + slotId
|
||||||
|
|
||||||
|
return Fetch.safe<CodexSlot>(url, {
|
||||||
|
method: "GET"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns storage that is for sale
|
||||||
|
*/
|
||||||
|
async availabilities(): Promise<SafeValue<CodexAvailability[]>> {
|
||||||
|
const url = this.url + Api.config.prefix + "/sales/availability"
|
||||||
|
|
||||||
|
return Fetch.safe<CodexAvailability[]>(url, {
|
||||||
|
method: "GET"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offers storage for sale
|
||||||
|
*/
|
||||||
|
async createAvailability(input: CodexCreateAvailabilityInput)
|
||||||
|
: Promise<SafeValue<CodexAvailabilityCreateResponse>> {
|
||||||
|
const cleaned = Value.Clean(CodexCreateAvailabilityInput, input)
|
||||||
|
|
||||||
|
if (!Value.Check(CodexCreateAvailabilityInput, cleaned)) {
|
||||||
|
const iterator = Value.Errors(CodexCreateAvailabilityInput, cleaned)
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
data: {
|
||||||
|
type: "validation",
|
||||||
|
message: "Cannot validate the input",
|
||||||
|
errors: CodexValidationErrors.map(iterator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = this.url + Api.config.prefix + "/sales/availability"
|
||||||
|
|
||||||
|
return Fetch.safe<CodexAvailabilityCreateResponse>(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify(cleaned)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new parameters will be only considered for new requests.
|
||||||
|
* Existing Requests linked to this Availability will continue as is.
|
||||||
|
*/
|
||||||
|
async updateAvailability(input: CodexUpdateAvailabilityInput): Promise<SafeValue<CodexAvailability>> {
|
||||||
|
const cleaned = Value.Clean(CodexUpdateAvailabilityInput, input)
|
||||||
|
|
||||||
|
if (!Value.Check(CodexUpdateAvailabilityInput, cleaned)) {
|
||||||
|
const iterator = Value.Errors(CodexUpdateAvailabilityInput, cleaned)
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
data: {
|
||||||
|
type: "validation",
|
||||||
|
message: "Cannot validate the input",
|
||||||
|
errors: CodexValidationErrors.map(iterator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = this.url + Api.config.prefix + "/sales/availability/" + cleaned.id
|
||||||
|
|
||||||
|
return Fetch.safe<CodexAvailability>(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify(cleaned)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return's list of Reservations for ongoing Storage Requests that the node hosts.
|
||||||
|
*/
|
||||||
|
async reservations(availabilityId: string): Promise<SafeValue<CodexReservation[]>> {
|
||||||
|
const url = this.url + Api.config.prefix + `/sales/availability/${availabilityId}/reservations`
|
||||||
|
|
||||||
|
return Fetch.safe<CodexReservation[]>(url, {
|
||||||
|
method: "GET"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of purchase IDs
|
||||||
|
*/
|
||||||
|
async purchaseIds(): Promise<SafeValue<string[]>> {
|
||||||
|
const url = this.url + Api.config.prefix + `/storage/purchases`
|
||||||
|
|
||||||
|
return Fetch.safe<string[]>(url, {
|
||||||
|
method: "GET"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns purchase details
|
||||||
|
*/
|
||||||
|
async purchaseDetail(purchaseId: string): Promise<SafeValue<CodexPurchase>> {
|
||||||
|
const url = this.url + Api.config.prefix + `/storage/purchases/` + purchaseId
|
||||||
|
|
||||||
|
return Fetch.safe<CodexPurchase>(url, {
|
||||||
|
method: "GET"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new request for storage.
|
||||||
|
*/
|
||||||
|
async createStorageRequest(input: CodexCreateStorageRequestInput): Promise<SafeValue<CodexCreateStorageRequestResponse>> {
|
||||||
|
const cleaned = Value.Clean(CodexCreateStorageRequestInput, input)
|
||||||
|
|
||||||
|
if (!Value.Check(CodexCreateStorageRequestInput, cleaned)) {
|
||||||
|
const iterator = Value.Errors(CodexCreateStorageRequestInput, cleaned)
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
data: {
|
||||||
|
type: "validation",
|
||||||
|
message: "Cannot validate the input",
|
||||||
|
errors: CodexValidationErrors.map(iterator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { cid, ...body } = cleaned
|
||||||
|
const url = this.url + Api.config.prefix + "/storage/request/" + cid
|
||||||
|
|
||||||
|
return Fetch.safe<CodexCreateStorageRequestResponse>(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
195
src/marketplace/types.ts
Normal file
195
src/marketplace/types.ts
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import { Static, Type } from "@sinclair/typebox"
|
||||||
|
|
||||||
|
export type CodexStorageRequest = {
|
||||||
|
id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address of Ethereum address
|
||||||
|
*/
|
||||||
|
client: string
|
||||||
|
|
||||||
|
ask: {
|
||||||
|
/**
|
||||||
|
* Number of slots that the tequest want to have the content spread over.
|
||||||
|
*/
|
||||||
|
slots: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amount of storage per slot (in bytes) as decimal string.
|
||||||
|
*/
|
||||||
|
slotSize: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The duration of the storage request in seconds.
|
||||||
|
*/
|
||||||
|
duration: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How often storage proofs are required as decimal string (in periods).
|
||||||
|
*/
|
||||||
|
proofProbability: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum amount of tokens paid per second per slot to hosts
|
||||||
|
* the client is willing to pay.
|
||||||
|
*/
|
||||||
|
reward: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max slots that can be lost without data considered to be lost.
|
||||||
|
*/
|
||||||
|
maxSlotLoss: number
|
||||||
|
},
|
||||||
|
|
||||||
|
content: {
|
||||||
|
/**
|
||||||
|
* Unique Content ID
|
||||||
|
*/
|
||||||
|
cid: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erasure code parameters
|
||||||
|
*/
|
||||||
|
erasure: {
|
||||||
|
/**
|
||||||
|
* Total number of chunks generated by the erasure code process.
|
||||||
|
*/
|
||||||
|
totalChunks: number
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters for Proof of Retrievability
|
||||||
|
*/
|
||||||
|
por: {
|
||||||
|
u: string
|
||||||
|
publicKey: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Number as decimal string that represents expiry threshold in seconds from when the Request is submitted.
|
||||||
|
* When the threshold is reached and the Request does not find requested amount of nodes to host the data, the Request is voided.
|
||||||
|
* The number of seconds can not be higher then the Request's duration itself.
|
||||||
|
*/
|
||||||
|
expiry: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Random data
|
||||||
|
*/
|
||||||
|
nonce: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A storage slot is a portion of a storage contract that needs to be fulfilled
|
||||||
|
* by a host. To initiate a contract, all the slots must be filled.
|
||||||
|
*/
|
||||||
|
export type CodexSlot = {
|
||||||
|
id: string
|
||||||
|
|
||||||
|
request: CodexStorageRequest,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The slot index as hexadecimal string
|
||||||
|
*/
|
||||||
|
slotIndex: "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage availability for sell.
|
||||||
|
*/
|
||||||
|
export type CodexAvailability = {
|
||||||
|
id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of available storage in bytes
|
||||||
|
*/
|
||||||
|
totalSize: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum time the storage should be sold for (in seconds)
|
||||||
|
*/
|
||||||
|
duration: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum price to be paid (in amount of tokens)
|
||||||
|
*/
|
||||||
|
minPrice: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum collateral user is willing to pay per filled Slot (in amount of tokens)
|
||||||
|
*/
|
||||||
|
maxCollateral: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CodexAvailabilityCreateResponse = CodexAvailability & {
|
||||||
|
id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unused size of availability's storage in bytes as decimal string
|
||||||
|
*/
|
||||||
|
freeSize: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CodexCreateAvailabilityInput = Type.Object({
|
||||||
|
totalSize: Type.Number({ minimum: 1 }),
|
||||||
|
duration: Type.Number({ minimum: 1 }),
|
||||||
|
minPrice: Type.Number(),
|
||||||
|
maxCollateral: Type.Number()
|
||||||
|
})
|
||||||
|
|
||||||
|
export type CodexCreateAvailabilityInput = Static<typeof CodexCreateAvailabilityInput>
|
||||||
|
|
||||||
|
export const CodexUpdateAvailabilityInput = Type.Object({
|
||||||
|
id: Type.String(),
|
||||||
|
totalSize: Type.Optional(Type.Number({ minimum: 1 })),
|
||||||
|
duration: Type.Optional(Type.Number({ minimum: 1 })),
|
||||||
|
minPrice: Type.Optional(Type.Number()),
|
||||||
|
maxCollateral: Type.Optional(Type.Number())
|
||||||
|
})
|
||||||
|
|
||||||
|
export type CodexUpdateAvailabilityInput = Static<typeof CodexUpdateAvailabilityInput>
|
||||||
|
|
||||||
|
export type CodexReservation = {
|
||||||
|
id: string
|
||||||
|
availabilityId: string
|
||||||
|
requestId: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size in bytes
|
||||||
|
*/
|
||||||
|
size: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slot Index as hexadecimal string
|
||||||
|
*/
|
||||||
|
slotIndex: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CodexPurchase = {
|
||||||
|
/**
|
||||||
|
* Description of the request's state
|
||||||
|
*/
|
||||||
|
state: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If request failed, then here is presented the error message
|
||||||
|
*/
|
||||||
|
error: string
|
||||||
|
|
||||||
|
request: CodexStorageRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CodexCreateStorageRequestInput = Type.Object({
|
||||||
|
cid: Type.String(),
|
||||||
|
duration: Type.Number({ minimum: 1 }),
|
||||||
|
reward: Type.Number(),
|
||||||
|
proofProbability: Type.Number(),
|
||||||
|
nodes: Type.Optional(Type.Number({ default: 1 })),
|
||||||
|
tolerance: Type.Optional(Type.Number({ default: 0 })),
|
||||||
|
expiry: Type.Number({ minimum: 1 }),
|
||||||
|
collateral: Type.Number()
|
||||||
|
})
|
||||||
|
|
||||||
|
export type CodexCreateStorageRequestInput = Static<typeof CodexCreateStorageRequestInput>
|
||||||
|
|
||||||
|
export type CodexCreateStorageRequestResponse = Omit<CodexCreateStorageRequestInput, "cid">
|
||||||
10
src/values/values.ts
Normal file
10
src/values/values.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { CodexError } from "../errors/errors";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SafeValue is a type used for error handling instead of throwing errors.
|
||||||
|
* It is inspired by Go's "error as value" concept.
|
||||||
|
* If the value represents an error, `error` is true and `data` will contain the error.
|
||||||
|
* If the value is not an error, `error` is false and `data` will contain the requested data.
|
||||||
|
*/
|
||||||
|
export type SafeValue<T> = { error: false, data: T } | { error: true, data: CodexError }
|
||||||
|
|
||||||
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/strictest/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"declaration": true,
|
||||||
|
"removeComments": false,
|
||||||
|
"lib": [
|
||||||
|
"ES2020",
|
||||||
|
"DOM",
|
||||||
|
],
|
||||||
|
"outDir": "./dist",
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user