diff --git a/package.json b/package.json index 532f3b0..86be3bd 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "@typescript-eslint/parser": "^5.20.0", "@wharfkit/mock-data": "^1.0.0-beta11", "@wharfkit/session": "^1.0.0-beta4", - "assert": "^2.0.0", "chai": "^4.3.4", "eslint": "^8.13.0", "eslint-config-prettier": "^8.1.0", diff --git a/scripts/codegen-cli.js b/scripts/codegen-cli.js index 39db085..81452d0 100755 --- a/scripts/codegen-cli.js +++ b/scripts/codegen-cli.js @@ -4,7 +4,7 @@ const {APIClient} = require('@wharfkit/session') const fs = require('fs') const {fetch} = require('node-fetch') -const {codegen, Contract} = require('../lib/contract.js') +const {codegen, ContractKit} = require('../lib/contract.js') const client = new APIClient({ url: process.env.ANTELOPE_NODE_URL || 'https://eos.greymass.com', @@ -23,31 +23,28 @@ async function codegenCli() { log(`Fetching ABI for ${contractName}...`) - const contract = Contract.from({name: contractName, client}) + const contractKit = new ContractKit({ + client, + }) + const contract = await contractKit.load(contractName) - const abi = await contract.getAbi() + log(`Generating Contract helper for ${contractName}...`) - if (!abi) { - log(`No ABI found for ${contractName}`) - } else { - log(`Generating Contract helper for ${contractName}...`) - - const generatedCode = await codegen(contractName, abi) + const generatedCode = await codegen(contractName, contract.abi) - log(`Generated Contract helper class for ${contractName}...`) + log(`Generated Contract helper class for ${contractName}...`) - // Check if --file is present in the command line arguments - const fileFlagIndex = process.argv.indexOf('--file') - if (fileFlagIndex !== -1 && process.argv[fileFlagIndex + 1]) { - const contractFilePath = process.argv[fileFlagIndex + 1] + // Check if --file is present in the command line arguments + const fileFlagIndex = process.argv.indexOf('--file') + if (fileFlagIndex !== -1 && process.argv[fileFlagIndex + 1]) { + const contractFilePath = process.argv[fileFlagIndex + 1] - fs.writeFileSync(contractFilePath, generatedCode) + fs.writeFileSync(contractFilePath, generatedCode) - log(`Generated Contract helper for ${contractName} saved to ${contractFilePath}`) - } else { - log(`Generated Contract helper class:`) - log(generatedCode) - } + log(`Generated Contract helper for ${contractName} saved to ${contractFilePath}`) + } else { + log(`Generated Contract helper class:`) + log(generatedCode) } } diff --git a/src/codegen.ts b/src/codegen.ts index c47e251..810a1f7 100644 --- a/src/codegen.ts +++ b/src/codegen.ts @@ -1,4 +1,3 @@ -import {ABI} from '@greymass/eosio' import * as ts from 'typescript' import { @@ -10,8 +9,7 @@ import { getFieldTypesFromAbi, } from './codegen/helpers' import {generateNamespace, generateNamespaceName} from './codegen/namespace' -import {generateActions} from './codegen/actions' -import {generateTableClass} from './codegen/table' +import {generateContractClass} from './codegen/contract' const printer = ts.createPrinter() @@ -34,29 +32,8 @@ export async function codegen(contractName, abi) { '@wharfkit/contract' ) - const {methods: actionMethods, interfaces: actionsInterfaces} = generateActions( - contractName, - namespaceName, - ABI.from(abi) - ) - - // Generate actions namespace - const actionsNamespace = generateNamespace(namespaceName, [ - generateNamespace('actions', actionMethods), - ]) - - const tableClasses: ts.ClassDeclaration[] = [] - const tableInterfaces: ts.InterfaceDeclaration[] = [] - - for (const table of abi.tables) { - const {classDeclaration} = await generateTableClass(contractName, namespaceName, table, abi) - tableClasses.push(classDeclaration) - } - - // Generate tables namespace - const tableNamespace = generateNamespace(namespaceName, [ - generateNamespace('tables', tableClasses), - ]) + + const {classDeclaration} = await generateContractClass(namespaceName, contractName, abi) // Extract fields from the ABI const structs = getFieldTypesFromAbi(abi) @@ -76,20 +53,15 @@ export async function codegen(contractName, abi) { // Generate types namespace const typesDeclaration = generateNamespace(namespaceName, [ - generateNamespace('types', [ - ...actionsInterfaces, - ...tableInterfaces, - ...structDeclarations, - ]), + generateNamespace('types', structDeclarations), ]) const sourceFile = ts.factory.createSourceFile( [ importContractStatement, importCoreStatement, - actionsNamespace, - tableNamespace, typesDeclaration, + classDeclaration, ], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None diff --git a/src/codegen/actions.ts b/src/codegen/actions.ts deleted file mode 100644 index f1896dd..0000000 --- a/src/codegen/actions.ts +++ /dev/null @@ -1,213 +0,0 @@ -import {ABI} from '@greymass/eosio' -import assert from 'assert' -import * as ts from 'typescript' - -import { - findExternalType, - generateClassDeclaration, - generateInterface, - generatePropertyDeclarationForField, -} from './helpers' -import {capitalize, pascalCase} from '../utils' - -export function generateActions(contractName: string, namespaceName: string, abi: ABI) { - const structs: Map = new Map() - const coreImports: Set = new Set() - - const resolved = abi.resolveAll() - - function getTypeIdentifier(type: ABI.ResolvedType) { - if (type.fields) { - return getStructDeclaration(type).name! - } - return ts.factory.createIdentifier(type.name) - } - - function getStructDeclaration(type: ABI.ResolvedType) { - const name = pascalCase(type.name) - assert(type.fields, 'struct type must have fields') - if (structs.has(name)) { - return structs.get(name)! - } - let parentClass: ts.Identifier - if (type.base) { - parentClass = getTypeIdentifier(type.base) - } else { - coreImports.add('Struct') - parentClass = ts.factory.createIdentifier('Struct') - } - - const members = type.fields!.map((field) => { - return generatePropertyDeclarationForField(field.name, field.type) - }) - const structDecorator = ts.factory.createDecorator( - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('Struct'), - ts.factory.createIdentifier('type') - ), - undefined, // type arguments - [ts.factory.createStringLiteral(type.name)] - ) - ) - const structClass = generateClassDeclaration(name, members, { - export: true, - parent: parentClass.text, - decorator: structDecorator, - }) - structs.set(name, structClass) - return structClass - } - - for (let abiType of resolved.structs) { - while (abiType.ref) { - abiType = abiType.ref - } - const members = abiType.allFields!.map((field) => { - return generatePropertyDeclarationForField(field.name, field.type) - }) - const structDecorator = ts.factory.createDecorator( - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('Struct'), - ts.factory.createIdentifier('type') - ), - undefined, // type arguments - [ts.factory.createStringLiteral(abiType.name)] - ) - ) - const structClass = generateClassDeclaration(abiType.name, members, { - export: true, - decorator: structDecorator, - }) - structs.set(abiType.name, structClass) - } - - const members: ts.FunctionDeclaration[] = [] - const interfaces: ts.InterfaceDeclaration[] = [] - - // add a method for each action - for (const action of abi.actions) { - const actionStruct = resolved.structs.find((struct) => - struct.name.includes(String(action.name)) - ) - if (!actionStruct) { - throw Error(`Action Struct not found for ${action.name}`) - } - const actionName = String(action.name) - const actionNameIdentifier = ts.factory.createIdentifier(actionName) - const fields = actionStruct.fields || [] - - const interfaceMembers = fields.map((field) => - ts.factory.createPropertySignature( - undefined, - field.name, - undefined, - ts.factory.createTypeReferenceNode(findExternalType(field.type.name, abi)) - ) - ) - - interfaces.push( - generateInterface(`${capitalize(actionName)}Params`, true, interfaceMembers) - ) - - const actionMethod = ts.factory.createFunctionDeclaration( - undefined, - [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], - undefined, - actionNameIdentifier, // name - undefined, // question token - [ - ts.factory.createParameterDeclaration( - undefined, // decorators - undefined, // dot dot dot token - ts.factory.createIdentifier(`${actionName}Params`), // name - undefined, // question token - ts.factory.createTypeReferenceNode( - `${namespaceName}.types.${capitalize(actionName)}Params` - ), // type - undefined // initializer - ), - ts.factory.createParameterDeclaration( - undefined, // decorators - undefined, // dot dot dot token - ts.factory.createIdentifier('session'), // name - undefined, // question token - ts.factory.createTypeReferenceNode('Session'), // type - undefined // initializer - ), - ], - ts.factory.createTypeReferenceNode('Promise', [ - ts.factory.createTypeReferenceNode('TransactResult'), - ]), // type - ts.factory.createBlock( - [ - ts.factory.createVariableStatement( - undefined, - ts.factory.createVariableDeclarationList( - [ - ts.factory.createVariableDeclaration( - 'contract', - undefined, - undefined, - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('Contract'), - ts.factory.createIdentifier('from') - ), - undefined, - [ - ts.factory.createObjectLiteralExpression( - [ - ts.factory.createPropertyAssignment( - 'name', - ts.factory.createStringLiteral(contractName) - ), - ], - false - ), - ] - ) - ), - ], - ts.NodeFlags.Const - ) - ), - ts.factory.createReturnStatement( - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('contract'), - ts.factory.createIdentifier('call') - ), - undefined, - [ - ts.factory.createStringLiteral(String(action.name)), - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(namespaceName), - ts.factory.createIdentifier('types') - ), - ts.factory.createIdentifier( - capitalize(actionStruct?.name) - ) - ), - ts.factory.createIdentifier('from') - ), - undefined, - [ts.factory.createIdentifier(`${actionName}Params`)] - ), - ts.factory.createIdentifier('session'), - ] - ) - ), - ], - true - ) - ) - members.push(actionMethod) - } - - return {methods: members, interfaces} -} diff --git a/src/codegen/table.ts b/src/codegen/contract.ts similarity index 84% rename from src/codegen/table.ts rename to src/codegen/contract.ts index fc15855..1c28d9b 100644 --- a/src/codegen/table.ts +++ b/src/codegen/contract.ts @@ -5,50 +5,19 @@ import {generateClassDeclaration} from './helpers' import {capitalize} from '../utils' import {APIClient} from '@greymass/eosio' -export async function generateTableClass(contractName, namespaceName, table, abi) { - const tableName = table.name - const struct = abi.structs.find((struct) => struct.name === table.type) +export async function generateContractClass(namespaceName, contractName, abi) { const members: ts.ClassElement[] = [] - const rowType = `${namespaceName}.types.${capitalize(struct.name)}` - - const tableInstance = Table.from({ - name: tableName, - contract: Contract.from({ - account: contractName, - abi, - // TODO: Remove the need for a table instance to need the full contract. - // The line below was added because `client` is now required for a contract - // It's not a good solution here, but it's a quick fix for now - client: new APIClient({url: 'https://jungle4.greymass.com'}), - }), - }) - const fieldToIndexMapping = tableInstance.getFieldToIndex() - + const abiHex = abi.toHex() + // Define fieldToIndex static property const fieldToIndex = ts.factory.createPropertyDeclaration( undefined, [ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)], - 'fieldToIndex', + 'abi', undefined, undefined, - ts.factory.createObjectLiteralExpression( - Object.keys(fieldToIndexMapping).map((keyName) => { - return ts.factory.createPropertyAssignment( - keyName, - ts.factory.createObjectLiteralExpression([ - ts.factory.createPropertyAssignment( - 'type', - ts.factory.createStringLiteral(fieldToIndexMapping[keyName].type) - ), - ts.factory.createPropertyAssignment( - 'index_position', - ts.factory.createStringLiteral( - fieldToIndexMapping[keyName].index_position - ) - ), - ]) - ) - }) + ts.factory.createStringLiteral( + abiHex ) ) members.push(fieldToIndex) diff --git a/src/index-module.ts b/src/index-module.ts index 931424e..5630953 100644 --- a/src/index-module.ts +++ b/src/index-module.ts @@ -1,4 +1,5 @@ export * from './contract' export * from './contract/table' export * from './contract/table-cursor' +export * from './codegen' export * from './kit' diff --git a/test/codegen-samples/rewards.gm.ts b/test/codegen-samples/rewards.gm.ts deleted file mode 100644 index 10faa5e..0000000 --- a/test/codegen-samples/rewards.gm.ts +++ /dev/null @@ -1,273 +0,0 @@ -import {Contract, GetTableRowsOptions, Table, TableCursor} from '../../src/index' -import { - APIClient, - Asset, - AssetType, - Float64, - Name, - NameType, - Session, - Struct, - TimePoint, - TransactResult, - UInt16, - UInt16Type, -} from '@wharfkit/session' -export class _RewardsGm extends Contract { - adduser( - adduserParams: _RewardsGm.types.AdduserParams, - session: Session - ): Promise { - return this.call('adduser', _RewardsGm.types.Adduser.from(adduserParams), session) - } - claim(claimParams: _RewardsGm.types.ClaimParams, session: Session): Promise { - return this.call('claim', _RewardsGm.types.Claim.from(claimParams), session) - } - configure( - configureParams: _RewardsGm.types.ConfigureParams, - session: Session - ): Promise { - return this.call('configure', _RewardsGm.types.Configure.from(configureParams), session) - } - deluser( - deluserParams: _RewardsGm.types.DeluserParams, - session: Session - ): Promise { - return this.call('deluser', _RewardsGm.types.Deluser.from(deluserParams), session) - } - receipt( - receiptParams: _RewardsGm.types.ReceiptParams, - session: Session - ): Promise { - return this.call('receipt', _RewardsGm.types.Receipt.from(receiptParams), session) - } - updateuser( - updateuserParams: _RewardsGm.types.UpdateuserParams, - session: Session - ): Promise { - return this.call('updateuser', _RewardsGm.types.Updateuser.from(updateuserParams), session) - } -} -export namespace _RewardsGm { - export namespace tables { - export class config { - static fieldToIndex = {} - static query( - queryParams: _RewardsGm.types.ConfigQueryParams, - getTableRowsOptions: GetTableRowsOptions, - client: APIClient - ): TableCursor<_RewardsGm.types.Config> { - const configTable = Table.from({ - contract: Contract.from({name: 'rewards.gm', client: client}), - name: 'config', - rowType: _RewardsGm.types.Config, - fieldToIndex: config.fieldToIndex, - }) - return configTable.query(queryParams, getTableRowsOptions) - } - static get( - queryParams: _RewardsGm.types.ConfigFindQueryParams, - client: APIClient - ): Promise<_RewardsGm.types.Config> { - const configTable = Table.from({ - contract: Contract.from({name: 'rewards.gm', client: client}), - name: 'config', - rowType: _RewardsGm.types.Config, - fieldToIndex: config.fieldToIndex, - }) - return configTable.get(queryParams) - } - static first(limit: number, client: APIClient): TableCursor<_RewardsGm.types.Config> { - const configTable = Table.from({ - contract: Contract.from({name: 'rewards.gm', client: client}), - name: 'config', - rowType: _RewardsGm.types.Config, - fieldToIndex: config.fieldToIndex, - }) - return configTable.first(limit) - } - } - export class users { - static fieldToIndex = {} - static query( - queryParams: _RewardsGm.types.UsersQueryParams, - getTableRowsOptions: GetTableRowsOptions, - client: APIClient - ): TableCursor<_RewardsGm.types.User_row> { - const usersTable = Table.from({ - contract: Contract.from({name: 'rewards.gm', client: client}), - name: 'users', - rowType: _RewardsGm.types.User_row, - fieldToIndex: users.fieldToIndex, - }) - return usersTable.query(queryParams, getTableRowsOptions) - } - static get( - queryParams: _RewardsGm.types.UsersFindQueryParams, - client: APIClient - ): Promise<_RewardsGm.types.User_row> { - const usersTable = Table.from({ - contract: Contract.from({name: 'rewards.gm', client: client}), - name: 'users', - rowType: _RewardsGm.types.User_row, - fieldToIndex: users.fieldToIndex, - }) - return usersTable.get(queryParams) - } - static first(limit: number, client: APIClient): TableCursor<_RewardsGm.types.User_row> { - const usersTable = Table.from({ - contract: Contract.from({name: 'rewards.gm', client: client}), - name: 'users', - rowType: _RewardsGm.types.User_row, - fieldToIndex: users.fieldToIndex, - }) - return usersTable.first(limit) - } - } - } -} -export namespace _RewardsGm { - export namespace types { - export interface AdduserParams { - account: NameType - weight: UInt16Type - } - export interface ClaimParams { - account: NameType - amount: AssetType - } - export interface ConfigureParams { - token_symbol: symbol - oracle_account: NameType - oracle_pairs: Oracle_pair - } - export interface DeluserParams { - account: NameType - } - export interface ReceiptParams { - account: NameType - amount: AssetType - ticker: Price_info - } - export interface UpdateuserParams { - account: NameType - weight: UInt16Type - } - export interface ConfigQueryParams { - token_symbol?: { - from: symbol - to: symbol - } - oracle_account?: { - from: NameType - to: NameType - } - oracle_pairs?: { - from: Oracle_pair - to: Oracle_pair - } - } - export interface ConfigFindQueryParams { - token_symbol?: symbol - oracle_account?: NameType - oracle_pairs?: Oracle_pair - } - export interface UsersQueryParams { - account?: { - from: NameType - to: NameType - } - weight?: { - from: UInt16Type - to: UInt16Type - } - balance?: { - from: AssetType - to: AssetType - } - } - export interface UsersFindQueryParams { - account?: NameType - weight?: UInt16Type - balance?: AssetType - } - @Struct.type('adduser') - export class Adduser extends Struct { - @Struct.field('account') - declare account: Name - @Struct.field('weight') - declare weight: UInt16 - } - @Struct.type('claim') - export class Claim extends Struct { - @Struct.field('account') - declare account: Name - @Struct.field('amount') - declare amount: Asset - } - @Struct.type('config') - export class Config extends Struct { - @Struct.field('token_symbol') - declare token_symbol: symbol - @Struct.field('oracle_account') - declare oracle_account: Name - @Struct.field('oracle_pairs') - declare oracle_pairs: _RewardsGm.types.Oracle_pair - } - @Struct.type('configure') - export class Configure extends Struct { - @Struct.field('token_symbol') - declare token_symbol: symbol - @Struct.field('oracle_account') - declare oracle_account: Name - @Struct.field('oracle_pairs') - declare oracle_pairs: _RewardsGm.types.Oracle_pair - } - @Struct.type('deluser') - export class Deluser extends Struct { - @Struct.field('account') - declare account: Name - } - @Struct.type('oracle_pair') - export class Oracle_pair extends Struct { - @Struct.field('name') - declare name: Name - @Struct.field('precision') - declare precision: UInt16 - } - @Struct.type('price_info') - export class Price_info extends Struct { - @Struct.field('pair') - declare pair: string - @Struct.field('price') - declare price: Float64 - @Struct.field('timestamp') - declare timestamp: TimePoint - } - @Struct.type('receipt') - export class Receipt extends Struct { - @Struct.field('account') - declare account: Name - @Struct.field('amount') - declare amount: Asset - @Struct.field('ticker') - declare ticker: _RewardsGm.types.Price_info - } - @Struct.type('updateuser') - export class Updateuser extends Struct { - @Struct.field('account') - declare account: Name - @Struct.field('weight') - declare weight: UInt16 - } - @Struct.type('user_row') - export class User_row extends Struct { - @Struct.field('account') - declare account: Name - @Struct.field('weight') - declare weight: UInt16 - @Struct.field('balance') - declare balance: Asset - } - } -}