@ubiquity-os/text-conversation-rewards
Install dependencies: Make sure you have Bun installed, then run:
bun install
Build the UI for production:
cd src/web && bun run ui:build
Copy and paste the .env.example
and populate the environment variables
Start the server:
bun run server
This plugin revolutionizes open source collaboration by implementing an AI-powered reward mechanism for quality contributions.
At the heart of the system is a content evaluation module that assigns monetary value to contributor comments in the context of work projects with specifications. Here's how it works:
The system processes both issue comments and pull request review comments through different evaluation pipelines, with comprehensive preprocessing that:
For issue comments, it generates a context-aware prompt that includes:
The evaluation process handles GitHub-flavored markdown intelligently:
The language model assigns relevance scores from 0 to 1:
interface Relevances {
[commentId: string]: number; // 0 = irrelevant, 1 = highly relevant
}
The review incentivization module implements a sophisticated algorithm for rewarding code reviews:
interface ReviewScore {
reviewId: number;
effect: {
addition: number;
deletion: number;
};
reward: number;
priority: number;
}
The system calculates rewards based on:
The permit generation module handles the secure distribution of rewards:
Security Checks:
Fee Processing:
Reward Distribution:
https://pay.ubq.fi?claim=[encoded_permit]
The system uses decimal.js for precise token calculations:
const feeRateDecimal = new Decimal(100).minus(env.PERMIT_FEE_RATE).div(100);
const totalAfterFee = new Decimal(rewardResult.total).mul(feeRateDecimal).toNumber();
For large conversations, the system implements intelligent token management:
// Dynamically handles token limits and chunking for large conversations
_calculateMaxTokens(prompt: string, totalTokenLimit: number = 16384) {
// Token limit is configurable and adjusts based on model and rate limits
const inputTokens = this.tokenizer.encode(prompt).length;
const limit = Math.min(this._configuration?.tokenCountLimit, this._rateLimit);
return Math.min(inputTokens, limit);
}
// Splits large conversations into manageable chunks
async _splitPromptForEvaluation(specification: string, comments: Comment[]) {
let chunks = 2;
while (this._exceedsTokenLimit(comments, chunks)) {
chunks++;
}
return this._processChunks(specification, comments, chunks);
}
The system maintains a comprehensive record of all permits and rewards:
interface PermitRecord {
amount: string;
nonce: string;
deadline: string;
signature: string;
beneficiary_id: number;
location_id: number;
}
{
"userName": {
"comments": [
{
"content": "comment content",
"url": "https://url-to-item",
"type": 18,
"score": {
"formatting": {
"content": {
"p": {
"count": 16,
"score": 1
}
},
"wordValue": 0.1,
"multiplier": 1
},
"reward": 0.8,
"relevance": 0.5
}
}
],
"total": 40.5,
"task": {
"reward": 37.5,
"multiplier": 1
},
"feeRate": 0.1,
"permitUrl": "https://example.com/permit",
"payoutMode": "permit",
"userId": 123,
"evaluationCommentHtml": "<p>Evaluation comment</p>"
}
}
Reward formula:
\sum_{i=0}^{n} \left( \sum_{j=0}^{n} \left(\text{wordCount}^{exponent} \times \text{wordValue} \times \text{relevance}\right) + \left(\text{score} \times \text{elementCount}\right) \right) \times multiplier + \text{task.reward} = \text{total}
Here is a possible valid configuration to enable this plugin. See these files for more details.
plugin: ubiquity-os/conversation-rewards
with:
dataCollection:
maxAttempts: 10
delayMs: 10000
rewards:
evmNetworkId: 100
evmPrivateEncrypted: "encrypted-key"
erc20RewardToken: "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d"
incentives:
requirePriceLabel: true
limitRewards: true
collaboratorOnlyPaymentInvocation: true
contentEvaluator:
llm:
model: "gpt-4" # Model identifier
endpoint: "https://api.openrouter.ai/api/v1" # Configurable LLM endpoint
tokenCountLimit: 124000 # Adjustable token limit
maxRetries: 5 # Number of retries for rate limits/errors
multipliers:
- role: [ISSUE_SPECIFICATION]
relevance: 1
- role: [PULL_AUTHOR]
relevance: 1
- role: [PULL_ASSIGNEE]
relevance: 1
- role: [PULL_COLLABORATOR]
relevance: 1
- role: [PULL_CONTRIBUTOR]
relevance: 1
userExtractor:
redeemTask: true
dataPurge:
skipCommentsWhileAssigned: all
reviewIncentivizer:
baseRate: 100
formattingEvaluator:
wordCountExponent: 0.85
multipliers:
- role: ["ISSUE_SPECIFICATION"]
multiplier: 1
rewards:
html:
br: { score: 0, countWords: true }
code: { score: 5, countWords: false }
p: { score: 0, countWords: true }
em: { score: 0, countWords: true }
img: { score: 5, countWords: true }
strong: { score: 0, countWords: false }
blockquote: { score: 0, countWords: false }
h1: { score: 1, countWords: true }
h2: { score: 1, countWords: true }
h3: { score: 1, countWords: true }
h4: { score: 1, countWords: true }
h5: { score: 1, countWords: true }
h6: { score: 1, countWords: true }
a: { score: 5, countWords: true }
li: { score: 0.5, countWords: true }
ul: { score: 1, countWords: true }
td: { score: 0, countWords: true }
hr: { score: 0, countWords: true }
pre: { score: 0, countWords: false }
ol: { score: 1, countWords: true }
wordValue: 0.1
- role: ["ISSUE_AUTHOR"]
multiplier: 1
rewards:
html:
br: { score: 0, countWords: true }
code: { score: 5, countWords: false }
p: { score: 0, countWords: true }
em: { score: 0, countWords: true }
img: { score: 5, countWords: true }
strong: { score: 0, countWords: false }
blockquote: { score: 0, countWords: false }
h1: { score: 1, countWords: true }
h2: { score: 1, countWords: true }
h3: { score: 1, countWords: true }
h4: { score: 1, countWords: true }
h5: { score: 1, countWords: true }
h6: { score: 1, countWords: true }
a: { score: 5, countWords: true }
li: { score: 0.5, countWords: true }
ul: { score: 1, countWords: true }
td: { score: 0, countWords: true }
hr: { score: 0, countWords: true }
pre: { score: 0, countWords: false }
ol: { score: 1, countWords: true }
wordValue: 0.2
- role: ["ISSUE_ASSIGNEE"]
multiplier: 0
rewards:
html:
br: { score: 0, countWords: true }
code: { score: 5, countWords: false }
p: { score: 0, countWords: true }
em: { score: 0, countWords: true }
img: { score: 5, countWords: true }
strong: { score: 0, countWords: false }
blockquote: { score: 0, countWords: false }
h1: { score: 1, countWords: true }
h2: { score: 1, countWords: true }
h3: { score: 1, countWords: true }
h4: { score: 1, countWords: true }
h5: { score: 1, countWords: true }
h6: { score: 1, countWords: true }
a: { score: 5, countWords: true }
li: { score: 0.5, countWords: true }
ul: { score: 1, countWords: true }
td: { score: 0, countWords: true }
hr: { score: 0, countWords: true }
pre: { score: 0, countWords: false }
ol: { score: 1, countWords: true }
wordValue: 0
- role: ["ISSUE_COLLABORATOR"]
multiplier: 1
rewards:
html:
br: { score: 0, countWords: true }
code: { score: 5, countWords: false }
p: { score: 0, countWords: true }
em: { score: 0, countWords: true }
img: { score: 5, countWords: true }
strong: { score: 0, countWords: false }
blockquote: { score: 0, countWords: false }
h1: { score: 1, countWords: true }
h2: { score: 1, countWords: true }
h3: { score: 1, countWords: true }
h4: { score: 1, countWords: true }
h5: { score: 1, countWords: true }
h6: { score: 1, countWords: true }
a: { score: 5, countWords: true }
li: { score: 0.5, countWords: true }
ul: { score: 1, countWords: true }
td: { score: 0, countWords: true }
hr: { score: 0, countWords: true }
pre: { score: 0, countWords: false }
ol: { score: 1, countWords: true }
wordValue: 0.1
- role: ["ISSUE_CONTRIBUTOR"]
multiplier: 0.25
rewards:
html:
br: { score: 0, countWords: true }
code: { score: 5, countWords: false }
p: { score: 0, countWords: true }
em: { score: 0, countWords: true }
img: { score: 5, countWords: true }
strong: { score: 0, countWords: false }
blockquote: { score: 0, countWords: false }
h1: { score: 1, countWords: true }
h2: { score: 1, countWords: true }
h3: { score: 1, countWords: true }
h4: { score: 1, countWords: true }
h5: { score: 1, countWords: true }
h6: { score: 1, countWords: true }
a: { score: 5, countWords: true }
li: { score: 0.5, countWords: true }
ul: { score: 1, countWords: true }
td: { score: 0, countWords: true }
hr: { score: 0, countWords: true }
pre: { score: 0, countWords: false }
ol: { score: 1, countWords: true }
wordValue: 0.1
- role: ["PULL_SPECIFICATION"]
multiplier: 0
rewards:
html:
br: { score: 0, countWords: true }
code: { score: 5, countWords: false }
p: { score: 0, countWords: true }
em: { score: 0, countWords: true }
img: { score: 5, countWords: true }
strong: { score: 0, countWords: false }
blockquote: { score: 0, countWords: false }
h1: { score: 1, countWords: true }
h2: { score: 1, countWords: true }
h3: { score: 1, countWords: true }
h4: { score: 1, countWords: true }
h5: { score: 1, countWords: true }
h6: { score: 1, countWords: true }
a: { score: 5, countWords: true }
li: { score: 0.5, countWords: true }
ul: { score: 1, countWords: true }
td: { score: 0, countWords: true }
hr: { score: 0, countWords: true }
pre: { score: 0, countWords: false }
ol: { score: 1, countWords: true }
wordValue: 0
- role: ["PULL_AUTHOR"]
multiplier: 2
rewards:
html:
br: { score: 0, countWords: true }
code: { score: 5, countWords: false }
p: { score: 0, countWords: true }
em: { score: 0, countWords: true }
img: { score: 5, countWords: true }
strong: { score: 0, countWords: false }
blockquote: { score: 0, countWords: false }
h1: { score: 1, countWords: true }
h2: { score: 1, countWords: true }
h3: { score: 1, countWords: true }
h4: { score: 1, countWords: true }
h5: { score: 1, countWords: true }
h6: { score: 1, countWords: true }
a: { score: 5, countWords: true }
li: { score: 0.5, countWords: true }
ul: { score: 1, countWords: true }
td: { score: 0, countWords: true }
hr: { score: 0, countWords: true }
pre: { score: 0, countWords: false }
ol: { score: 1, countWords: true }
wordValue: 0.2
- role: ["PULL_ASSIGNEE"]
multiplier: 1
rewards:
html:
br: { score: 0, countWords: true }
code: { score: 5, countWords: false }
p: { score: 0, countWords: true }
em: { score: 0, countWords: true }
img: { score: 5, countWords: true }
strong: { score: 0, countWords: false }
blockquote: { score: 0, countWords: false }
h1: { score: 1, countWords: true }
h2: { score: 1, countWords: true }
h3: { score: 1, countWords: true }
h4: { score: 1, countWords: true }
h5: { score: 1, countWords: true }
h6: { score: 1, countWords: true }
a: { score: 5, countWords: true }
li: { score: 0.5, countWords: true }
ul: { score: 1, countWords: true }
td: { score: 0, countWords: true }
hr: { score: 0, countWords: true }
pre: { score: 0, countWords: false }
ol: { score: 1, countWords: true }
wordValue: 0.1
- role: ["PULL_COLLABORATOR"]
multiplier: 1
rewards:
html:
br: { score: 0, countWords: true }
code: { score: 5, countWords: false }
p: { score: 0, countWords: true }
em: { score: 0, countWords: true }
img: { score: 5, countWords: true }
strong: { score: 0, countWords: false }
blockquote: { score: 0, countWords: false }
h1: { score: 1, countWords: true }
h2: { score: 1, countWords: true }
h3: { score: 1, countWords: true }
h4: { score: 1, countWords: true }
h5: { score: 1, countWords: true }
h6: { score: 1, countWords: true }
a: { score: 5, countWords: true }
li: { score: 0.5, countWords: true }
ul: { score: 1, countWords: true }
td: { score: 0, countWords: true }
hr: { score: 0, countWords: true }
pre: { score: 0, countWords: false }
ol: { score: 1, countWords: true }
wordValue: 0.1
- role: ["PULL_CONTRIBUTOR"]
multiplier: 0.25
rewards:
html:
br: { score: 0, countWords: true }
code: { score: 5, countWords: false }
p: { score: 0, countWords: true }
em: { score: 0, countWords: true }
img: { score: 5, countWords: true }
strong: { score: 0, countWords: false }
blockquote: { score: 0, countWords: false }
h1: { score: 1, countWords: true }
h2: { score: 1, countWords: true }
h3: { score: 1, countWords: true }
h4: { score: 1, countWords: true }
h5: { score: 1, countWords: true }
h6: { score: 1, countWords: true }
a: { score: 5, countWords: true }
li: { score: 0.5, countWords: true }
ul: { score: 1, countWords: true }
td: { score: 0, countWords: true }
hr: { score: 0, countWords: true }
pre: { score: 0, countWords: false }
ol: { score: 1, countWords: true }
wordValue: 0.1
payment:
autmaticTransferMode: false
githubComment:
post: true
debug: false
https://permit2-allowance.ubq.fi
evmPrivateEncrypted
parameterPartner private key (evmPrivateEncrypted
config param in conversation-rewards
plugin) supports 2 formats:
PRIVATE_KEY:GITHUB_OWNER_ID
PRIVATE_KEY:GITHUB_OWNER_ID:GITHUB_REPOSITORY_ID
Here GITHUB_OWNER_ID
can be:
Format PRIVATE_KEY:GITHUB_OWNER_ID
restricts in which particular organization (or user related repositories)
this private key can be used. It can be set either in the organization wide config either in the repository wide one.
Format PRIVATE_KEY:GITHUB_OWNER_ID:GITHUB_REPOSITORY_ID
restricts organization (or user related repositories) and a particular repository where private key is allowed to be used.
How to encrypt for you local organization for testing purposes:
curl -H "Accept: application/json" -H "Authorization: token GITHUB_PAT_TOKEN" https://api.github.com/orgs/ubiquity
x25519_PRIVATE_KEY
(which will be used in the conversation-rewards
plugin to decrypt encrypted wallet private key)PRIVATE_KEY:GITHUB_OWNER_ID
in the PLAIN_TEXT
UI text input where:PRIVATE_KEY
: your ethereum wallet private key without the 0x
prefixGITHUB_OWNER_ID
: your github organization id or user id (which you got from step 1)CIPHER_TEXT
fieldevmPrivateEncrypted
config parameterX25519_PRIVATE_KEY
environment variable in github secrets of your forked instance of the conversation-rewards
pluginTo enable and configure the automatic deduction of fees from generated rewards, you need to set specific environment variables:
PERMIT_FEE_RATE
:
5
for 5%, 2.5
for 2.5%).0
, or is not a valid number, the fee deduction process will be skipped entirely.PERMIT_TREASURY_GITHUB_USERNAME
:
ubiquity-os-treasury
).PERMIT_ERC20_TOKENS_NO_FEE_WHITELIST
(Optional):
0xTokenAddress1,0xTokenAddress2
). Ensure addresses are in the correct checksum format if applicable, although the check is case-insensitive.erc20RewardToken
configured for the current run matches any address in this whitelist, fees will not be applied for that specific reward calculation, even if PERMIT_FEE_RATE
and PERMIT_TREASURY_GITHUB_USERNAME
are set.Example Setup:
PERMIT_FEE_RATE=5
PERMIT_TREASURY_GITHUB_USERNAME=ubiquity-os-treasury
PERMIT_ERC20_TOKENS_NO_FEE_WHITELIST="0xSomeTokenAddress,0xAnotherTokenAddress"