Welcome to Userfeeds’s documentation!¶
Contents:
Overview¶
What is Userfeeds Platform?¶
Userfeeds Platform was envisioned as a infrastructure allowing developers to easily use and build better content rankings for their clients. We want to help developers by providing them ready to use APIs, libraries and algorithms that they can incorporate into their software.
What is a content ranking?¶
Some examples of content rankings are list of products on amazon.com, songs in playlist on spotify, news on nytimes, and all other places that have some content (links, articles, songs) sorted in particular way that you can access.
How Userfeeds Platform works?¶
Userfeeds Platform is composed of couple of parts that interact together to deliver rankings through HTTP APIs to your application.

First Userfeeds Platform reads all the information from supported blockchins and stores it in internal DB. We store all transactions and contract calls in Graph database and current balances in standard SQL database. When someone sends Claim data to Userfeeds Platform through HTTP or through Ethereum network, it is processed and inserted into Graph database for use in ranking algorithms. When application makes HTTP request to our Ranking API endpoint, an Ranking Engine is taking requested algorithm and applies it to current data stored in Graph database and SQL database and returns sorted entries for application to display to its user.
What blockchains are supported?¶
Currently we support Ethereum, Bitcoin, IPDB
What is Claim?¶
Claim is basic data entity in Userfeeds Platform. It may represent endorsement, like, upvote for given URL/Text/Identifier that is signed by one of supported signing methods.
What are supported signing methods?¶
You can sign Claim with ECDSA and send it through HTTP, or make Ethereum transaction with Claim data and it will be treated as signed.
Why should I use Userfeeds Platform?¶
- Our algorithms are open source and can be improved every time an issue arises
- You are able to create your own custom rankings
- We use blockchain as a source of ranking signals, eg. how much tokens are connected to given content, how stable were token holders endorsing given content, how involved they are in given token.
- You can monetize your app/site easily by providing sponsored ranking on your app/site
- You can deliver superior experiance for your users by customizing our ranking output based on your needs.
I’m a developer. How can I start using Userfeeds Platform?¶
You should go to Quick Start and API Reference to checkout how to use our read-only APIs and how to use Ethereum blockchain for pushing information into Userfeeds Platform. Later you can go to http://api.userfeeds.io/portal/, register as developer, and go to http://api.userfeeds.io/portal/apis/ API Catalog to request API Key. When you have API Key you can start using all of our HTTP APIs.
Features¶
Done¶
- Links Exchange
userfeeds-links
widget- Whitelisting
- Link bidding
- Rankings
- Links ranking
- Sponsored ranking
- Basic data synchronization
- Ethereum - ether
- mainnet
- ropsten
- rinkeby
- Simple sponsored Items App
- Claim transports
- HTTP
- Ethereum Contract
- Continuous deployment (dev environment)
Planned¶
- Stable data synchronization
- Ethereum - ether, tokens
- mainnet
- ropsten
- kovan
- rinkeby
- Bitcoin - value transactions
- BigchainDB (IPDB) - asset/value transactions
- Claim transports
- Ethereum Whisper
- BigchainDB (IPDB)
- Integration Tests Suite
- Link Exchange
- Production ready look&feel
- More
userfeeds-links
widget types - X links
- More
- Integrations with partners
- Status.im integration
- Pairing
- Auth Service
- Simple
userfeeds-button
widget - Simple Governance App
- Simple Attention Guard App
- Rankings
- Hold
- Public Database Access
- Data dumps
- Public instances
- External algorithm providers
- Github
- Gitlab
- Gist
- Bitbucket
- External fraud-detection data providers
Under consideration¶
- Distributed, decentralized, verifiable claims/data storage
Roadmap¶
Applications¶
Links¶
- Finish current views
- ShareApp - allow posting links to given contexts
Labels¶
- Web app gallery with pictures with labels, grouping based on labels
- Mobile gallery app with picture gouping based on labels
- ShareApp - allow sharing pictures with attached labels
StateOfTheDapps¶
- Integrate sponsored ranking for dapps
- Add sorting by hold for ether and other tokens
Status.im¶
- Integrate sponsored dapps page
- userfeeds-button endorse/star on each dapp
CryptoAuth¶
- claims signing
- claims sending through HTTP
- claims sending through web3.js (MetaMask, Mist)
- pairing support
- Support for import/export of identity to mobile app (ShareApp)
Clouds¶
- bring back clouds ui for most popular tokens
Algorithms¶
Labels¶
- Targets for context with their labels
- All labels attached to identifier
Hold¶
- Postgresql filled with hold data for tokens and ether
Piping¶
- Change algorithms to be albe to pipe | them through - links:contextID|timedecay:7days|groupby:id - links:contextID|hold|groupby:id - claims:contextID|hold - authored:contextID|timedecay:7days - labels:contextID|groupby
Widgets¶
userfeeds-links¶
- link list widget
userfeeds-button¶
- allow selecting transport
- connect with cryptoauth.io
Data synchronization¶
Tokens¶
- Add token transactions support
- Verify if postgresql fillers are working correctly
- Add token-hold data to postgresql
Claims¶
Move claims to blocks reader
- Based on Keccak-256 hash of:
- Claim(address,string)
- Claim(address,address,string)
- Look through all transaction recipts if it generates such topics and treat them as proper claims
Basic Concepts¶
Context¶
Identifier in format networkName:networkAddress
specifing to where given claims are destined.
Can be arbitrary string without special meaning. (needs to follow format)
More comples formats are possible, eg. networkName:networkAddress:Tag
depending on use case.
Quick Start¶
APIs that attempt to create Claims using HTTP require API key for access, before you start using them you should register and get your API Key at https://api.userfeeds.io/portal/apis/.
Read-only APIs do not require API key.
Get simple ranking for Ethereum¶
Those code snippets will return list of news shared to Ethereum context sorted according to our simple algorithm.
In python¶
import requests
RANKING_URL = "https://api.userfeeds.io/ranking/ethereum/claims/"
response = requests.get(RANKING_URL).json()
print("Simple ranking for Ethereum:\n")
for index, item in enumerate(response['items']):
print("{0}. {1}".format(index, item["target"]))
print("Score: {0}".format(item['score']))
print()
# Simple ranking for Ethereum:
#
# 0. http://example.com/one
# Score: 123.1234324
#
# 1. http://example.com/two
# Score: 32.234542343
In JavaScript (node.js)¶
const https = require('https');
const options = {
hostname: 'api.userfeeds.io',
port: 443,
path: '/ranking/ethereum/claims/',
method: 'GET'
};
console.log('Simple ranking for Ethereum:\n')
const req = https.request(options, (res) => {
let data = "";
res.setEncoding('utf8');
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
let ranking = JSON.parse(data);
for (let index in ranking.items) {
console.log(`${index}. ${ranking.items[index].target}`);
console.log(`Score: ${ranking.items[index].score}\n`);
}
});
});
req.on('error', (e) => {
console.error(e);
});
req.end();
// Simple ranking for Ethereum:
//
// 0. http://example.com/one
// Score: 123.1234324
//
// 1. http://example.com/two
// Score: 32.234542343
In JavaScript (browser)¶
<!DOCTYPE html>
<html>
<head>
<title>Simple ranking for Ethereum:</title>
</head>
<body>
<script>
fetch('https://api.userfeeds.io/ranking/ethereum/claims/')
.then(function(response) {
return response.json();
})
.then(function(ranking) {
var div = document.getElementById('ranking');
for (var i = ranking.items.length - 1; i >= 0; i--) {
var item = ranking.items[i];
div.innerHTML += i.toString() + '.<a href="' + item.target + '">' + item.target + '</a><br/>';
div.innerHTML += 'Score: ' + item.score + '<br/><br/>';
}
});
</script>
<h1>Simple ranking for Ethereum</h1>
<div id="ranking"></div>
</body>
</html>
Demo:
Get available algorithms for Ethereum¶
Sometimes you want to give your users more than one view onto the same dataset. You can get algorithms available for Ethereum context using those simple code snippets.
In python¶
import requests
RANKING_URL = "https://api.userfeeds.io/ranking/ethereum/"
response = requests.get(RANKING_URL).json()
print("Available algorithms for Ethereum:\n")
for index, item in enumerate(response['items']):
print("ID:", item["identifier"])
print("Summary:", item['description'])
print()
# Available algorithms for Ethereum:
#
# ID: simple
# Summary: Simple algorithm
#
# ID: sponsored
# Summary: Sponsored content algorithm
In JavaScript (node.js)¶
const https = require('https');
const options = {
hostname: 'api.userfeeds.io',
port: 443,
path: '/ranking/ethereum/',
method: 'GET'
};
console.log('Available algorithms for Ethereum:\n')
const req = https.request(options, (res) => {
let data = "";
res.setEncoding('utf8');
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
let ranking = JSON.parse(data);
for (let item of ranking.items) {
console.log(`ID: ${item.identifier}`);
console.log(`Summary: ${item.description}\n`);
}
});
});
req.on('error', (e) => {
console.error(e);
});
req.end();
// Available algorithms for Ethereum:
//
// ID: simple
// Summary: Simple algorithm
//
// ID: sponsored
// Summary: Sponsored content algorithm
In JavaScript (browser)¶
<!DOCTYPE html>
<html>
<head>
<title>Available algorithms for Ethereum:</title>
</head>
<body>
<script>
fetch('https://api.userfeeds.io/ranking/ethereum/')
.then(function(response) {
return response.json();
})
.then(function(ranking) {
var div = document.getElementById('ranking');
for (var i = ranking.items.length - 1; i >= 0; i--) {
var item = ranking.items[i];
div.innerHTML += 'ID: ' + item.identifier + '<br/>';
div.innerHTML += 'Summary: ' + item.description + '<br/><br/>';
}
});
</script>
<h1>Available algorithms for Ethereum</h1>
<div id="ranking"></div>
</body>
</html>
Demo:
Allow your users to sponsor content on your webpage¶
In JavaScript (browser)¶
<!DOCTYPE html>
<html>
<head>
<title>Sponsred ranking for Ethereum:</title>
<style type="text/css">
ul#choices li {
list-style: none;
display: inline-block;
}
ul#choices li img {
width: 200px;
height: auto;
}
div#ranking img {
width: 180px;
height: auto;
margin-right: 20px;
}
ul.winners li {
display: inline-block;
}
</style>
</head>
<body>
<h1>Winners:</h1>
<div id="ranking"></div>
Ranking address:<input type="text" id="address" value="rinkeby:0xfe5da6ae3f65e7d5ce29121a9f5872ffd83b45e6"/>
<button onclick="init();">Refresh</button>
<h1>Which is the best animal?:</h1>
<ul id="choices">
<li>
<img id="cat" src="img/cat.png">
<br/>
<button onclick="promote(this, 0.01);">Promote by 0.01 Ether</button><br/>
<button onclick="promote(this, 0.1);">Promote by 0.1 Ether</button><br/>
<button onclick="promote(this, 1);">Promote by 1 Ether</button>
</li>
<li>
<img id="dog" src="img/dog.png">
<br/>
<button onclick="promote(this, 0.01);">Promote by 0.01 Ether</button><br/>
<button onclick="promote(this, 0.1);">Promote by 0.1 Ether</button><br/>
<button onclick="promote(this, 1);">Promote by 1 Ether</button>
</li>
<li>
<img id="horse" src="img/horse.png">
<br/>
<button onclick="promote(this, 0.01);">Promote by 0.01 Ether</button><br/>
<button onclick="promote(this, 0.1);">Promote by 0.1 Ether</button><br/>
<button onclick="promote(this, 1);">Promote by 1 Ether</button>
</li>
<li>
<img id="bird" src="img/bird.png">
<br/>
<button onclick="promote(this, 0.01);">Promote by 0.01 Ether</button><br/>
<button onclick="promote(this, 0.1);">Promote by 0.1 Ether</button><br/>
<button onclick="promote(this, 1);">Promote by 1 Ether</button>
</li>
</ul>
<script>
var rankingAddress = null;
var content = {};
function init() {
rankingAddress = document.getElementById('address').value;
var choices = document.getElementById('choices').getElementsByTagName("li");
for (var i = choices.length - 1; i >= 0; i--) {
var img = choices[i].getElementsByTagName('img')[0];
content[img.id] = img.src;
}
displayRanking();
}
function displayRanking() {
fetch(`https://api.userfeeds.io/ranking/${rankingAddress}/sponsored/`)
.then(response => response.json())
.then(ranking => {
let div = document.getElementById('ranking');
let html = '<ul class="winners">';
for (let i = 0; i <= 3; i++) {
let item = ranking.items[i];
if (item) {
let src = content[toAscii(item.target)];
let score = item.score;
if (src) {
html += `<li><img src="${src}"><br/>Score: ${score}</li>`;
}
}
}
html += '</ul>';
div.innerHTML = html;
});
}
function promote(button, value) {
var img = button.parentElement.getElementsByTagName('img')[0];
// Send identifier as transaction data
var data = web3.fromAscii(img.id);
web3.eth.sendTransaction({to: rankingAddress.split(':')[1], data: data, value: web3.toWei(value, 'ether')}, function(err, address) {
if (!err)
alert("Thank You!");
});
}
function toAscii (hex) {
var str = '',
i = 0,
l = hex.length;
if (hex.substring(0, 2) === '0x') {
i = 2;
}
for (; i < l; i+=2) {
var code = parseInt(hex.substr(i, 2), 16);
if (code === 0) continue; // this is added
str += String.fromCharCode(code);
}
return str;
};
init();
</script>
</body>
</html>
Demo:
API Reference¶
API root is https://api.userfeeds.io/
Note
APIs that attempt to create Claims using HTTP require API key for access, before you start using them you should register and get your API Key at https://api.userfeeds.io/portal/apis/.
Read-Only APIs do not require API key.
Ranking¶
Get ranking for given context according to algorithm.
read-only
Available algorithms are described in Algorithms section.
$ curl https://api.userfeeds.io/ranking/ethereum/all/
-
GET
/ranking/
(string: context)/
(string: algorithm)/
¶ Example request:
GET /ranking/ethereum/all/ HTTP/1.1 Host: api.userfeeds.io Accept: application/json
Example response:
HTTP/1.1 200 OK Vary: Accept Content-Type: application/json { "items": [ { "target": "https://ethereum.org/", "score": 123 }, { "target": "https://userfeeds.io/", "score": 88 } ] }
Algorithms¶
Get all available algorithms for given context
read-only
$ curl https://api.userfeeds.io/ranking/ethereum/
-
GET
/ranking/
(string: context)/
¶ Example request:
GET /ranking/ethereum/ HTTP/1.1 Host: api.userfeeds.io Accept: application/json
Example response:
HTTP/1.1 200 OK Vary: Accept Content-Type: application/json { "items": [ { "description": "Simple hold-based algorithm", "source": "https://github.com/Userfeeds/Algorithms/simple/" "identifier": "simple" }, { "description": "All claims sorted by time", "source": "https://github.com/Userfeeds/Algorithms/all/" "identifier": "all" } ] }
Storage¶
Post claim
to Userfeeds Platform.
Note
This API requires Authorization header. Register at https://api.userfeeds.io/portal/apis/ to get one.
$ curl -X POST https://api.userfeeds.io/storage/ \
-H "Content-Type: application/json" \
-H "Authorization: API_KEY" \
--data @/path/to/claim.json
-
POST
/storage/
¶ Example request:
POST /storage/ HTTP/1.1 Host: api.userfeeds.io Content-Type: application/json Authorization: API_KEY Content-Length: 104
Example response:
HTTP/1.1 202 ACCEPTED Content-Type: application/json Content-Length: 27 {"status": "Accepted"}
Verification¶
Verify claim
signature.
Note
This API requires Authorization header. Register at https://api.userfeeds.io/portal/apis/ to get one.
$ curl -X POST https://api.userfeeds.io/verify/ \
-H "Content-Type: application/json" \
-H "Authorization: API_KEY" \
--data @/path/to/claim.json
-
POST
/verify/
¶ Example request:
POST /verify/ HTTP/1.1 Host: api.userfeeds.io Content-Type: application/json Authorization: API_KEY Content-Length: 104
Example response:
HTTP/1.1 200 OK Content-Type: application/json Content-Length: 27 {"valid": true, "error": null}
HTTP/1.1 400 BAD REQUEST Content-Type: application/json Content-Length: 27 {"valid": false, "error": "Invalid message format"}
Contexts¶
Get contexts supported by Userfeeds Platform
TODO
Tokens¶
Get token balance for given identifier
TODO
Guides¶
In this section we will try to introduce you to more in-depth look at Userfeeds Platform
API Authorization¶
APIs that write to Userfeeds Platform require Authorization
header to be present with API key as a value.
Note
API key can be requested at https://api.userfeeds.io/portal/apis/, after registration. API key requests are currently approved manually so it may take up to 24h for you request to be approved.
Example of Authorized request would be:
cURL:
$ curl -X POST https://api.userfeeds.io/verify/ \
-H "Content-Type: application/json" \
-H "Authorization: YourAPIKey123" \
--data @/path/to/claim.json
Python:
import json
import requests
claim = open('claim.json').read()
claim = json.loads(claim)
response = requests.post(
"https://api.userfeeds.io/storage/",
json=claim,
headers={
"Authorization": "YourAPIKey123"
})
print(response.content)
Claims Signatures¶
Signed claims are created by adding special signature
object to claim.
All claims inside Userfeeds Platform databases are signed (in some way). Claims sent to Userfeeds Platform through HTTP needs to be signed prior to being sent in contrast to claims sent via Ethereum transaction which signature is created on Userfeeds Platform side based on transaction containing the claim.
signature
object structure inside claim
looks like this:
{
"context": "...",
"claim": {
"target": "..."
},
"signature": {
"type": "TYPE",
"creator": "CREATOR",
"signatureValue": "SIGNATURES_VALUE"
}
}
type
¶
It descries what kind of signature we are dealing with.
Full list of supported types can be found at claim.signature.type reference.
creator
¶
Identifier of entity that created the signature.
signatureValue
¶
Signature allowing for verification that creator
signed this claim
.
Create signed Claim (ECDSA)¶
Following code will show you how to create signed claim with signature type of ecdsa.*.
Simple signing script written in Python.
import ecdsa
import json
import sys
import binascii
import hashlib
## Read claim from file
claim = open(sys.argv[-1]).read()
claim = json.loads(claim)
## Generate Keys
private_key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p)
public_key = private_key.get_verifying_key()
## Sign claim
message = json.dumps(claim, separators=(',', ':'), sort_keys=True).encode('utf8')
creator = public_key.to_string()
creator = binascii.hexlify(creator)
creator = creator.decode("utf8")
signature = private_key.sign(message, hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_der)
signature = binascii.hexlify(signature)
signature = signature.decode("utf8")
claim["signature"] = {
"type": "ecdsa.prime256v1",
"creator": creator,
"signatureValue": signature
}
## Print signed claim
print(json.dumps(claim, separators=(',', ':'), sort_keys=True))
Save this code to sign.py
and run it like this:
$ pip install ecdsa
$
$ python sign.py /path/to/claim.json
Simple signing script written in Javascript.
let KJUR = require('jsrsasign');
let serialize = require('canonical-json');
let fs = require('fs');
// Read claim from file
let claim = fs.readFileSync(process.argv[process.argv.length - 1]);
claim = JSON.parse(claim);
// Generate Keys
let curve = "secp256k1";
let keypair = KJUR.KEYUTIL.generateKeypair("EC", curve);
let sig = new KJUR.crypto.Signature({"alg": "SHA256withECDSA"});
// Sign claim
let message = serialize(claim);
let creator = keypair.pubKeyObj.pubKeyHex;
sig.init(keypair.prvKeyObj);
sig.updateString(message);
let signature = sig.sign();
claim. signature = {
type: "ecdsa." + curve,
creator: creator,
signatureValue: signature
};
// Print signed claim
console.log(serialize(claim));
Save this code to sign.js
and run it like this:
$ npm install canonical-json jsrsasign
$
$ node sign.js /path/to/claim.json
Verify claim signature¶
$ curl -X POST https://api.userfeeds.io/verify/ \
-H "Content-Type: application/json" \
-H "Authorization: API_KEY" \
--data @/path/to/claim.json
Sponsored vs Organic Rankings¶
TODO
Organic ranking with interface token¶
Warning
NOT IMPLEMENTED!
You can pass additional token to organic rankings to inject sponsored results into organic rankings. Those results will be marked with sponsored: true key in items list.
example:
GET /ranking/ORGANIC_TOKEN/SIMPLE_RANKING/?sponsored=INTERFACE_TOKEN
{
"items": [
{
"value": "http://organic.com/art/1",
"score": 1234
},
{
"value": "http://organic.com/art/1",
"score": 0,
"sponsored": true
},
{
"value": "http://organic.com/art/1",
"score": 1100
},
...
]
}
TODO...
Claims¶
Claims are the smallest self contained piece of information inside Userfeeds Platform. All claims are cryptographically signed or have reference to their cryptographic origin (eg. transaction on blockchain)
Structure¶
Overview¶
{
"context": "CONTEXT",
"type": ["TYPEA", "TYPEB", "..."],
"claim": {
"target": "TARGET",
"additional": "fields",
"go": ["here", "..."]
},
"credits": [
{
"type": "interface",
"value": "INTERFACE IDENTIFIER"
}
],
"signature": {
"type": "TYPE",
"creator": "CREATOR",
"signatureValue": "SIGNATURE"
}
}
context
¶
optional
Context field is used to denote destination of given claim. It can be interpreted as name of any topic/thing that people might want to share information about. Usually it will have something to do with blockchain space (but it doesn’t have to).
For example if you would like to share some information to all people interested in Ethereum blockchain you will send claim with ethereum
as context.
Other examples of contexts might be:
- ethereum
- ethereumclassic
- bitcoin
- ethereum:0x4564567890abcdef....abc
- ethereumclassic:0x4564567890abcdef....abc
- myspecialcontext
- companyx
- companyx:departmenty
ethereum:0x123....456
context identifiers are interpreted as object 0x123....456 on ethereum and usually will be used to share information about contracts/addresses on given blockchain.
Special contexts starting with userfeeds: are technical context and have special meaning in Userfeeds Platform. eg. userfeeds:pairing will create special PAIRED relationship allowing one to connect their cryptocurrency holdings with additional public key that will only be used to sign claims.
type
¶
optional
Type describes additional data present in claim object.
Example can be labels type, with this type claim object needs to have labels key with array of values.
Description of all supported types can be found at Types
claim
¶
This key is used to store user provided information and it is mandatory for all claims.
claim
object will always have target
key and additional fields depending on type
array.
target
¶
target
value identifies target object that user wants to share or tag with additional information.
Some examples of proper target
values:
- http://some.url/path/
- text:base64:base64encodedtext
- ipfs:somehash
- ipdb:somehash
- claim:claimsignaturehash
- mediachain:somehash
- isbn:0451450523
- isan:0000-0000-9E59-0000-O-0000-0000-2
- btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a
- ethereum:0x4564567890abcdef...abc
- bitcoin:0x4afebcdef...123
signature
¶
This field is generated to denote ownership of claim and can be cryptographic signature or a pointer to cryptographically secure origin of claim.
type
¶
Describes what type of signature we are dealing with. It could be ecdsa.secp256r1
for elliptic curve signature or ethereum.transaction
for claim origination from Ethereum blockchain.
Supported Signature Types¶
ecdsa.prime192v1
- In python: ecdsa.NIST192p
ecdsa.secp256r1
/ecdsa.prime256v1
- In python: ecdsa.NIST256p
ecdsa.secp224r1
- In python: ecdsa.NIST224p
ecdsa.secp384r1
- In python: ecdsa.NIST384p
ecdsa.secp521r1
- In python: ecdsa.NIST521p
ecdsa.secp256k1
- In python: ecdsa.SECP256k1
ethereum.transaction
- Claims posted on ethereum blockchain will be verified by comparing blockchain content with claim content.
creator
¶
This identifies public key or address that signed claim.
Format: identifier
- hex:04861127b14bf0036e...ef7127b114988057
- rinkeby:0x1234567890abcdef...1456
- ethereum:0x1235...145
- bitcoin:0x123456...1234
signatureValue
¶
This key holds raw signature value as produced by signing algorithm, or it can be transaction hash or any valid identifier of externally verifiable origin of claim.
Types¶
Basic¶
Basic claim:
{
"claim": {
"target": "http://some.url/path/"
},
"context": "ethereum:0x4564567890abcdef....abc",
"signature": {
"creator": "94d1aa6655d931294d524cf52b0df866976f89774bac38a730cf20e2d51dd24d34efc2bbb4d5bba91a7a6582511491dde1803dcdf7fd55550cf3132aae16077a",
"signatureValue": "304402203dac2176721d7e05cd8c580a27a504b64b0a8ee171b18a07630201cbed979ac7022013faf8735f90b957ca4656efb17349856ac22894aab553db136b7dfc2b03ede4",
"type": "ecdsa.prime256v1"
}
}
With acknowledgment of interface from which claim was created:
{
"claim": {
"target": "http://some.url/path/"
},
"context": "ethereum:0x4564567890abcdef....abc",
"credits": [
{
"type": "interface",
"value": "http://blog.example.com/path/"
}
],
"signature": {
"creator": "0df1d4915347bcae90a0696c9efd6300e33b610d31130c3049d329fa61af138de7a7ee55f99057fd8d39c4664be9f1c34361c237433b34c8f523c5858f9fb9a0",
"signatureValue": "304502206c243684007c9e412612b5d1a371b20eb146652e4b149bb1fc0e6da437e7f728022100b8c77983949feac478de449751587be82d5b37ff30b257da04f2352aab5f8793",
"type": "ecdsa.prime256v1"
}
}
link
¶
Additional keys:
title
stringsummary
string
{
"claim": {
"summary": "summary",
"target": "http://some.url/path/",
"title": "title"
},
"context": "rinkeby:0xfe5da6ae3f65e7d5ce29121a9f5872ffd83b45e6",
"credits": [
{
"type": "interface",
"value": "http://blog.example.com/path/"
}
],
"signature": {
"creator": "ffc8c2f39e8a302bf9ca37b06fa9014f0cd3c85900c3d8b771f31a91ce33c050948faedad14d73a4f6c41f5937c3a010b5af24055092c136b7a667fee2249f87",
"signatureValue": "304602210093c55c30be1868de005def995aefb391d7ff20f235457b37a01e6921427701f80221008e5fc2afc2c9124660a57f9a2c9f4be1187afe3bc4b3c67a30e9a4c52747f4f7",
"type": "ecdsa.prime256v1"
},
"type": [
"link"
]
}
labels
¶
Additional keys:
labels
array of string
{
"claim": {
"labels": [
"Good",
"Book",
"Cats"
],
"target": "http://some.url/path/"
},
"context": "rinkeby:0xfe5da6ae3f65e7d5ce29121a9f5872ffd83b45e6",
"signature": {
"creator": "de9965ce03cf6f960a7efe423633409a0052ad8f9f2100e27026ad94551d4d69058c0a263dbd0cacf999ca3e97ddcc0afae5051e91dc42c4ca008e4c7c5c0ddb",
"signatureValue": "3044022069457927f1fc06b26467a7cc93c99085efea4d8811c6979ffab9ba2196be5ad702201587d3f88d2e9058d6ce48708ddf8713b07017a311ce713b566a1829ea516e41",
"type": "ecdsa.prime256v1"
},
"type": [
"labels"
]
}
Transports¶
HTTP¶
You can send signed claims through Userfeeds Platform HTTP Gateway
Properties:
- In-transport secrecy
- the claim cannot be sniffed on transport thanks to HTTPS connection and will only be available to outside world after it’s incorporated into Userfeeds Platform through rankings APIs and database dumps.
- Independent distribution
- the claim exists only inside Userfeeds Platform database and will be distributed with Userfeeds Platform database dumps.
Example of posting claim directly to Userfeeds Platform through HTTP transport
$ curl \
-X POST https://api.userfeeds.io/storage/ \
-H "Content-Type: application/json" \
-H "Authorization: 59049c8fdfed920001508e2a03414df648e34ea665f544a17d5c113b" \
-d '{"claim":{"target":"http://some.url/path/"},"context":"ethereum:0x4564567890abcdef....abc","signature":{"creator":"82fb68fc14719b94b36e99e588c9988458ca187d4791463164285bb064458232c4bb5bb638158096f4ed957e275e2e1576d1b24cca81ceb3a53cd7493ae88474","signatureValue":"c675d123fb1e99ffe59e7626c655806ebe47ec1ca4100b953029731159c2e14f4461e2ebf7f43a071b847b57cbcbd66bd8fa9451a5e27c4db4bbaec3cdd16b02","type":"ecdsa.prime256v1"}}'
Ethereum Transaction¶
You can send claim through Ethereum Blockchain transaction.
Note
Userfeeds Platform does not yet monitor every transaction for potential claims
To send a claim through transaction you need to call special contracts that are monitored by Userfeeds Platform.
Properties:
- In-transport secrecy
- The claim is available from the moment it’s distributed through Ethereum network and can be sniffed before it reaches desired ranking.
- Independent distribution
- The claim will be a part of Etherum Blockchain and will be available from all copies of the blockchain.
Contracts¶
With value transfer
Code
pragma solidity ^0.4.11;
contract Userfeeds {
event Claim(address sender, address userfeed, string data);
function post(address userfeed, string data) payable {
userfeed.transfer(msg.value);
Claim(msg.sender, userfeed, data);
}
}
ABI
[
{
"constant":false,
"inputs":[
{ "name":"userfeed", "type":"address" },
{ "name":"data", "type":"string" }
],
"name":"post",
"outputs":[],
"payable":true,
"type":"function"
},
{
"anonymous":false,
"inputs":[
{ "indexed":false, "name":"sender", "type":"address" },
{ "indexed":false, "name":"userfeed", "type":"address" },
{ "indexed":false, "name":"data", "type":"string" }
],
"name":"Claim",
"type":"event"
}
]
Addresses
- Mainnet: ...
- Rinkeby: 0x0a48ac8263d9d79768d10cf9d7e82a19c49f0002
- Ropsten: 0xa845c686a696c3d33988917c387d8ab939c66226
- Kovan: 0xebe67ecee8f7a222e4451f04dca903cb2bfead7f
Without value transfer
Code
pragma solidity ^0.4.11;
contract Userfeeds {
event Claim(address sender, string data);
function post(string data) {
Claim(msg.sender, data);
}
}
ABI
[
{
"constant":false,
"inputs":[
{ "name":"data", "type":"string" }
],
"name":"post",
"outputs":[],
"payable":false,
"type":"function"
},
{
"anonymous":false,
"inputs":[
{ "indexed":false, "name":"sender", "type":"address" },
{ "indexed":false, "name":"data", "type":"string" }
],
"name":"Claim",
"type":"event"
}
]
Addresses
- Mainnet: ...
- Rinkeby: 0x09dcdf34e0c28b106fdfe51009cb71ae92bf8bbc
- Ropsten: 0x5c3fe6b94b57c1e294000403340f12f083e71b83
- Kovan: 0x7d9ab4747413f470306a9fb1dab4092cfbc3355e
Whisper Protocol¶
TODO: Direct message TODO: Broadcast
IPDB (BigchainDB)¶
TODO
Algorithms¶
Userfeeds Platform allows running community written algorithms on too of data gathered from all supported sources like Ethereum blockchain (mainnet, ropsten, rinkeby, kovan, whisper messages), IPDB (BigchainDB) or direct messages.
Algorithms can be referenced by their identifier that depends on where the author decided to share them.
Some of algorithm sources and naming schemes are:
- Github -
github:username.repository.filename
- Bitbucket -
bitbucket:username.repository.filename
- Gist -
gist:hash
Note
More esoteric sources are possible, like downloading code from blockchain or encrypted algorithm code but their support is not planned at the moment.
Warning
Current support is limited to built-in algorithms through their short identifiers.
Built-in algorithms¶
Source code: https://github.io/Userfeeds/algorithms
All algorithms require one information: context
.
They will get all claims intended for given context
and sort them
according to their specification.
Note
Built-in algorithms can be referenced normally via eg. github:username.repo.filename
or by their short name eg. latest
Claims (TODO)¶
Identifier: claims
, github:Userfeeds.algorithms.claims
The simples algorithm of all. It just returns 100 latest claims for given context
Parameters¶
type
- string
- optional
Type of claims to return
Example data¶
{
"items": [
{
"created_at": 1499676056549,
"id": "0xd10409e340b5f41be0c0c8db85a61b2db0e5f4fb6c87fd3a5e4b73adc9bee9bf",
"score": 1000000000000000000,
"target": "https://userfeeds.io/"
},
{
"created_at": 1499676053781,
"id": "0xcd05844f64d472d8aeceeafc1c14f377bc664a9f94574ee820128254c962bfdc",
"score": 5000000000000000000,
"target": "https://example.com/"
},
{
"created_at": 1499676051855,
"id": "0xaa6b523d72d1f0e9b00689f494ac622804e0552e55c939cabc97381bd401732b",
"score": 0,
"target": "0x64537b9f7c9d85bae5c52cb4fa2307e7da14c6f1cae6d710b66a4ddcc059e1a8"
},
{
"created_at": 1499676051757,
"id": "0x64537b9f7c9d85bae5c52cb4fa2307e7da14c6f1cae6d710b66a4ddcc059e1a8",
"score": 100000000000000,
"target": "https://some.path/"
}
]
}
Authored (TODO)¶
Identifier: authored
, github:Userfeeds.algorithms.authored
This algorithm return all claims that were authored by context.
In other words all claims that have AUTHORED
relation to Account
node with id
equal to context
.
Parameters¶
type
- string
- optional
Type of claims to return
Example data¶
{
"items": [
{
"created_at": 1499676051855,
"id": "0xaa6b523d72d1f0e9b00689f494ac622804e0552e55c939cabc97381bd401732b",
"score": 0,
"target": "0x64537b9f7c9d85bae5c52cb4fa2307e7da14c6f1cae6d710b66a4ddcc059e1a8"
},
{
"created_at": 1499675901184,
"id": "0xc3921abdd00ca53925e5a5b8559537d44e19165ff3ad4e89daf0bb034a4afa21",
"score": 10000000000000000,
"target": "Lorem ipsum dolor sit amet, consectetur adipisicing elit"
},
{
"created_at": 1499156515246,
"id": "0x85b6643f69e746c7525fc3ed88e1f18241068bcff6516fbf2dd0d967ebdaf390",
"score": 0,
"target": "0x794d0c42fc35af7eba9216261ea3799e19d079d24dcf795fda94acf20ad298f8"
}
]
}
Sponsored¶
Identifier: sponsored
, github:Userfeeds.algorithms.sponsored
This algorithm is different form other as it does not use claims
as a source of information but more low level transaction.input
and as such works only for contexts like ethereum
.
It just takes whatever is inside transaction.input
sums up all transaction.value
, sorts the result and returns those inputs as target
.
The idea is that developer would send inside transaction.input
only values that make sense for their application and only this application can display appropriate content for each entry.
An example application can be a gallery of images with identifiers like img1
img2
... Using those identifiers as transaction.input
values, developer can create special sponsored gallery in his application that will display images sorted by overall value of ether used to promote them.
Example application can be found at Quick Start.
Example data¶
{
"items": [
{
"bids": 2,
"score": 1.01,
"target": "0x636174"
},
{
"bids": 1,
"score": 1.0,
"target": "0x62697264"
},
{
"bids": 1,
"score": 0.333,
"target": "0x6b616e6761726f6f"
},
{
"bids": 1,
"score": 0.01,
"target": "0x646f67"
}
]
}
Links¶
Identifier: links
, github:Userfeeds.algorithms.links
Time based algorithm that collects all claims with type link
and sorts them according to formula:
period = 7 days
score = Sum(
if now - claim.create_at > period:
0
else:
claim.value / period * (now - claim.create_at)
)
Parameters¶
period
- string
- optional
- default: 7 days
Period of time decay to use during calculations
whitelist
- string
- optional
When present all claims type
whitelist
andclaim.signature.creator
equal towhitelist
parameter will be used as filtering mechanism. Whitelisting claim needs to specify link claimclaim.signature.signatureValue
in it’sclaim.target
.
Whitelisting¶
Ads algorithm has special property that allows for arbitrary list filtering based on whitelist
parameter passed to it.
It works like that:
Algorithm gathers all claims with type link
intended for given context
and compares their signature.signatureValue
with claim.target
value of claims with signature.creator
equal to whitelist
parameter.
Example:
Link claims
context | type | target | signatureValue |
---|---|---|---|
ethereum:0xA | link | http://some.url/1 | hashABC |
ethereum:0xA | link | http://some.url/2 | hashDEF |
ethereum:0xA | link | http://some.url/3 | hashGHI |
Whitelist claims
target | creator |
---|---|
hashABC | ethereum:0xjohn |
hashGHI | ethereum:0xjohn |
hashABC | ethereum:0xalice |
hashDEF | ethereum:0xbob |
Above combination will produce two links with whitelist
parameter equal to ethereum:0xjohn
:
target |
---|
http://some.url/1 |
http://some.url/3 |
with whitelist
equal to ethereum:0xalice
it will produce one link:
target |
---|
http://some.url/1 |
and with ethereum:0xbob
:
target |
---|
http://some.url/2 |
if you omit whitelist
in api call the algorithm will produce full set of links:
target |
---|
http://some.url/1 |
http://some.url/2 |
http://some.url/3 |
Example data¶
{
"items": [
{
"id": "0xdddf6852e4e239cba808ebdb620535a30189cb5e66b2a4bdbc46e41c844543d6",
"score": 340000000000000000,
"summary": "Has nice pipelines",
"target": "https://bitbucket.org",
"title": "Bitbucket"
},
{
"id": "0xbc7dfcd2a2148cda1f763fb2d4713911c55c9f0d3fa56d62264c085f550a2586",
"score": 321923548932000000,
"summary": "Best social platform",
"target": "https://github.com",
"title": "GitHub"
},
{
"id": "0x943e5c024f04acd0352095a09c71b4e5ce6e5d789da588ad6aacdec859917cd6",
"score": 10000000000000000,
"summary": "Userfeeds Platform",
"target": "https://userfeeds.io/",
"title": "Userfeeds"
},
{
"id": "0xb4ed77fd554929f28c255279c307aedbd39510437e405693e5d84fd6a69b8fca",
"score": 10000000000000000,
"summary": "Status",
"target": "https://status.im/",
"title": "Status.im"
}
]
}
Hold (TODO)¶
Identifier: hold
, github:Userfeeds.algorithms.hold
Simple algorithm that collects all claims and sorts them according to cumulative hold
. Hold is holdings*time
for given token. If someone has 10 ETH for 1 day then we can say he has hold of value 10, if he still has 10 ETH during next 6 days his hold
will raise to 70.
Writing ranking algorithms¶
The best way to start writing your own algorithms is to look at the built-in one at https://github.io/Userfeeds/algorithms
TODO: ^^ more effort here please...
Data available for ranking algorithms¶
Database dumps can be found here:
Those data dumps are updated TODO:daily/hourly/constantly?
and can be used for development and data analysis.
TODO: Import instructions
Note
Direct database access will be possible to our read-only instances for projects that require lower delays.
Claims¶
TODO
Balances and Aggregated data¶
TODO
Helper functions¶
TODO
Testing your algorithm¶
Importing database¶
Apps¶
In this section we will describe basic applications built using Userfeeds Platform.
Apps overview can be found at https://userfeeds.io/apps.html
Links¶
Quick Start¶
Widgets configurator: https://userfeeds.io/apps/widgets/#/configurator/ Widgets demo: https://userfeeds.io/demo
Overview¶
Links app allows Publishers to integrate widgets into their web and mobile apps for presenting links and Advertisers to buy presentation space directly from given Publisher. This simple scheme allows Publishers to monetize their interfaces directly.
Whitelisting¶
Publisher has an option to choose which links he wants to display. He can either self-manage links by whitelisting them by hand or outsource this task to someone else. All he needs to do is to set whitelist option to desired identifier. This identifier will be used to query Claim.signature.creator field. It can be set to public address from metamask/mist if Publisher wants to manage links by himself or to any other identifier that will be used by some bot/AI to post whitelist claims.
Widgets¶
Links App is based on userfeeds-links widget.
Algorithms¶
Link App uses custom algorithm ads to provide basic advertise-like experiance.
Example response:
{
"items": [
{
"target": "http://target.one/",
"score": 123,
"title": "Title One",
"summary": "Lorem ipsum dolor incididunt officia cillum aute incididunt nisi exercitation voluptate elit.",
"bids": 10,
"id": "abc"
},
{
"target": "http://target.two/",
"score": 123,
"title": "Title Two",
"summary": "Lorem ipsum dolor incididunt officia cillum aute incididunt nisi exercitation voluptate elit.",
"bids": 10,
"id": "abcd"
},
{
"target": "http://target.three/",
"score": 123,
"title": "Title Three",
"summary": "Lorem ipsum dolor incididunt officia cillum aute incididunt nisi exercitation voluptate elit.",
"bids": 10,
"id": "abcde"
}
]
}
Source: https://github.com/Userfeeds/Algorithms/links/
This algorithm uses time-decay with 7 days decay period. (TODO)
Score key represents cumulated ETH value calculated based time-decay. Time decay is calculated from the moment of posting link so if publisher is using whitelisting mechanism it can have lower score if publisher takes long time to whitelist link. We encourage advertisers to post links with low amount of ether attached first and only after whitelisting adding more ether to them.
For example if someone pays 10 ETH for link it will have score 10 ETH at this moment. After 1 day the score will drop linearly to 10 * 6/7 ~ 8.57. If at this moment strengthening claim will be created with link claim id as target the score will increase to 18.57.
(TODO: add graphs)
Claims¶
Links App uses type=`link` claim for posting new link and basic claims for approving (whitelisting).
Posting Link Claim:
{
"context": "ethereum:0x4564567890abcdef....abc",
"type": ["Link"],
"claim": {
"target": "http://some.url/path/",
"title": "title",
"summary": "summary"
},
...
}
This claim type has two additional keys compared with basic claim.
title will be used as link title summary will be used as extended text (might not be displayed on some widgets)
Whitelisting claim is essentially a basic claim:
{
"claim": {
"target": "claim:signatureOfLinkClaim",
},
"signature": {
"type": "...",
"creator": "ethereum:0x1234567890abcdef....123",
"signatureValue": "..."
}
}
Widgets¶
This section describes details about widgets options and implementation.
Widgets configurator: https://userfeeds.io/apps/widgets/#/configurator/
userfeeds-links¶
Common parameters:
context
- string
- required
- values: Context
identifier specifing source of links and also destination of funds sent with link claims.
type
- string
- optional
- default: single
- values: single, list
specifies if widget should display one link at a time and rotate them or display all links in a list
limit
- number
- optional
- default: 10
Limit number of links taken from API
algorithm
- string
- optional
- default: ads
- values: Algorithms
name of the algorithm to use for links filtering/sorting
whitelist
- string
- optional
- values: Whitelisting
identifier specifing source of filtering whitelist for claims
Events¶
Lifecycle events
linksLoadStart
- when a request to ranking API is made
linksLoadSuccess
- when a request to ranking API finished successfully and returned non-empty list
linksLoadError
- when a request to ranking API failed for some reason (e.g. no network, 5xx response)
linkDisplay
- when user is shown with specific link from list (i.e. just after Links Load Success and when user or timeout changes link)
User interactions
linkOpen
- when currently displayed link is opened/clicked
widgetOpen
- when widget details is opened (i.e. modal on web, browser on Android)
JavaScript¶
Source: https://github.com/Userfeeds/JS-Widgets
Latest version is available here: https://cdn.jsdelivr.net/npm/@userfeeds/links
JS specific options
size
- string
- optional
- default: rectangle
- values: rectangle, leaderboard
Sample usage:
<userfeeds-links
context="rinkeby:0xcd73518680ab60ec2253841909d3448bc60f0665"
>
</userfeeds-links>
<script src="https://cdn.jsdelivr.net/npm/@userfeeds/links"></script>
--------------------------------
<userfeeds-links
type="single"
size="rectangle"
algorithm="internal"
context="rinkeby:0xcd73518680ab60ec2253841909d3448bc60f0665"
whitelist="rinkeby:0x123456...987"
>
</userfeeds-links>
<script src="https://cdn.jsdelivr.net/npm/@userfeeds/links"></script>
Events¶
TODO: Registering one events
Android¶
Source: https://github.com/Userfeeds/Android-Widgets
Code examples
Add widget via xml:
<io.userfeeds.widget.LinksViewPager
xmlns:userfeeds="http://schemas.android.com/apk/res-auto"
android:id="@+id/my_links_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
userfeeds:context="ethereum:0x0406735fC1a657398941A50A0602eddf9723A6C8"/>
--------------------------
<io.userfeeds.widget.LinksViewPager
xmlns:userfeeds="http://schemas.android.com/apk/res-auto"
android:id="@+id/my_links_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
userfeeds:algorithm="ads"
userfeeds:context="ethereum:0x0406735fC1a657398941A50A0602eddf9723A6C8"
userfeeds:whitelist="ethereum:0x123...456"/>
Or via code:
TODO
Events¶
linksLoadCancel
- when user exits the screen before links load
linkLongClick
- user long clicks the link; currently equivalent to Widget Details Open
linkSwipe
- user swipes between links; will be followed by
linkDisplay
userfeeds-button¶
This is universal button, it can be deployed anywhere to create basic or labeling claim
Options:
target
- string
- required
Specifies claim
target
value. Identifier that will be used astarget
in API responsetext
- string
- required
Button label
labels
- comma separated list of strings
- optional
Claim labels, if this is set claim type will have labels in it.
value
- number
- optional
Amount of coins to send with claim, if this is set, possible transports are reduced to value-transferable only (blockchains).
JavaScript¶
Source: https://github.com/Userfeeds/JS-Widgets
Latest version is available here: https://cdn.jsdelivr.net/npm/@userfeeds/button
Sample usage:
<userfeeds-button
text="Like"
target="http://myblog.com/"
context="rinkeby:0xcd73518680ab60ec2253841909d3448bc60f0665"
>
</userfeeds-button>
<script src="https://cdn.jsdelivr.net/npm/@userfeeds/button"></script>
-----------------------
<userfeeds-button
text="Like"
labels="like"
target="http://myblog.com/"
context="rinkeby:0xcd73518680ab60ec2253841909d3448bc60f0665"
>
</userfeeds-button>
<script src="https://cdn.jsdelivr.net/npm/@userfeeds/button"></script>
-----------------------
<userfeeds-button
text="This is a Cat"
labels="cat"
target="http://myphotos.com/some-photo"
context="rinkeby:0xcd73518680ab60ec2253841909d3448bc60f0665"
>
</userfeeds-button>
<script src="https://cdn.jsdelivr.net/npm/@userfeeds/button"></script>
-----------------------
<userfeeds-button
text="Upvote"
labels="up,good,positive"
target="http://myphotos.com/some-photo"
context="rinkeby:0xcd73518680ab60ec2253841909d3448bc60f0665"
>
</userfeeds-button>
<script src="https://cdn.jsdelivr.net/npm/@userfeeds/button"></script>
-----------------------
<userfeeds-button
text="Post"
value="0.5"
target="http://myphotos.com/some-photo"
context="rinkeby:0xcd73518680ab60ec2253841909d3448bc60f0665"
>
</userfeeds-button>
<script src="https://cdn.jsdelivr.net/npm/@userfeeds/button"></script>
Android¶
Source: https://github.com/Userfeeds/Android-Widgets
Code examples
Add widget via xml:
<io.userfeeds.widget.ButtonView
xmlns:userfeeds="http://schemas.android.com/apk/res-auto"
android:id="@+id/my_button_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Like"
userfeeds:target="http://someurl.url/"
userfeeds:labels="like"
userfeeds:context="ethereum:0x0406735fC1a657398941A50A0602eddf9723A6C8"/>
-----------------------
<io.userfeeds.widget.ButtonView
xmlns:userfeeds="http://schemas.android.com/apk/res-auto"
android:id="@+id/my_button_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is a Cat"
userfeeds:target="http://someurl.url/"
userfeeds:labels="cat"
userfeeds:context="ethereum:0x0406735fC1a657398941A50A0602eddf9723A6C8"/>
-----------------------
<io.userfeeds.widgets.ButtonView
xmlns:userfeeds="http://schemas.android.com/apk/res-auto"
android:id="@+id/my_button_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Upvote"
userfeeds:target="http://someurl.url/"
userfeeds:labels="up,good,positive"
userfeeds:context="ethereum:0x0406735fC1a657398941A50A0602eddf9723A6C8"/>
Or via code:
TODO