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
69 changes: 57 additions & 12 deletions src/services/hypernativeApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,33 @@ class HypernativeApi {
this.clientSecret = clientSecret
}

// Python source for the python_processing node in rate-revert agents.
// Tracks consecutive revert count in the account-wide KV store so that the
// alert only fires after 100 consecutive reverts.
private static buildRevertPythonSource(kvKey: string): string {
return `def my_function(extracted_variables):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only way to do it via this weird template injection? No other more elegant way possible?

"""Your custom Python logic goes here.
The \`extracted_variables\` dictionary
contains variables from previous nodes.
The return value will be stored in the
variable defined below."""
is_null = extracted_variables["is_null"]
key = "${kvKey}"
base = "https://api.hypernative.xyz/custom-agents/values"

if is_null == 1:
existing = hn_api_get(f"{base}/{key}").json()["data"]
if existing is None:
count = 1
else:
count = int(existing) + 1
else:
count = 0

hn_api_put(f"{base}/{key}", {"value": str(count)})
return count`
}

// Mapping of viem chain names to Hypernative chain strings
private static chainNameToHypernativeKey: { [key: string]: string } = {
'Arbitrum One': 'arbitrum',
Expand Down Expand Up @@ -85,28 +112,46 @@ class HypernativeApi {
}

public async createCustomAgentRateRevert(input: CustomAgentInput): Promise<HypernativeAgent> {
// This custom agent rule is already complete it only needs modification for rate provider address
const customAgentRule = { ...rateProviderRateRevertRule }
// Deep-clone so the shared template isn't mutated across calls
const customAgentRule = JSON.parse(JSON.stringify(rateProviderRateRevertRule))
const chainKey = this.getValidChainNameFromViemChain(input.chain)

// Per-agent KV key for the persistent revert counter (account-wide store)
const kvKey = `revert_count_${chainKey}_${input.contractAddress.slice(-4).toLowerCase()}`
const pythonSource = HypernativeApi.buildRevertPythonSource(kvKey)
const pythonSourceB64 = Buffer.from(pythonSource, 'utf-8').toString('base64')

// Modify the rule based on input
customAgentRule.rule.chain = this.getValidChainNameFromViemChain(input.chain)
customAgentRule.rule.chain = chainKey
customAgentRule.rule.ruleString = input.ruleString
// TODO: This address has no impact on the rule execution
customAgentRule.rule.contractAddress = input.contractAddress
customAgentRule.agentName = input.agentName
customAgentRule.rule.customDescription = input.ruleString

customAgentRule.rule.conditions[1].operands[0].variable_extraction[3].contract_address = input.contractAddress
customAgentRule.rule.conditions[1].operands[0].eval.custom_description = input.ruleString

customAgentRule.rule.time_based_trigger.chain = this.getValidChainNameFromViemChain(input.chain)
const extractions = customAgentRule.rule.conditions[1].operands[0].variable_extraction
// [3] gcr node
extractions[3].contract_address = input.contractAddress.toLowerCase()
extractions[3].chain = chainKey
// [5] python_processing node — inject per-agent source
const pythonExt = extractions.find((e: any) => e.type === 'python_processing')
if (pythonExt) pythonExt.source_code = pythonSourceB64

customAgentRule.graphData.nodes[0].data.chain = this.getValidChainNameFromViemChain(input.chain)
customAgentRule.rule.conditions[1].operands[0].eval.custom_description = input.ruleString

customAgentRule.graphData.nodes[1].data.chain = this.getValidChainNameFromViemChain(input.chain)
customAgentRule.graphData.nodes[1].data.contractAddress = input.contractAddress
customAgentRule.graphData.nodes[1].data.contractAddressAlias = input.contractAddress
customAgentRule.rule.time_based_trigger.chain = chainKey

customAgentRule.graphData.nodes[4].data.message = input.ruleString
// Look up graph nodes by type so positional shifts don't break the wiring
const nodesByType: Record<string, any> = {}
for (const node of customAgentRule.graphData.nodes) {
nodesByType[node.type] = node
}
nodesByType['time-based-node'].data.chain = chainKey
nodesByType['read-contract-node'].data.chain = chainKey
nodesByType['read-contract-node'].data.contractAddress = input.contractAddress
nodesByType['read-contract-node'].data.contractAddressAlias = input.contractAddress
nodesByType['send-alert-node'].data.message = input.ruleString
nodesByType['python-function-node'].data.pythonCode = pythonSourceB64

const requestBody = customAgentRule

Expand Down
69 changes: 56 additions & 13 deletions src/utils/hypernative/rate-provider-rate-revert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const rateProviderRateRevertRule = {
inputDataType: [],
outputDataType: [],
idlJson: {},
abi: {},
contractFunctionObject: {},
outputIndex: '',
funcSig: '',
Expand All @@ -23,6 +24,7 @@ export const rateProviderRateRevertRule = {
period_unit: 'blocks',
chain: 'ethereum',
},
typeIds: [],
fileName: 'some_file_name.json',
operands: [],
operator: '',
Expand Down Expand Up @@ -67,16 +69,25 @@ export const rateProviderRateRevertRule = {
func_sig: 'getRate()',
chain: 'base',
var_name: 'getRateOutput',
contract_function_object: null,
idl_json: null,
abi_string: null,
},
{
type: 'json_processing',
json_path: 'Z2V0UmF0ZU91dHB1dD1udWxsID8gMTow',
json_path_engine: 'jsonata',
var_name: 'is_null',
},
{
type: 'python_processing',
source_code: '',
var_name: 'consecutive_revert_count',
entry_point: 'my_function',
},
{
type: 'json_processing',
json_path: 'aXNfbnVsbCA+IDA=',
json_path: 'Y29uc2VjdXRpdmVfcmV2ZXJ0X2NvdW50ID49IDEwMA==',
json_path_engine: 'jsonata',
var_name: 'condition_0',
},
Expand All @@ -92,6 +103,10 @@ export const rateProviderRateRevertRule = {
},
],
isReminderEnabled: false,
module: '',
moduleName: '',
typeArguments: [],
genericTypeParams: [],
advanced: ['customDescription'],
customDescription: 'On Base - the getRate() function reverted. contract 0xe1b1..4ddd',
ruleString: '',
Expand All @@ -105,6 +120,8 @@ export const rateProviderRateRevertRule = {
},
],
remindersConfigurations: [],
endAlertsOption: 'sameAsStart',
endAlertsConfigurations: [],
delay: 600,
securitySuitIds: [1373],
graphData: {
Expand All @@ -119,16 +136,21 @@ export const rateProviderRateRevertRule = {
source: '07b6cf44-db10-47b8-8a89-5c2b43d775e6',
target: '6c41586c-ced7-4db6-86cb-fe2f85f73f9e',
},
{
id: 'xy-edge__6c41586c-ced7-4db6-86cb-fe2f85f73f9e-6aecd52f-8b2f-49f5-9707-12952ef36557',
source: '6c41586c-ced7-4db6-86cb-fe2f85f73f9e',
target: '6aecd52f-8b2f-49f5-9707-12952ef36557',
},
{
id: 'xy-edge__6aecd52f-8b2f-49f5-9707-12952ef36557-b6d012b6-03e1-4295-9389-a2288fbfb211',
source: '6aecd52f-8b2f-49f5-9707-12952ef36557',
target: 'b6d012b6-03e1-4295-9389-a2288fbfb211',
},
{
id: 'xy-edge__6c41586c-ced7-4db6-86cb-fe2f85f73f9e-cf43c1f0-b099-4f21-af4c-4925c07d7ec5',
source: '6c41586c-ced7-4db6-86cb-fe2f85f73f9e',
target: 'cf43c1f0-b099-4f21-af4c-4925c07d7ec5',
},
{
id: 'xy-edge__cf43c1f0-b099-4f21-af4c-4925c07d7ec5-6aecd52f-8b2f-49f5-9707-12952ef36557',
source: 'cf43c1f0-b099-4f21-af4c-4925c07d7ec5',
target: '6aecd52f-8b2f-49f5-9707-12952ef36557',
},
],
nodes: [
{
Expand All @@ -141,7 +163,7 @@ export const rateProviderRateRevertRule = {
type: 'time-based-node',
measured: {
width: 158,
height: 99,
height: 107,
},
position: {
x: 1000,
Expand Down Expand Up @@ -180,7 +202,7 @@ export const rateProviderRateRevertRule = {
type: 'read-contract-node',
measured: {
width: 269,
height: 286,
height: 341,
},
position: {
x: 710,
Expand All @@ -198,7 +220,7 @@ export const rateProviderRateRevertRule = {
type: 'calculation-node',
measured: {
width: 269,
height: 148,
height: 157,
},
position: {
x: 1093,
Expand All @@ -208,17 +230,18 @@ export const rateProviderRateRevertRule = {
{
id: '6aecd52f-8b2f-49f5-9707-12952ef36557',
data: {
valueA: 'is_null',
valueB: '0',
condition: '>',
valueA: 'consecutive_revert_count',
valueB: '100',
condition: 'gte',
description: '',
valueAMultiplier: 0,
valueBMultiplier: 0,
valueCMultiplier: 0,
},
type: 'condition-node',
measured: {
width: 269,
height: 84,
height: 92,
},
position: {
x: 1223,
Expand All @@ -241,7 +264,27 @@ export const rateProviderRateRevertRule = {
y: 455,
},
},
{
id: 'cf43c1f0-b099-4f21-af4c-4925c07d7ec5',
data: {
alias: 'consecutive_revert_count',
aliasType: 'number',
entryPoint: 'my_function',
pythonCode: '',
description: '',
},
type: 'python-function-node',
measured: {
width: 269,
height: 111,
},
position: {
x: 1414.49634369287,
y: 149.54816042047526,
},
},
],
description: '',
},
mode: 'onchain',
}
Loading