Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 2.3.84

### Patch Changes

- feat(sdk): price a comment's RC cost the way the chain does (#1486)

## 2.3.83

### Patch Changes
Expand Down
174 changes: 173 additions & 1 deletion packages/sdk/dist/browser/index.d.ts

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions packages/sdk/dist/browser/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/sdk/dist/browser/index.js.map

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions packages/sdk/dist/node/index.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/sdk/dist/node/index.cjs.map

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions packages/sdk/dist/node/index.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/sdk/dist/node/index.mjs.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@ecency/sdk",
"private": false,
"version": "2.3.83",
"version": "2.3.84",
"description": "Ecency SDK",
"repository": {
"type": "git",
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/modules/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from "./queries";
export * from "./query-keys";
export * from "./types";
export * from "./utils";
export * from "./utf8";
1 change: 1 addition & 0 deletions packages/sdk/src/modules/core/query-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ export const QueryKeys = {
account: (username: string) =>
["resource-credits", "account", username],
stats: () => ["resource-credits", "stats"],
resourceParams: () => ["resource-credits", "resource-params"],
},

// ===========================================================================
Expand Down
42 changes: 42 additions & 0 deletions packages/sdk/src/modules/core/utf8.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* UTF-8 byte length of a string.
*
* `TextEncoder` is missing on some runtimes the SDK ships to (React Native /
* Hermes), and `String.length` is NOT a substitute: it counts UTF-16 code
* units, so anything non-ASCII is undercounted. Where that number feeds an RC
* estimate, undercounting means telling someone a post is affordable when the
* chain will reject it.
*/
export function utf8ByteLength(value: string): number {
if (typeof TextEncoder !== "undefined") {
return new TextEncoder().encode(value).length;
}

let bytes = 0;
for (let i = 0; i < value.length; i++) {
const c = value.charCodeAt(i);
if (c < 0x80) {
bytes += 1;
} else if (c < 0x800) {
bytes += 2;
} else if (c >= 0xd800 && c <= 0xdbff && i + 1 < value.length) {
// surrogate pair encodes as four bytes
i++;
bytes += 4;
} else {
bytes += 3;
}
}
return bytes;
}

/** Byte length of Hive's unsigned LEB128 varint for `value`. */
export function varintByteLength(value: number): number {
let count = 0;
let remaining = value;
do {
count++;
remaining >>>= 7;
} while (remaining > 0);
return count;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import { getRcResourceParamsQueryOptions } from "./get-rc-resource-params-query-options";
import { QueryKeys } from "@/modules/core";

describe("getRcResourceParamsQueryOptions", () => {
const options = getRcResourceParamsQueryOptions();

it("uses the shared query key rather than a literal", () => {
expect(options.queryKey).toEqual(QueryKeys.resourceCredits.resourceParams());
});

/**
* Regression: gcTime and staleTime were both Infinity. Infinite gcTime is
* correct, it is the one value that schedules no timer and so cannot hold a
* request's cache open on the server. Infinite staleTime is not: a
* long-lived session would keep pricing RC with pre-hardfork coefficients
* indefinitely, and a wrong estimate here tells someone a post is affordable
* when the chain will reject it.
*/
it("schedules no gc timer, so it cannot pin a server request's cache", () => {
expect(options.gcTime).toBe(Infinity);
});

it("keeps a bounded staleTime so hardfork changes are picked up", () => {
expect(Number.isFinite(options.staleTime)).toBe(true);
expect(options.staleTime).toBeGreaterThan(0);
});

it("does not refetch so often that it is chatty", () => {
// Params change at a hardfork, so a day is the intent, not minutes.
expect(options.staleTime).toBeGreaterThanOrEqual(60 * 60 * 1000);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { queryOptions } from "@tanstack/react-query";
import { callRPC } from "@/modules/core/hive-tx";
import { QueryKeys } from "@/modules/core";
import type { RcResourceParams } from "../types/resource-params";

/**
* Curve coefficients and sizing constants used to price resource usage.
*
* These only change at a hardfork, so the entry is kept for the session:
* `gcTime: Infinity` is the one value that schedules no gc timer at all, so it
* does not hold a request's query cache open on the server the way a long
* finite window would.
*
* `staleTime` stays bounded on purpose. Making it infinite too would mean a
* long-lived session keeps pricing with pre-hardfork coefficients forever,
* quietly producing wrong RC estimates with no way to recover short of a
* reload. A day is long enough that this is effectively never refetched, and
* short enough that a hardfork corrects itself.
*/
export function getRcResourceParamsQueryOptions() {
return queryOptions({
queryKey: QueryKeys.resourceCredits.resourceParams(),
staleTime: 24 * 60 * 60 * 1000,
gcTime: Infinity,
queryFn: async () => (await callRPC("rc_api.get_resource_params", {})) as RcResourceParams
});
}
1 change: 1 addition & 0 deletions packages/sdk/src/modules/resource-credits/queries/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./get-rc-stats-query-options";
export * from "./get-account-rc-query-options";
export * from "./get-rc-resource-params-query-options";
1 change: 1 addition & 0 deletions packages/sdk/src/modules/resource-credits/types/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./stats";
export * from "./resource-params";
65 changes: 65 additions & 0 deletions packages/sdk/src/modules/resource-credits/types/resource-params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/** Shape returned by `rc_api.get_resource_params`. Numbers arrive as strings. */
export interface RcPriceCurveParams {
coeff_a: string | number;
coeff_b: string | number;
shift: string | number;
}

export interface RcResourceDynamicsParams {
resource_unit: string | number;
budget_per_time_unit: string | number;
pool_eq: string | number;
max_pool_size: string | number;
}

export interface RcResourceParamEntry {
resource_dynamics_params: RcResourceDynamicsParams;
price_curve_params: RcPriceCurveParams;
}

/**
* Per-operation and per-transaction sizing constants. Only the members this
* module needs are declared; the node returns many more.
*/
export interface RcSizeInfo {
resource_state_bytes: {
comment_base_size: number;
comment_permlink_char_size: number;
comment_beneficiaries_member_size: number;
transaction_base_size: number;
[key: string]: number;
};
resource_execution_time: {
comment_time: number;
comment_options_time: number;
transaction_time: number;
verify_authority_time: number;
[key: string]: number;
};
[key: string]: Record<string, number>;
}

export interface RcResourceParams {
resource_params: Record<string, RcResourceParamEntry>;
size_info: RcSizeInfo;
}

/**
* Resource order is consensus-defined (`HIVE_RC_NUM_RESOURCE_TYPES`) and the
* `pool`, `share` and `budget` arrays in rc_stats are indexed by it.
*/
export const RC_RESOURCE_NAMES = [
"resource_history_bytes",
"resource_new_accounts",
"resource_market_bytes",
"resource_state_bytes",
"resource_execution_time"
] as const;

export type RcResourceName = (typeof RC_RESOURCE_NAMES)[number];

export interface RcCostBreakdown {
resource: RcResourceName;
usage: number;
cost: number;
}
Loading