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: 5 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [14, 16, 18]
node-version: [18, 20, 22]
name: Node.js v${{ matrix.node-version }}
env:
# Node 22.18+ strips types itself, which leaves the decorators in the test
# suite for V8 to choke on before ts-node is ever reached
NODE_OPTIONS: ${{ matrix.node-version >= 22 && '--no-experimental-strip-types' || '' }}
steps:
- name: Setup Node.js
uses: actions/setup-node@v2
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@wharfkit/contract",
"description": "ContractKit for Wharf",
"version": "1.1.5",
"version": "1.3.0",
"homepage": "https://github.com/wharfkit/contract",
"license": "BSD-3-Clause",
"main": "lib/contract.js",
Expand Down Expand Up @@ -62,5 +62,6 @@
"typedoc": "^0.24.6",
"typescript": "^4.9.5",
"yarn-deduplicate": "^6.0.2"
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
17 changes: 15 additions & 2 deletions src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ import {
import {PlaceholderAuth} from '@wharfkit/signing-request'

import {Table} from './contract/table'
import {formatExceptionMessage, TableScopeType} from './utils'

export interface ContractArgs {
abi: ABIDef
account: NameType
client: APIClient
}

export interface ContractOptions {
debug?: boolean
}

export interface ActionOptions {
authorization?: PermissionLevelType[]
}
Expand All @@ -45,13 +50,14 @@ export class Contract {
readonly abi: ABI
readonly account: Name
readonly client: APIClient
readonly debug: boolean = false

/**
* Constructs a new `Contract` instance.
*
* @param {ContractArgs} args - The required arguments for a contract.
*/
constructor(args: ContractArgs) {
constructor(args: ContractArgs, options: ContractOptions = {}) {
if (!args.abi) {
throw new Error('Contract requires an ABI')
}
Expand All @@ -64,6 +70,9 @@ export class Contract {
throw new Error('Contract requires an APIClient')
}
this.client = args.client
if (options.debug) {
this.debug = options.debug
}
}

public get tableNames(): string[] {
Expand All @@ -74,14 +83,15 @@ export class Contract {
return this.tableNames.includes(String(name))
}

public table<RowType>(name: NameType, scope?: NameType, rowType?): Table<RowType | any> {
public table<RowType>(name: NameType, scope?: TableScopeType, rowType?): Table<RowType | any> {
if (!this.hasTable(name)) {
throw new Error(`Contract (${this.account}) does not have a table named (${name})`)
}
return Table.from<RowType>({
abi: this.abi,
account: this.account,
client: this.client,
debug: this.debug,
defaultScope: scope,
name,
rowType,
Expand Down Expand Up @@ -133,6 +143,9 @@ export class Contract {
})
// Execute and retrieve response
const response = await this.client.v1.chain.send_read_only_transaction(transaction)
if (response.processed.except) {
throw new Error(formatExceptionMessage(response.processed.except))
}
// Decode and return results
const hexData = response.processed.action_traces[0].return_value_hex_data
const returnType = this.abi.action_results.find((a) => Name.from(a.name).equals(name))
Expand Down
23 changes: 3 additions & 20 deletions src/contract/row-cursor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {API, Serializer} from '@wharfkit/antelope'
import {wrapIndexValue} from '../utils'
import {TableCursor} from './table-cursor'

export class TableRowCursor<RowType = any> extends TableCursor {
Expand All @@ -15,26 +14,10 @@ export class TableRowCursor<RowType = any> extends TableCursor {
return []
}

// Set the lower_bound, and override if the cursor has a next_key value
let lower_bound = this.params.lower_bound
if (this.next_key) {
lower_bound = this.next_key
}

// Determine the maximum number of remaining rows for the cursor
const rowsRemaining = this.maxRows - this.rowsCount

// Find the lowest amount between rows remaining, rows per request, or the provided query params limit
const limit = Math.min(rowsRemaining, rowsPerAPIRequest, this.params.limit)

// Assemble and perform the v1/chain/get_table_rows query
const query = {
...this.params,
limit,
lower_bound: wrapIndexValue(lower_bound),
upper_bound: wrapIndexValue(this.params.upper_bound),
}
// Assemble the query params
const query = this.getTableRowsParams(rowsPerAPIRequest)

// Execute the query
const result = await this.client!.v1.chain.get_table_rows(query)

// Determine if we need to decode the rows, based on if:
Expand Down
34 changes: 34 additions & 0 deletions src/contract/table-cursor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {ABI, ABIDef, API, APIClient, Name} from '@wharfkit/antelope'
import {wrapIndexValue} from '../utils'

/** Mashup of valid types for an APIClient call to v1.chain.get_table_rows */
export type TableRowParamsTypes =
Expand Down Expand Up @@ -68,6 +69,13 @@ export abstract class TableCursor<RowType = any> {
this.type = table.type
}

/**
* Get the current next key value to use for the lower_bounds parameter in the next API request.
*/
get nextkey() {
return this.next_key
}

/**
* Implements the async iterator protocol for the cursor.
*
Expand Down Expand Up @@ -116,4 +124,30 @@ export abstract class TableCursor<RowType = any> {
}
return rows
}

/**
* Build the query for the get_table_rows API endpoint.
*/
getTableRowsParams(rowsPerAPIRequest: number = Number.MAX_SAFE_INTEGER): any {
// Set the lower_bound, and override if the cursor has a next_key value
let lower_bound = this.params.lower_bound
if (this.next_key) {
lower_bound = this.next_key
}

// Determine the maximum number of remaining rows for the cursor
const rowsRemaining = this.maxRows - this.rowsCount

// Find the lowest amount between rows remaining, rows per request, or the provided query params limit
const limit = Math.min(rowsRemaining, rowsPerAPIRequest, this.params.limit)

// Assemble and perform the v1/chain/get_table_rows query
const query = {
...this.params,
limit,
lower_bound: wrapIndexValue(lower_bound),
upper_bound: wrapIndexValue(this.params.upper_bound),
}
return query
}
}
50 changes: 35 additions & 15 deletions src/contract/table.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import {ABI, ABIDef, API, APIClient, Name, NameType, Serializer} from '@wharfkit/antelope'
import {indexPositionInWords, wrapIndexValue} from '../utils'
import {ABI, ABIDef, API, APIClient, Name, NameType, Serializer, UInt64} from '@wharfkit/antelope'
import {
indexPositionInWords,
isAbsentScope,
TableScopeType,
wrapIndexValue,
wrapScopeValue,
} from '../utils'
import {TableRowCursor} from './row-cursor'
import {TableScopeCursor} from './scope-cursor'
import {TableCursor} from './table-cursor'

export interface QueryParams {
index?: string
index_position?: string
scope?: NameType | number
scope?: TableScopeType
key_type?: keyof API.v1.TableIndexTypes
json?: boolean
from?: API.v1.TableIndexType | string | number
Expand All @@ -30,13 +36,14 @@ interface TableParams<TableRow = any> {
name: NameType
rowType?: TableRow
fieldToIndex?: FieldToIndex
debug?: boolean
defaultRowLimit?: number
defaultScope?: NameType
defaultScope?: TableScopeType
}

export interface GetTableRowsOptions {
limit?: number
scope?: NameType
scope?: TableScopeType
}

/**
Expand All @@ -52,10 +59,11 @@ export class Table<RowType = any> {
readonly name: Name
readonly rowType?: RowType
readonly tableABI: ABI.Table
readonly debug: boolean = false

private fieldToIndex?: any

public defaultScope?: NameType
public defaultScope?: TableScopeType
public defaultRowLimit = 1000

/**
Expand All @@ -82,6 +90,9 @@ export class Table<RowType = any> {
}
this.tableABI = tableABI
this.defaultScope = args.defaultScope
if (args.debug) {
this.debug = true
}
}

/**
Expand Down Expand Up @@ -112,12 +123,10 @@ export class Table<RowType = any> {
// Table query
table: this.name,
code: this.account,
scope:
params.scope !== undefined
? String(params.scope)
: this.defaultScope || this.account,
scope: this.resolveScope(params.scope),
// Response typing
type: this.rowType,
json: this.debug,
// Filtering
index_position: params.index_position,
key_type: params.key_type,
Expand Down Expand Up @@ -164,17 +173,14 @@ export class Table<RowType = any> {
const tableRowsParams: any = {
table: this.name,
code: this.account,
scope:
params.scope !== undefined
? String(params.scope)
: this.defaultScope || this.account,
scope: this.resolveScope(params.scope),
type: this.rowType!,
limit: 1,
lower_bound: wrapIndexValue(value),
upper_bound: wrapIndexValue(value),
index_position: params.index_position,
key_type: params.key_type,
json: false,
json: this.debug,
reverse: params.reverse,
}

Expand All @@ -199,6 +205,11 @@ export class Table<RowType = any> {
}
let [row] = rows

// Debug mode will return a JSON result, so just return it
if (this.debug) {
return row
}

if (!this.rowType) {
row = Serializer.decode({
data: row,
Expand Down Expand Up @@ -236,6 +247,15 @@ export class Table<RowType = any> {
return this.query(params).all()
}

/** Resolve the scope of a query, falling back to the table default and then the contract account. */
private resolveScope(scope?: TableScopeType): Name | UInt64 | string {
const value = isAbsentScope(scope) ? this.defaultScope : scope
if (isAbsentScope(value)) {
return this.account
}
return wrapScopeValue(value)
}

getFieldToIndex() {
if (this.fieldToIndex) {
return this.fieldToIndex
Expand Down
21 changes: 16 additions & 5 deletions src/kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ export interface ABIDefinition {
export interface ContractKitOptions {
abiCache?: ABICacheInterface
abis?: ABIDefinition[]
debug?: boolean
}

const defaultContractKitOptions: ContractKitOptions = {}

export class ContractKit {
readonly abiCache: ABICacheInterface
readonly client: APIClient
readonly debug: boolean = false

constructor(args: ContractKitArgs, options: ContractKitOptions = defaultContractKitOptions) {
if (args.client) {
Expand All @@ -43,6 +45,10 @@ export class ContractKit {
this.abiCache.setAbi(Name.from(name), ABI.from(abi))
)
}

if (options.debug) {
this.debug = options.debug
}
}

/**
Expand All @@ -54,10 +60,15 @@ export class ContractKit {
async load(contract: NameType): Promise<Contract> {
const account = Name.from(contract)
const abiDef = await this.abiCache.getAbi(account)
return new Contract({
abi: ABI.from(abiDef),
account,
client: this.client,
})
return new Contract(
{
abi: ABI.from(abiDef),
account,
client: this.client,
},
{
debug: this.debug,
}
)
}
}
Loading
Loading