Skip to content
Open
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "oreid-js",
"version": "4.7.1",
"version": "4.8.0",
"description": "Add authentication and signing to any blockchain app",
"author": "AIKON",
"license": "MIT",
Expand Down
1 change: 1 addition & 0 deletions src/api/endpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from './newUserWithToken'
export * from './passwordLessSendCode'
export * from './passwordLessVerifyCode'
export * from './signTransaction'
export * from './validateTransaction'
95 changes: 95 additions & 0 deletions src/api/endpoints/validateTransaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import OreIdContext from '../../core/IOreidContext'
import { ApiEndpoint, ChainNetwork, RequestType, ValidateTransactionFees, ValidateTransactionResources} from '../../models'
import { assertHasApiKeyOrAccessToken, assertParamsHaveRequiredValues } from '../helpers'
import { ApiResultWithErrorCode } from '../models'

export type ValidateTransactionParams = {
chainNetwork: ChainNetwork
encodedTransaction?: string
transactionChainAccount?: string
transactionOptionsStringified?: string
transactionRecordId?: string
}

export type ApiValidateTransactionBodyParams = {
chain_network: ChainNetwork
encoded_transaction?: string
transaction_chain_account?: string
transaction_options_stringified?: string
transaction_record_id?: string
}

export type ValidateTransactionPayerParams = {
chainNetwork: ChainNetwork
encodedTransaction?: string
payerChainAccount: string
transactionChainAccount?: string
transactionOptionsStringified?: string
transactionRecordId?: string
}

export type ApiValidateTransactionPayerBodyParams = {
chain_network: ChainNetwork
encoded_transaction?: string
payer_chain_account: string
transaction_chain_account?: string
transaction_options_stringified?: string
transaction_record_id?: string
}

export type ValidateTransactionResult = {
isValid: boolean
canChange: boolean
validFrom: string
validTo: string
errorMessage: string
fees: ValidateTransactionFees
resources: ValidateTransactionResources
actions: string[]
} & ApiResultWithErrorCode

export async function callApiValidateTransaction(
oreIdContext: OreIdContext,
params: ValidateTransactionParams,
): Promise<ValidateTransactionResult> {
const apiName = ApiEndpoint.ValidateTransaction

const { chainNetwork, encodedTransaction, transactionChainAccount, transactionOptionsStringified, transactionRecordId } = params
const body: ApiValidateTransactionBodyParams = {
chain_network: chainNetwork,
encoded_transaction: encodedTransaction,
transaction_chain_account: transactionChainAccount,
transaction_options_stringified: transactionOptionsStringified,
transaction_record_id: transactionRecordId
}

assertHasApiKeyOrAccessToken(oreIdContext, apiName)
assertParamsHaveRequiredValues(params, ['chainNetwork'], apiName)

const results = await oreIdContext.callOreIdApi(RequestType.Post, ApiEndpoint.ValidateTransaction, body, null)
return results
}

export async function callApiValidatePayerTransaction(
oreIdContext: OreIdContext,
params: ValidateTransactionPayerParams,
): Promise<ValidateTransactionResult> {
const apiName = ApiEndpoint.ValidatePayerTransaction

const { chainNetwork, encodedTransaction, payerChainAccount, transactionChainAccount, transactionOptionsStringified, transactionRecordId } = params

const body: ApiValidateTransactionPayerBodyParams = {
chain_network: chainNetwork,
encoded_transaction: encodedTransaction,
payer_chain_account: payerChainAccount,
transaction_chain_account: transactionChainAccount,
transaction_options_stringified: transactionOptionsStringified,
transaction_record_id: transactionRecordId
}

assertHasApiKeyOrAccessToken(oreIdContext, apiName)
assertParamsHaveRequiredValues(params, ['chainNetwork', 'payerChainAccount'], apiName)

const results = await oreIdContext.callOreIdApi(RequestType.Post, ApiEndpoint.ValidatePayerTransaction, body, null)
return results
}
2 changes: 2 additions & 0 deletions src/api/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export enum ApiEndpoint {
PasswordLessSendCode = 'account/login-passwordless-send-code',
PasswordLessVerifyCode = 'account/login-passwordless-verify-code',
TransactionSign = 'transaction/sign',
ValidatePayerTransaction = 'transaction/validate-payer',
ValidateTransaction = 'transaction/validate',
UpdateDelayWalletSetup = 'account/update-delay-wallet-setup',
}

Expand Down
115 changes: 101 additions & 14 deletions src/core/oreId.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,50 @@
import { OreIdOptions } from '../core/IOreIdOptions'
import OreId from './oreId'
import Transaction from '../transaction/transaction'
import { callApiCustodialNewAccount, callApiCustodialMigrateAccount } from '../api'
import { callApiCustodialNewAccount, callApiCustodialMigrateAccount, callApiValidateTransaction } from '../api'
import { ChainNetwork, TransactionData, UserChainAccount, UserData } from '../models'

const payerErrorMessage = 'a low resource error message'
const validationErrorMessage = 'a error message'

jest.mock('../api', () => ({
...jest.requireActual('../api'),
callApiCustodialMigrateAccount: jest.fn(),
callApiCustodialNewAccount: jest.fn(),
callApiValidateTransaction: jest.fn().mockImplementation(() => Promise.resolve(
{
isValid: false, errorMessage: validationErrorMessage,
fees: {
chainSupportsFees: true,
feesByPriority: [
{
priority: "low",
fee: "0",
}
]
}
},
)),
callApiValidatePayerTransaction: jest.fn().mockImplementation(() => Promise.resolve(
{
resources: {
chainSupportsResources: true,
resourcesRequired: true,
resourceEstimationType: "exact",
lowResourceErrorMessages: [payerErrorMessage],
},
fees: {
chainSupportsFees: true,
feesByPriority: [
{
priority: "low",
fee: "0",
lowFeeErrorMessage: "balance available is 0"
}
],
}}
)),
}))

jest.mock('../transaction/transaction')

// use factories as this is good to ensure that the values are these, and that the tests do not change the values
const getOptions = (): OreIdOptions => ({
Expand Down Expand Up @@ -94,18 +130,69 @@ describe('custodial Custodial Account', () => {
})
})

describe('Transaction', () => {
test('createTransaction', async () => {
//@ts-ignore
jest.spyOn(oreId._auth.user, 'hasData', 'get').mockReturnValue(true)
const transactionReturn = { param: 'return' }
;(Transaction as jest.Mock).mockReturnValue(transactionReturn)
describe('Create new Transaction with createTransaction', () => {
const userChainAccount: UserChainAccount = {
chainNetwork: ChainNetwork.AlgoBeta,
chainAccount: 'chainAccount',
defaultPermission: { name: 'active' },
permissions: [{ name: 'active' }],
}

const userData: UserData = {
chainAccounts: [userChainAccount],
email: 'email',
name: 'name',
username: 'username',
picture: 'picture',
accountName: 'accountName',
}

const transactionData : TransactionData = {
account: 'accountName',
chainAccount: 'chainAccount',
chainNetwork: ChainNetwork.AlgoBeta,
transaction: {"actions":[{"from":"HRFT6WNEDH5LAN4JTUQIVYFHPZB7JUMPHGIYZZZOMOKBHHUV4HGEFF3JFA","to":"TM4HSPWPRUHEHBVVAYGX3YQTQG5KSEZ4OMAN6NPGELNPYOB7SYEA2PODTQ","amount":1000000,"note":"Hello World"}]}
}

beforeEach(() => {
jest.spyOn(oreId.auth.user, 'getData').mockResolvedValue(userData)
jest.spyOn(oreId.auth.user, 'hasData', 'get').mockReturnValue(true)
jest.spyOn(oreId.auth.user, 'accountName', 'get').mockReturnValue('accountName')
})

test('createTransaction should not throw but fill error fields on validation failure when doesNotThrow is true', async () => {
const result = await oreId.createTransaction(transactionData, true)

expect(result.validationData).toBeDefined();
expect(result.validationData.isValid).toBeFalsy();
expect(result.validationError).toBe('a error message');
expect(result.payerErrors).toContain('balance available is 0');
expect(result.payerErrors).toContain('a low resource error message');
})

test('createTransaction should throw an error on data validation failure when doesNotThrow is set to false', async () => {
try{
await oreId.createTransaction(transactionData, false)
}
catch(error){
// doesNotThrow is false by default
expect(error.message).toBe(`Validation error: ${validationErrorMessage}`);
}
})

expect(Transaction).not.toBeCalled()
const transactionData = { param: 'my-params' }
const result = await oreId.createTransaction(transactionData as any)
test('createTransaction should throw an error on a payer validation failure when doesNotThrow is set to false', async () => {
(callApiValidateTransaction as jest.Mock).mockImplementationOnce(() => Promise.resolve(
{
isValid: true
}
));

expect(result).toEqual(transactionReturn)
expect(Transaction).toBeCalledWith({ oreIdContext: oreId, user: expect.any(Object), data: transactionData })
try{
// doesNotThrow is false by default
await oreId.createTransaction(transactionData)
}
catch(error){
expect(error.message).toBe(`Fee or Resource error: ${payerErrorMessage}`);
}
})
})
13 changes: 11 additions & 2 deletions src/core/oreId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,20 @@ export default class OreId implements IOreidContext {
}

/** Create a new Transaction object - used for composing and signing transactions */
async createTransaction(data: TransactionData) {
async createTransaction(data: TransactionData, dontThrowOnErrors: boolean = false) {
if (!this._auth.user.hasData) {
await this._auth.user.getData()
}
return new Transaction({ oreIdContext: this, user: this.auth.user, data })
const transaction = new Transaction({ oreIdContext: this, user: this.auth.user, data })
await transaction.validate();

if (!dontThrowOnErrors && transaction.hasErrors) {
if (!transaction.validationData.isValid) {
throw new Error(`Validation error: ${transaction.validationData.errorMessage}`)
}
throw new Error(`Fee or Resource error: ${transaction.payerErrors[0]}`)
}
return transaction
}

/** Call the setBusyCallback() callback provided in optiont
Expand Down
19 changes: 19 additions & 0 deletions src/transaction/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,24 @@ export type TransactionSignOptions = {
state?: string
}

export type FeesByPriority = {
priority: any
fee: string
lowFeeErrorMessage: string
}

export type ValidateTransactionFees = {
chainSupportsFees: boolean
feesByPriority: FeesByPriority[]
resourceEstimationType: string
}

export type ValidateTransactionResources = {
chainSupportsResources: boolean
resourcesRequired: string
resourceEstimationType: string
lowResourceErrorMessages: string[]
}

export interface CreateTransactionData
extends Omit<TransactionData, 'account' | 'encodedSignedTransaction' | 'encodedTransaction'> {}
Loading