-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The `Contract` now requires an ABI, Account, and Client The `call` method is currently not implemented.
- Loading branch information
Showing
5 changed files
with
116 additions
and
214 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,148 +1,87 @@ | ||
import { | ||
ABI, | ||
ABISerializableObject, | ||
Action, | ||
Name, | ||
NameType, | ||
Session, | ||
TransactResult, | ||
} from '@wharfkit/session' | ||
import type {APIClient} from '@wharfkit/session' | ||
import {ABI, ABIDef, APIClient, Name, NameType, Session} from '@wharfkit/session' | ||
|
||
import {Table} from './contract/table' | ||
|
||
export interface ContractArgs { | ||
abi: ABIDef | ||
account: NameType | ||
client: APIClient | ||
} | ||
|
||
export interface ContractOptions { | ||
name: NameType | ||
client?: APIClient | ||
abi?: ABI.Def | ||
session?: Session | ||
} | ||
|
||
/** | ||
* Represents a smart contract in the blockchain. | ||
* Represents a smart contract deployed to a specific blockchain. | ||
* Provides methods for interacting with the contract such as | ||
* calling actions, reading tables, and getting the ABI of the contract. | ||
*/ | ||
export class Contract { | ||
private static _shared: Contract | null = null | ||
private static account: Name | ||
|
||
private abi?: ABI.Def | ||
|
||
readonly abi: ABI | ||
readonly account: Name | ||
readonly client?: APIClient | ||
readonly client: APIClient | ||
|
||
/** | ||
* Constructs a new `Contract` instance. | ||
* | ||
* @param {ContractArgs} args - The required arguments for a contract. | ||
* @param {ContractOptions} options - The options for the contract. | ||
* @param {NameType} options.name - The name of the contract. | ||
* @param {APIClient} options.client - The client to connect to the blockchain. | ||
*/ | ||
constructor(options: ContractOptions) { | ||
this.account = Name.from(options.name) | ||
|
||
this.client = options.client | ||
|
||
this.abi = options.abi | ||
} | ||
|
||
/** | ||
* Creates a new `Contract` instance with the given options. | ||
* | ||
* @param {ContractOptions} options - The options for the contract. | ||
* @return {Contract} A new contract instance. | ||
*/ | ||
static from(options: ContractOptions): Contract { | ||
return new this(options) | ||
} | ||
|
||
/** | ||
* Calls a contract action. | ||
* | ||
* @param {NameType} name - The name of the action. | ||
* @param {ABISerializableObject | {[key: string]: any}} data - The data for the action. | ||
* @param {Session} session - The session object to use to sign the transaction. | ||
* @return {Promise<TransactResult>} A promise that resolves with the transaction data. | ||
*/ | ||
async call( | ||
name: NameType, | ||
data: ABISerializableObject | {[key: string]: any}, | ||
session: Session | ||
): Promise<TransactResult> { | ||
const action: Action = Action.from({ | ||
account: this.account, | ||
name, | ||
authorization: [], | ||
data, | ||
}) | ||
|
||
// Trigger the transaction using the session kit | ||
return session.transact({action}) | ||
} | ||
|
||
/** | ||
* Gets all the tables for the contract. | ||
* | ||
* @return {Promise<Table[]>} A promise that resolves with all the tables for the contract. | ||
*/ | ||
async getTables(): Promise<Table[]> { | ||
const abi = await this.getAbi() | ||
constructor(args: ContractArgs, options: ContractOptions = {}) { | ||
this.abi = ABI.from(args.abi) | ||
this.account = Name.from(args.account) | ||
this.client = args.client | ||
|
||
return abi.tables.map((table) => { | ||
return new Table({ | ||
this.abi.tables.forEach((tableDef) => { | ||
this.tables[String(tableDef.name)] = Table.from({ | ||
contract: this, | ||
name: table.name, | ||
name: tableDef.name, | ||
rowType: tableDef.type, | ||
}) | ||
}) | ||
} | ||
|
||
/** | ||
* Gets a specific table for the contract. | ||
* | ||
* @param {NameType} name - The name of the table. | ||
* @return {Promise<Table>} A promise that resolves with the specified table. | ||
*/ | ||
async getTable(name: NameType): Promise<Table> { | ||
const tables = await this.getTables() | ||
static from(args: ContractArgs, options: ContractOptions = {}): Contract { | ||
return new this(args, options) | ||
} | ||
|
||
const table = tables.find((table) => table.name.equals(name)) | ||
get tables(): string[] { | ||
return this.abi.tables.map((table) => String(table.name)) | ||
} | ||
|
||
if (!table) { | ||
throw new Error(`No table found with name ${name}`) | ||
public table(name: NameType) { | ||
if (!this.tables.includes(String(name))) { | ||
throw new Error(`Contract (${this.account}) does not have a table named (${name})`) | ||
} | ||
|
||
return table | ||
return Table.from({ | ||
contract: this, | ||
name, | ||
}) | ||
} | ||
|
||
// TODO: reimplement call method | ||
/** | ||
* Gets the ABI for the contract. | ||
* Calls a contract action. | ||
* | ||
* @return {Promise<ABI.Def>} A promise that resolves with the ABI for the contract. | ||
* @param {NameType} name - The name of the action. | ||
* @param {ABISerializableObject | {[key: string]: any}} data - The data for the action. | ||
* @param {Session} session - The session object to use to sign the transaction. | ||
* @return {Promise<TransactResult>} A promise that resolves with the transaction data. | ||
*/ | ||
async getAbi(): Promise<ABI.Def> { | ||
if (this.abi) { | ||
return this.abi | ||
} | ||
|
||
if (!this.client) { | ||
throw new Error('Cannot get ABI without client') | ||
} | ||
|
||
let response | ||
|
||
try { | ||
response = await this.client.v1.chain.get_abi(this.account) | ||
} catch (error: any) { | ||
if (error.message.includes('Account not found')) { | ||
throw new Error(`No ABI found for ${this.account}`) | ||
} else { | ||
throw new Error(`Error fetching ABI: ${JSON.stringify(error)}`) | ||
} | ||
} | ||
|
||
const {abi} = response | ||
|
||
this.abi = abi | ||
|
||
return abi | ||
} | ||
// async call( | ||
// name: NameType, | ||
// data: ABISerializableObject | {[key: string]: any}, | ||
// session: Session | ||
// ): Promise<TransactResult> { | ||
// const action: Action = Action.from({ | ||
// account: this.account, | ||
// name, | ||
// authorization: [], | ||
// data, | ||
// }) | ||
|
||
// // Trigger the transaction using the session kit | ||
// return session.transact({action}) | ||
// } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,109 +1,77 @@ | ||
import {assert} from 'chai' | ||
import {makeClient, makeMockAction, mockSession} from '@wharfkit/mock-data' | ||
|
||
import {Contract, Table} from '$lib' | ||
import ContractKit, {Contract, ContractArgs, Table} from '$lib' | ||
import {ABI, Name, Serializer} from '@wharfkit/session' | ||
|
||
const mockClient = makeClient('https://eos.greymass.com') | ||
|
||
const mockContractArgs: ContractArgs = { | ||
abi: {version: 'eosio::abi/1.2'}, | ||
account: 'eosio', | ||
client: mockClient, | ||
} | ||
|
||
suite('Contract', () => { | ||
let mockContract: Contract | ||
|
||
setup(async function () { | ||
mockContract = new Contract({ | ||
name: 'decentiumorg', | ||
const kit = new ContractKit({ | ||
client: mockClient, | ||
}) | ||
mockContract = await kit.load('eosio') | ||
}) | ||
|
||
suite('from', () => { | ||
test('returns a Contract instance', () => { | ||
const contract = Contract.from({ | ||
name: 'decentiumorg', | ||
client: mockClient, | ||
}) | ||
assert.instanceOf(contract, Contract) | ||
}) | ||
}) | ||
|
||
suite('call', () => { | ||
test('calls a contract action', async () => { | ||
suite('construct', function () { | ||
test('typed', function () { | ||
const contract = new Contract({ | ||
name: 'eosio.token', | ||
client: mockClient, | ||
...mockContractArgs, | ||
abi: ABI.from(mockContractArgs.abi), | ||
account: Name.from(mockContractArgs.account), | ||
}) | ||
const session = mockSession | ||
const actionName = 'transfer' | ||
const {data} = makeMockAction() | ||
await contract.call(actionName, data, session) | ||
assert.instanceOf(contract, Contract) | ||
}) | ||
}) | ||
|
||
suite('getTables', () => { | ||
test('returns list of tables', async () => { | ||
const tables = await mockContract.getTables() | ||
assert.lengthOf(tables, 5) | ||
assert.instanceOf(tables[0], Table) | ||
test('untyped', function () { | ||
const contract = new Contract(mockContractArgs) | ||
assert.instanceOf(contract, Contract) | ||
}) | ||
}) | ||
|
||
suite('getTable', () => { | ||
test('returns single table', async () => { | ||
assert.instanceOf(await mockContract.getTable('blogs'), Table) | ||
assert.instanceOf(await mockContract.getTable('links'), Table) | ||
assert.instanceOf(await mockContract.getTable('posts'), Table) | ||
suite('tables', function () { | ||
test('list table names', function () { | ||
assert.isArray(mockContract.tables) | ||
assert.lengthOf(mockContract.tables, 26) | ||
assert.isTrue(mockContract.tables.includes('voters')) | ||
}) | ||
}) | ||
|
||
suite('getAbi', () => { | ||
test('returns ABI for the contract', async () => { | ||
const abi = await mockContract.getAbi() | ||
|
||
assert.isObject(abi) | ||
assert.hasAllKeys(abi, [ | ||
'version', | ||
'types', | ||
'structs', | ||
'actions', | ||
'tables', | ||
'ricardian_clauses', | ||
'error_messages', | ||
'abi_extensions', | ||
'action_results', | ||
'variants', | ||
]) | ||
|
||
const abiSecondCall = await mockContract.getAbi() | ||
assert.strictEqual( | ||
abi, | ||
abiSecondCall, | ||
'ABI should be cached and return the same instance on subsequent calls' | ||
) | ||
suite('table', function () { | ||
test('load table using Name', function () { | ||
const table = mockContract.table('voters') | ||
assert.instanceOf(table, Table) | ||
assert.isTrue(table.name.equals('voters')) | ||
}) | ||
|
||
test('throws error when client is not set', async () => { | ||
const contractWithoutClient = new Contract({name: 'decentiumorg'}) | ||
try { | ||
await contractWithoutClient.getAbi() | ||
assert.fail('Expected method to reject.') | ||
} catch (err: any) { | ||
assert.strictEqual(err.message, 'Cannot get ABI without client') | ||
} | ||
test('load table using string', function () { | ||
const table = mockContract.table(Name.from('voters')) | ||
assert.instanceOf(table, Table) | ||
assert.isTrue(table.name.equals('voters')) | ||
}) | ||
|
||
test('throws error when ABI not found', async () => { | ||
const contractWithNonExistentName = new Contract({ | ||
name: 'nonExistent', | ||
client: mockClient, | ||
}) | ||
|
||
try { | ||
await contractWithNonExistentName.getAbi() | ||
} catch (error: any) { | ||
assert.strictEqual( | ||
error.message, | ||
`No ABI found for ${contractWithNonExistentName.account}` | ||
) | ||
} | ||
test('throws on invalid name', function () { | ||
assert.throws(() => mockContract.table('foo')) | ||
}) | ||
}) | ||
|
||
// TODO: reimplement call tests | ||
// suite('call', () => { | ||
// test('calls a contract action', async () => { | ||
// const contract = new Contract({ | ||
// name: 'eosio.token', | ||
// client: mockClient, | ||
// }) | ||
// const session = mockSession | ||
// const actionName = 'transfer' | ||
// const {data} = makeMockAction() | ||
// await contract.call(actionName, data, session) | ||
// }) | ||
// }) | ||
}) |
Oops, something went wrong.