Skip to content

Commit fd73f95

Browse files
committed
first draft
1 parent aa0869c commit fd73f95

File tree

5 files changed

+177
-0
lines changed

5 files changed

+177
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Do not merge this manual test to main
2+
3+
NODE_PRIVATE_KEY="1111111111111111111111111111111111111111111111111111111111111111"
4+
OWNER_PRIVATE_KEY="2222222222222222222222222222222222222222222222222222222222222222"
5+
SPONSORER_PRIVATE_KEY="3333333333333333333333333333333333333333333333333333333333333333"
6+
EARNINGS_PER_SECOND=12
7+
STAKED_AMOUNT=500000
8+
SPONSOR_AMOUNT=1234567
9+
10+
NODE_ADDRESS=$(ethereum-address $NODE_PRIVATE_KEY | jq -r '.address')
11+
OWNER_ADDRESS=$(ethereum-address $OWNER_PRIVATE_KEY | jq -r '.address')
12+
SPONSORER_ADDRESS=$(ethereum-address $SPONSORER_PRIVATE_KEY | jq -r '.address')
13+
14+
cd ../../cli-tools
15+
echo 'Mint tokens'
16+
npx tsx bin/streamr.ts internal token-mint $NODE_ADDRESS 10000000 10000000
17+
npx tsx bin/streamr.ts internal token-mint $OWNER_ADDRESS 10000000 10000000
18+
echo 'Create operator'
19+
OPERATOR_CONTRACT_ADDRESS=$(npx tsx bin/streamr.ts internal operator-create -c 10 --node-addresses $NODE_ADDRESS --env dev2 --private-key $OWNER_PRIVATE_KEY | jq -r '.address')
20+
npx tsx bin/streamr.ts internal token-mint $SPONSORER_ADDRESS 10000000 10000000
21+
npx tsx bin/streamr.ts stream create /foo --env dev2 --private-key $SPONSORER_PRIVATE_KEY
22+
SPONSORSHIP_CONTRACT_ADDRESS=$(npx tsx bin/streamr.ts internal sponsorship-create /foo -e $EARNINGS_PER_SECOND --env dev2 --private-key $SPONSORER_PRIVATE_KEY | jq -r '.address')
23+
npx tsx bin/streamr.ts internal sponsorship-sponsor $SPONSORSHIP_CONTRACT_ADDRESS $SPONSOR_AMOUNT --env dev2 --private-key $SPONSORER_PRIVATE_KEY
24+
npx tsx bin/streamr.ts internal operator-delegate $OPERATOR_CONTRACT_ADDRESS $STAKED_AMOUNT --env dev2 --private-key $OWNER_PRIVATE_KEY
25+
26+
jq -n \
27+
--arg nodePrivateKey "$NODE_PRIVATE_KEY" \
28+
--arg operatorOwnerPrivateKey "$OWNER_PRIVATE_KEY" \
29+
--arg operatorContractAddress "$OPERATOR_CONTRACT_ADDRESS" \
30+
'{
31+
"$schema": "https://schema.streamr.network/config-v3.schema.json",
32+
client: {
33+
auth: {
34+
privateKey: $nodePrivateKey
35+
},
36+
environment: "dev2"
37+
},
38+
plugins: {
39+
autostaker: {
40+
operatorOwnerPrivateKey: $operatorOwnerPrivateKey,
41+
operatorContractAddress: $operatorContractAddress
42+
}
43+
}
44+
}' > ../node/configs/autostaker.json
45+
46+
jq -n \
47+
--arg operatorContract "$OPERATOR_CONTRACT_ADDRESS" \
48+
--arg sponsorshipContract "$SPONSORSHIP_CONTRACT_ADDRESS" \
49+
'$ARGS.named'
50+
51+
cd ../node
52+
npx tsx bin/streamr-node.ts configs/autostaker.json

packages/node/src/config/validateConfig.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const validateConfig = (data: unknown, schema: Schema, contextName?: stri
99
})
1010
addFormats(ajv)
1111
ajv.addFormat('ethereum-address', /^0x[a-zA-Z0-9]{40}$/)
12+
ajv.addFormat('ethereum-private-key', /^(0x)?[a-zA-Z0-9]{64}$/)
1213
ajv.addSchema(DEFINITIONS_SCHEMA)
1314
if (!ajv.validate(schema, data)) {
1415
const prefix = (contextName !== undefined) ? (contextName + ': ') : ''

packages/node/src/pluginRegistry.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Plugin } from './Plugin'
22
import { StrictConfig } from './config/config'
3+
import { AutostakerPlugin } from './plugins/autostaker/AutostakerPlugin'
34
import { ConsoleMetricsPlugin } from './plugins/consoleMetrics/ConsoleMetricsPlugin'
45
import { HttpPlugin } from './plugins/http/HttpPlugin'
56
import { InfoPlugin } from './plugins/info/InfoPlugin'
@@ -27,6 +28,8 @@ export const createPlugin = (name: string, brokerConfig: StrictConfig): Plugin<a
2728
return new SubscriberPlugin(name, brokerConfig)
2829
case 'info':
2930
return new InfoPlugin(name, brokerConfig)
31+
case 'autostaker':
32+
return new AutostakerPlugin(name, brokerConfig)
3033
default:
3134
throw new Error(`Unknown plugin: ${name}`)
3235
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { _operatorContractUtils, StreamrClient } from '@streamr/sdk'
2+
import { collect, Logger, scheduleAtInterval, WeiAmount } from '@streamr/utils'
3+
import { Schema } from 'ajv'
4+
import { formatEther, parseEther, Wallet } from 'ethers'
5+
import sample from 'lodash/sample'
6+
import { Plugin } from '../../Plugin'
7+
import PLUGIN_CONFIG_SCHEMA from './config.schema.json'
8+
9+
export interface AutostakerPluginConfig {
10+
operatorContractAddress: string
11+
// TODO is it possible implement this without exposing the private key here?
12+
// e.g. by configuring so that operator nodes can stake behalf of the operator?
13+
operatorOwnerPrivateKey: string
14+
runIntervalInMs: number
15+
}
16+
17+
const STAKE_AMOUNT: WeiAmount = parseEther('10000')
18+
19+
const logger = new Logger(module)
20+
21+
export class AutostakerPlugin extends Plugin<AutostakerPluginConfig> {
22+
23+
private abortController: AbortController = new AbortController()
24+
25+
async start(streamrClient: StreamrClient): Promise<void> {
26+
logger.info('Start autostaker plugin')
27+
scheduleAtInterval(async () => {
28+
try {
29+
await this.runActions(streamrClient)
30+
} catch (err) {
31+
logger.warn('Error while running autostaker actions', { err })
32+
}
33+
}, this.pluginConfig.runIntervalInMs, false, this.abortController.signal)
34+
}
35+
36+
private async runActions(streamrClient: StreamrClient): Promise<void> {
37+
logger.info('Run autostaker actions')
38+
const provider = (await streamrClient.getSigner()).provider
39+
const operatorContract = _operatorContractUtils.getOperatorContract(this.pluginConfig.operatorContractAddress)
40+
.connect(new Wallet(this.pluginConfig.operatorOwnerPrivateKey, provider))
41+
// 30000 DELEGOINTEJA
42+
// 10000 niistä steikattu = totalStakedIntoSponsorshipsWei()
43+
// 123 on saatu jo tuottoja, ja withdrawattu sponsorshipeistä
44+
// 20000 = X
45+
// valueWithoutEarnings() = 30123 = joka voisi olla nimeltä esim. totalBalanceInData()
46+
const stakedAmount = await operatorContract.totalStakedIntoSponsorshipsWei()
47+
const availableBalance = (await operatorContract.valueWithoutEarnings()) - stakedAmount // tämä on ihan sama, mutta toi valueWithoutEarnings(), voi käyttää kumpaa vaan await getTestTokenContract().connect(provider).balanceOf(await operatorContract.getAddress()) // = 20123
48+
logger.info(`Available balance: ${formatEther(availableBalance)} (staked=${formatEther(stakedAmount)})`)
49+
// TODO is there a better way to get the client? Maybe we should add StreamrClient#getTheGraphClient()
50+
// TODO what are good where consitions for the sponsorships query
51+
// @ts-expect-error private
52+
const queryResult = streamrClient.theGraphClient.queryEntities((lastId: string, pageSize: number) => {
53+
return {
54+
query: `
55+
{
56+
sponsorships(
57+
where: {
58+
projectedInsolvency_gt: ${Math.floor(Date.now() / 1000)},
59+
spotAPY_gt: 0
60+
id_gt: "${lastId}"
61+
},
62+
first: ${pageSize}
63+
) {
64+
id
65+
}
66+
}
67+
`
68+
}
69+
})
70+
const sponsorships: { id: string }[] = await collect(queryResult)
71+
logger.info(`Available sponsorships: ${sponsorships.map((s) => s.id).join(',')}`)
72+
if ((sponsorships.length) > 0 && (availableBalance >= STAKE_AMOUNT)) {
73+
const targetSponsorship = sample(sponsorships)!
74+
logger.info(`Stake ${formatEther(STAKE_AMOUNT)} to ${targetSponsorship.id}`)
75+
await _operatorContractUtils.stake(
76+
operatorContract,
77+
targetSponsorship.id,
78+
STAKE_AMOUNT
79+
)
80+
}
81+
}
82+
83+
async stop(): Promise<void> {
84+
logger.info('Stop autostaker plugin')
85+
this.abortController.abort()
86+
}
87+
88+
// eslint-disable-next-line class-methods-use-this
89+
override getConfigSchema(): Schema {
90+
return PLUGIN_CONFIG_SCHEMA
91+
}
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"$id": "config.schema.json",
3+
"$schema": "http://json-schema.org/draft-07/schema#",
4+
"type": "object",
5+
"description": "Autostaker plugin configuration",
6+
"additionalProperties": false,
7+
"required": [
8+
"operatorContractAddress",
9+
"operatorOwnerPrivateKey"
10+
],
11+
"properties": {
12+
"operatorContractAddress": {
13+
"type": "string",
14+
"description": "Operator contract Ethereum address",
15+
"format": "ethereum-address"
16+
},
17+
"operatorOwnerPrivateKey": {
18+
"type": "string",
19+
"description": "Operator owner's private key",
20+
"format": "ethereum-private-key"
21+
},
22+
"runIntervalInMs": {
23+
"type": "integer",
24+
"description": "The interval (in milliseconds) at which autostaking possibilities are analyzed and executed",
25+
"minimum": 0,
26+
"default": 30000
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)