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
35 changes: 32 additions & 3 deletions src/api/v1/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,38 @@ export class ChainAPI {
lower_bound,
},
});
let ram_payers: Name[] | undefined;

if (params.show_payer) {
let ram_payers: (Name | undefined)[] | undefined;

// Wire-sysio unified get_table_rows returns KV-backed rows as
// {key: {scope, primary_key, ...}, value: <decoded>, payer?: <name>}
// instead of the legacy shape (decoded struct directly, or
// {data, payer} when show_payer is set). Detect by shape: each row
// must be an object whose `key` is itself an object (the wire-sysio
// key is always composite scope+primary_key) and also has a `value`
// field. Requiring `key` to be an object avoids misinterpreting user
// tables that happen to have scalar fields named `key` and `value`.
const isWireKvShape =
Array.isArray(rows) &&
rows.length > 0 &&
typeof rows[0] === 'object' &&
rows[0] !== null &&
'key' in rows[0] &&
'value' in rows[0] &&
typeof rows[0].key === 'object' &&
rows[0].key !== null;

if (isWireKvShape) {
if (params.show_payer) {
ram_payers = [];
rows = rows.map((row: {value: any; payer?: string}) => {
ram_payers!.push(row.payer ? Name.from(row.payer) : undefined);
return row.value;
});
} else {
rows = rows.map((row: {value: any}) => row.value);
}
} else if (params.show_payer) {
// Legacy show_payer wrapper: {data, payer}
ram_payers = [];
rows = rows.map(({data, payer}) => {
ram_payers!.push(Name.from(payer));
Expand Down
7 changes: 6 additions & 1 deletion src/api/v1/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,12 @@ export interface GetTableRowsParamsTyped<Index = TableIndexType | string, Row =
export interface GetTableRowsResponse<Index = TableIndexType, Row = any> {
rows: Row[];
more: boolean;
ram_payers?: Name[];
/**
* Populated when `show_payer` is set. Each entry aligns with `rows[i]`.
* The entry is `undefined` when the chain returned a row without a payer
* (wire-sysio KV shape makes `payer` optional).
*/
ram_payers?: (Name | undefined)[];
next_key?: Index;
}

Expand Down
88 changes: 84 additions & 4 deletions src/chain/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ export class ABI implements ABISerializableObject {
const numTables = decoder.readVaruint32();

for (let i = 0; i < numTables; i++) {
const name = Name.fromABI(decoder);
// name is a length-prefixed string (widened from sysio::name uint64);
// table_id (uint16) and secondary_indexes (vector<index_def>) follow.
// Binary order matches sysio::chain::table_def.
const name = decoder.readString();
const index_type = decoder.readString();
const key_names: string[] = [];
const numKeyNames = decoder.readVaruint32();
Expand All @@ -116,7 +119,33 @@ export class ABI implements ABISerializableObject {
}

const type = decoder.readString();
tables.push({name, index_type, key_names, key_types, type});
const tidLo = decoder.readByte();
const tidHi = decoder.readByte();
const table_id = tidLo | (tidHi << 8);
const secondary_indexes: ABI.Index[] = [];
const numIndexes = decoder.readVaruint32();

for (let j = 0; j < numIndexes; j++) {
const idxName = decoder.readString();
const idxKeyType = decoder.readString();
const idxLo = decoder.readByte();
const idxHi = decoder.readByte();
secondary_indexes.push({
name: idxName,
key_type: idxKeyType,
table_id: idxLo | (idxHi << 8),
});
}

tables.push({
name,
index_type,
key_names,
key_types,
type,
table_id,
secondary_indexes,
});
}

const ricardian_clauses: ABI.Clause[] = [];
Expand Down Expand Up @@ -175,6 +204,14 @@ export class ABI implements ABISerializableObject {
}
}

// protobuf_types and any further string-typed trailing extensions:
// drain them so a later-appended extension doesn't cause a decode
// error. The SDK does not consume these fields. Non-string extensions
// added in the future will need explicit handling here.
while (decoder.canRead()) {
decoder.readString();
}

return new ABI({
version,
types,
Expand Down Expand Up @@ -220,7 +257,7 @@ export class ABI implements ABISerializableObject {
encoder.writeVaruint32(this.tables.length);

for (const table of this.tables) {
Name.from(table.name).toABI(encoder);
encoder.writeString(table.name);
encoder.writeString(table.index_type);
encoder.writeVaruint32(table.key_names.length);

Expand All @@ -235,6 +272,23 @@ export class ABI implements ABISerializableObject {
}

encoder.writeString(table.type);
// table_id is uint16 LE; chain side computes DJB2(name) % 65536.
// Default to 0 for hand-built tables that omit it.
const tid = table.table_id ?? 0;
ABI.assertUint16(tid, `table ${table.name} table_id`);
encoder.writeByte(tid & 0xff);
encoder.writeByte((tid >> 8) & 0xff);
const secIdx = table.secondary_indexes ?? [];
encoder.writeVaruint32(secIdx.length);

for (const idx of secIdx) {
encoder.writeString(idx.name);
encoder.writeString(idx.key_type);
const idxTid = idx.table_id ?? 0;
ABI.assertUint16(idxTid, `index ${idx.name} table_id`);
encoder.writeByte(idxTid & 0xff);
encoder.writeByte((idxTid >> 8) & 0xff);
}
}

encoder.writeVaruint32(this.ricardian_clauses.length);
Expand Down Expand Up @@ -263,6 +317,18 @@ export class ABI implements ABISerializableObject {
Name.from(result.name).toABI(encoder);
encoder.writeString(result.result_type);
}

// protobuf_types: forward-compat extension; always written as an empty
// string for symmetry with the parser.
encoder.writeString('');
}

private static assertUint16(value: number, label: string) {
if (!Number.isInteger(value) || value < 0 || value > 0xffff) {
throw new Error(
`ABI ${label} must be a uint16 in [0, 65535], got ${value}`
);
}
}

resolveType(name: string): ABI.ResolvedType {
Expand Down Expand Up @@ -398,12 +464,26 @@ export namespace ABI {
type: string;
ricardian_contract: string;
}
// Per-secondary-index metadata embedded in Table. Mirrors
// sysio::chain::index_def. table_id is a uint16 DJB2(name) % 65536.
export interface Index {
name: string;
key_type: string;
table_id?: number;
}
// table_def.name is a free-form string (was sysio::name uint64 pre-wire),
// so names > 12 chars / containing arbitrary characters are valid.
// table_id (uint16) = DJB2(name) % 65536 and secondary_indexes provide
// per-table namespace isolation for KV-backed tables. This SDK only
// supports the wire-sysio binary format.
export interface Table {
name: NameType;
name: string;
index_type: string;
key_names: string[];
key_types: string[];
type: string;
table_id?: number;
secondary_indexes?: Index[];
}
export interface Clause {
id: string;
Expand Down
Loading