Skip to content

Commit 4dc04c8

Browse files
Lib: Add api query (#232)
Co-authored-by: Agustincito <agus@poap.io>
1 parent 1c4cb01 commit 4dc04c8

12 files changed

Lines changed: 178 additions & 62 deletions

File tree

.changeset/little-sloths-cry.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@mimicprotocol/lib-ts": patch
3+
"@mimicprotocol/cli": patch
4+
"@mimicprotocol/test-ts": patch
5+
---
6+
7+
Add api query

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ jobs:
5151
steps:
5252
- name: Checkout
5353
uses: actions/checkout@v3
54+
- name: Setup Node
55+
uses: actions/setup-node@v4
56+
with:
57+
node-version-file: ".nvmrc"
5458
- name: Install
5559
shell: bash
5660
run: yarn
@@ -66,6 +70,10 @@ jobs:
6670
steps:
6771
- name: Checkout
6872
uses: actions/checkout@v3
73+
- name: Setup Node
74+
uses: actions/setup-node@v4
75+
with:
76+
node-version-file: .nvmrc
6977
- name: Install
7078
shell: bash
7179
run: yarn

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
22
1+
22.15.1

packages/cli/src/lib/InputsInterfaceGenerator.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,10 @@ function generateImports(inputs: Record<string, string>): string {
4444
}
4545

4646
function generateInputsMapping(inputs: Record<string, string>, originalInputs: ManifestInputs): string {
47+
const variableTypes = new Set(['string', 'Address', 'Bytes', 'BigInt', 'BlockchainToken', 'TokenAmount'])
4748
return Object.entries(inputs)
4849
.map(([name, type]) => {
49-
const declaration =
50-
type === 'string' ||
51-
type === 'Address' ||
52-
type === 'Bytes' ||
53-
type === 'BigInt' ||
54-
type === 'BlockchainToken' ||
55-
type === 'TokenAmount'
56-
? `var ${name}: string | null`
57-
: `const ${name}: ${type}`
50+
const declaration = variableTypes.has(type) ? `var ${name}: string | null` : `const ${name}: ${type}`
5851

5952
const originalInput = originalInputs[name]
6053
const hasDescription = typeof originalInput === 'object' && !!originalInput.description

packages/cli/src/lib/ManifestHandler.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as fs from 'fs'
44
import { load } from 'js-yaml'
55
import { ZodError } from 'zod'
66

7-
import { DuplicateEntryError, EmptyManifestError, MoreThanOneEntryError } from '../errors'
7+
import { DuplicateEntryError, EmptyManifestError, GENERIC_SUGGESTION, MoreThanOneEntryError } from '../errors'
88
import { Manifest } from '../types'
99
import { ManifestValidator } from '../validators'
1010

@@ -41,9 +41,9 @@ export default {
4141
},
4242
}
4343

44-
function mergeIfUnique(list: Record<string, unknown>[]) {
44+
function mergeIfUnique(list: Record<string, unknown>[] = []) {
4545
const merged: Record<string, unknown> = {}
46-
for (const obj of list || []) {
46+
for (const obj of list) {
4747
const entries = Object.entries(obj)
4848
if (entries.length !== 1) throw new MoreThanOneEntryError(entries)
4949
const [key, val] = entries[0]
@@ -72,9 +72,7 @@ function handleValidationError(command: Command, err: unknown): never {
7272
suggestions = err.errors.map((e) => `Fix Field "${e.path.join('.')}" -- ${e.message}`)
7373
} else {
7474
;[message, code] = [`Unkown Error: ${err}`, 'UnknownError']
75-
suggestions = [
76-
'Contact the Mimic team for further assistance at our website https://www.mimic.fi/ or discord https://discord.com/invite/cpcyV9EsEg',
77-
]
75+
suggestions = GENERIC_SUGGESTION
7876
}
7977

8078
command.error(message, { code, suggestions })

packages/lib-ts/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from './src/evm'
44
export * from './src/helpers'
55
export * from './src/intents'
66
export * from './src/log'
7+
export * from './src/queries'
78
export * from './src/storage'
89
export * from './src/svm'
910
export * from './src/tokens'

packages/lib-ts/src/environment.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { evm } from './evm'
55
import { Consensus, ListType, MIMIC_HELPER_ADDRESS } from './helpers'
66
import { Intent } from './intents'
77
import {
8+
ApiQuery,
9+
ApiQueryResponse,
810
EvmCallQuery,
911
EvmCallQueryResponse,
1012
RelevantTokensQuery,
@@ -38,6 +40,9 @@ export namespace environment {
3840
@external('environment', '_subgraphQuery')
3941
declare function _subgraphQuery(params: string): string
4042

43+
@external('environment', '_apiQuery')
44+
declare function _apiQuery(params: string): string
45+
4146
@external('environment', '_svmAccountsInfoQuery')
4247
declare function _svmAccountsInfoQuery(params: string): string
4348

@@ -162,6 +167,20 @@ export namespace environment {
162167
return SvmAccountsInfoQueryResponse.fromJson<SvmAccountsInfoQueryResponse>(responseStr).toResult()
163168
}
164169

170+
/**
171+
* Executes an HTTP API GET call and returns the raw (stringified) response body.
172+
* @param url - The endpoint URL to call
173+
* @param timestamp - Optional. Cache/snapshot timestamp used to fetch a previously cached response at the given point in time
174+
* @returns A `Result` containing either the response body as a string or an error string
175+
*/
176+
export function apiQuery(
177+
url: string,
178+
timestamp: Date | null = null
179+
): Result<string, string> {
180+
const responseStr = _apiQuery(JSON.stringify(ApiQuery.from(url, timestamp)))
181+
return ApiQueryResponse.fromJson<ApiQueryResponse>(responseStr).toResult()
182+
}
183+
165184
/**
166185
* Returns the current execution context containing environment information.
167186
* @returns The Context object containing: user, settler, timestamp, consensusThreshold and triggerPayload
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Result } from '../types'
2+
3+
import { QueryResponseBase } from './QueryResponse'
4+
5+
@json
6+
class ApiQueryBase {
7+
constructor(public readonly url: string) {}
8+
}
9+
10+
@json
11+
export class ApiQuery extends ApiQueryBase {
12+
public readonly timestamp: i64
13+
14+
constructor(url: string, timestamp: i64) {
15+
super(url)
16+
this.timestamp = timestamp
17+
}
18+
19+
static from(url: string, timestamp: Date | null): ApiQueryBase {
20+
return timestamp ? new ApiQuery(url, changetype<Date>(timestamp).getTime()) : new ApiQueryBase(url)
21+
}
22+
}
23+
24+
@json
25+
export class ApiQueryResponse extends QueryResponseBase {
26+
public data: string
27+
28+
constructor(success: string, data: string, error: string) {
29+
super(success, error)
30+
this.data = data
31+
}
32+
33+
toResult(): Result<string, string> {
34+
return this.buildResult<string>(this.data, 'Unknown error getting API response')
35+
}
36+
}

packages/lib-ts/src/queries/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './ApiQuery'
12
export * from './EvmCallQuery'
23
export * from './QueryResponse'
34
export * from './RelevantTokensQuery'
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { ApiQueryResponse } from '../../src/queries'
2+
3+
describe('ApiQueryResponse', () => {
4+
describe('toResult', () => {
5+
describe('when response is successful', () => {
6+
describe('when data is provided', () => {
7+
it('should return result with data', () => {
8+
const responseData = '{"test": true}'
9+
const response = new ApiQueryResponse('true', responseData, '')
10+
const result = response.toResult()
11+
12+
expect(result.isOk).toBe(true)
13+
const data = result.unwrap()
14+
expect(data).toBe(responseData)
15+
})
16+
})
17+
18+
describe('when data is empty', () => {
19+
it('should return empty string', () => {
20+
const response = new ApiQueryResponse('true', '', '')
21+
const result = response.toResult()
22+
23+
expect(result.isOk).toBe(true)
24+
const data = result.unwrap()
25+
expect(data).toBe('')
26+
})
27+
})
28+
})
29+
30+
describe('when response is not successful', () => {
31+
describe('when error message is provided', () => {
32+
it('should return error with provided message', () => {
33+
const errorMessage = 'Something went wrong'
34+
const response = new ApiQueryResponse('false', '', errorMessage)
35+
const result = response.toResult()
36+
37+
expect(result.isError).toBe(true)
38+
expect(result.error).toBe(errorMessage)
39+
})
40+
})
41+
42+
describe('when error message is not provided', () => {
43+
it('should return default error message', () => {
44+
const response = new ApiQueryResponse('false', '', '')
45+
const result = response.toResult()
46+
47+
expect(result.isError).toBe(true)
48+
expect(result.error).toBe('Unknown error getting API response')
49+
})
50+
})
51+
})
52+
})
53+
})

0 commit comments

Comments
 (0)