Scavngr is a decentralized recycling platform built on Stellar blockchain using Soroban smart contracts. This documentation covers all contract functions, REST endpoints, and integration examples.
All contract interactions require a valid Stellar account with sufficient XLM for transaction fees. REST API endpoints may require API keys for rate limiting.
# Generate keypair
soroban keys generate my-account
# Fund account on testnet
curl "https://friendbot.stellar.org?addr=$(soroban keys address my-account)"- Default: 100 requests per minute per IP
- Burst: 10 requests per second
- Response Header:
X-RateLimit-Remaining
| Code | Message | Description |
|---|---|---|
| 1 | UnauthorizedAdmin | Only admin can perform this action |
| 2 | ParticipantNotFound | Participant not registered |
| 3 | InvalidRole | Invalid participant role |
| 4 | WasteNotFound | Waste/material not found |
| 5 | InvalidTransfer | Invalid waste transfer path |
| 6 | IncentiveNotFound | Incentive not found |
| 7 | InsufficientBudget | Incentive budget exhausted |
| 8 | InvalidCoordinates | Latitude/longitude out of range |
| 9 | InvalidWeight | Weight exceeds maximum |
| 10 | AlreadyConfirmed | Waste already confirmed |
Initialize contract admin (can only be called once).
Parameters:
admin(Address): Admin account address
Returns: None
Example:
let admin = Address::from_contract_id(&env, &contract_id);
contract.initialize_admin(&admin);Error Codes: None (first call only)
Transfer admin rights to another account.
Parameters:
current_admin(Address): Current admin addressnew_admin(Address): New admin address
Returns: None
Example:
contract.transfer_admin(¤t_admin, &new_admin);Error Codes: 1 (UnauthorizedAdmin)
Set charity contract address for donations.
Parameters:
admin(Address): Admin accountcharity_address(Address): Charity contract address
Returns: None
Example:
contract.set_charity_contract(&admin, &charity_address);Error Codes: 1 (UnauthorizedAdmin)
Set reward token address.
Parameters:
admin(Address): Admin accounttoken_address(Address): Token contract address
Returns: None
Example:
contract.set_token_address(&admin, &token_address);Error Codes: 1 (UnauthorizedAdmin)
Set reward split percentages (must sum to 100).
Parameters:
admin(Address): Admin accountcollector_pct(u32): Collector percentage (0-100)owner_pct(u32): Owner percentage (0-100)
Returns: None
Example:
contract.set_percentages(&admin, 30, 70);Error Codes: 1 (UnauthorizedAdmin)
Register a new participant.
Parameters:
address(Address): Participant addressrole(u32): Role (0=Recycler, 1=Collector, 2=Manufacturer)name(String): Participant namelat(i32): Latitude (scaled by 1,000,000)lon(i32): Longitude (scaled by 1,000,000)
Returns: None
Example:
contract.register_participant(
&participant_addr,
0, // Recycler
String::from_slice(&env, "John's Recycling"),
40_000_000, // 40.0°N
-74_000_000, // -74.0°W
);Error Codes: 3 (InvalidRole), 8 (InvalidCoordinates)
Get participant information.
Parameters:
address(Address): Participant address
Returns: Participant struct or None
Example:
let participant = contract.get_participant(&address);Error Codes: None
Get participant with statistics.
Parameters:
address(Address): Participant address
Returns: ParticipantInfo struct or None
Example:
let info = contract.get_participant_info(&address);
if let Some(info) = info {
println!("Wastes submitted: {}", info.stats.total_wastes);
}Error Codes: None
Update participant role.
Parameters:
address(Address): Participant addressnew_role(u32): New role (0=Recycler, 1=Collector, 2=Manufacturer)
Returns: None
Example:
contract.update_role(&address, 1); // Change to CollectorError Codes: 2 (ParticipantNotFound), 3 (InvalidRole)
Deregister a participant.
Parameters:
address(Address): Participant address
Returns: None
Example:
contract.deregister_participant(&address);Error Codes: 2 (ParticipantNotFound)
Check if participant is registered.
Parameters:
address(Address): Participant address
Returns: Boolean
Example:
if contract.is_participant_registered(&address) {
println!("Participant is registered");
}Error Codes: None
Submit waste material.
Parameters:
submitter(Address): Submitter addresswaste_type(u32): Type (0=Plastic, 1=Paper, 2=Metal, 3=Glass, 4=Organic)weight(u128): Weight in gramslat(i32): Latitude (scaled by 1,000,000)lon(i32): Longitude (scaled by 1,000,000)
Returns: Waste ID
Example:
let waste_id = contract.submit_material(
&submitter,
0, // Plastic
5000, // 5kg
40_000_000,
-74_000_000,
);Error Codes: 2 (ParticipantNotFound), 8 (InvalidCoordinates), 9 (InvalidWeight)
Batch submit multiple materials.
Parameters:
submitter(Address): Submitter addressmaterials(Vec): Array of materials
Returns: Array of waste IDs
Example:
let materials = vec![
Material { waste_type: 0, weight: 5000, lat: 40_000_000, lon: -74_000_000 },
Material { waste_type: 1, weight: 3000, lat: 40_000_000, lon: -74_000_000 },
];
let ids = contract.submit_materials_batch(&submitter, &materials);Error Codes: 2 (ParticipantNotFound), 8 (InvalidCoordinates), 9 (InvalidWeight)
Verify a material submission.
Parameters:
material_id(u64): Waste IDverifier(Address): Verifier address
Returns: None
Example:
contract.verify_material(waste_id, &verifier);Error Codes: 4 (WasteNotFound), 2 (ParticipantNotFound)
Transfer waste to another participant.
Parameters:
waste_id(u64): Waste IDfrom(Address): Current ownerto(Address): New ownerlat(i32): Transfer location latitudelon(i32): Transfer location longitudenote(String): Transfer note
Returns: None
Example:
contract.transfer_waste(
waste_id,
&recycler,
&collector,
40_000_000,
-74_000_000,
String::from_slice(&env, "Collected from site A"),
);Error Codes: 4 (WasteNotFound), 5 (InvalidTransfer), 8 (InvalidCoordinates)
Confirm waste details.
Parameters:
waste_id(u64): Waste IDconfirmer(Address): Confirmer address
Returns: None
Example:
contract.confirm_waste_details(waste_id, &confirmer);Error Codes: 4 (WasteNotFound), 10 (AlreadyConfirmed)
Reset waste confirmation status.
Parameters:
waste_id(u64): Waste IDowner(Address): Waste owner
Returns: None
Example:
contract.reset_waste_confirmation(waste_id, &owner);Error Codes: 4 (WasteNotFound)
Deactivate waste (admin only).
Parameters:
admin(Address): Admin addresswaste_id(u64): Waste ID
Returns: None
Example:
contract.deactivate_waste(&admin, waste_id);Error Codes: 1 (UnauthorizedAdmin), 4 (WasteNotFound)
Get waste by ID.
Parameters:
waste_id(u64): Waste ID
Returns: Waste struct or None
Example:
if let Some(waste) = contract.get_waste(waste_id) {
println!("Waste type: {}", waste.waste_type);
}Error Codes: None
Get all waste IDs for a participant.
Parameters:
participant(Address): Participant address
Returns: Array of waste IDs
Example:
let waste_ids = contract.get_participant_wastes(&participant);Error Codes: None
Get transfer history for waste.
Parameters:
waste_id(u64): Waste ID
Returns: Array of transfer records
Example:
let history = contract.get_waste_transfer_history(waste_id);
for record in history {
println!("Transferred from {:?} to {:?}", record.from, record.to);
}Error Codes: None
Create incentive for waste type.
Parameters:
rewarder(Address): Manufacturer addresswaste_type(u32): Waste type (0-4)reward_points(u128): Points per unitbudget(u128): Total budget
Returns: Incentive ID
Example:
let incentive_id = contract.create_incentive(
&manufacturer,
0, // Plastic
100, // 100 points per kg
10000, // 10000 point budget
);Error Codes: 2 (ParticipantNotFound), 3 (InvalidRole)
Update incentive.
Parameters:
incentive_id(u64): Incentive IDrewarder(Address): Manufacturer addressreward_points(u128): New reward pointsbudget(u128): New budget
Returns: None
Example:
contract.update_incentive(incentive_id, &manufacturer, 150, 15000);Error Codes: 6 (IncentiveNotFound), 2 (ParticipantNotFound)
Deactivate incentive.
Parameters:
incentive_id(u64): Incentive IDrewarder(Address): Manufacturer address
Returns: None
Example:
contract.deactivate_incentive(incentive_id, &manufacturer);Error Codes: 6 (IncentiveNotFound)
Get incentive by ID.
Parameters:
incentive_id(u64): Incentive ID
Returns: Incentive struct or None
Example:
if let Some(incentive) = contract.get_incentive_by_id(incentive_id) {
println!("Reward points: {}", incentive.reward_points);
}Error Codes: None
Get active incentives by waste type.
Parameters:
waste_type(u32): Waste type (0-4)
Returns: Array of incentives
Example:
let incentives = contract.get_incentives(0); // Plastic incentivesError Codes: None
Get all active incentives.
Returns: Array of incentives
Example:
let all_incentives = contract.get_active_incentives();Error Codes: None
Get best active incentive for manufacturer and waste type.
Parameters:
manufacturer(Address): Manufacturer addresswaste_type(u32): Waste type (0-4)
Returns: Incentive struct or None
Example:
if let Some(incentive) = contract.get_active_mfr_incentive(&manufacturer, 0) {
println!("Best incentive: {} points", incentive.reward_points);
}Error Codes: None
Distribute supply chain rewards.
Parameters:
waste_id(u64): Waste IDincentive_id(u64): Incentive IDmanufacturer(Address): Manufacturer address
Returns: None
Example:
contract.distribute_rewards(waste_id, incentive_id, &manufacturer);Error Codes: 4 (WasteNotFound), 6 (IncentiveNotFound), 7 (InsufficientBudget)
Get global metrics.
Returns: GlobalMetrics struct
Example:
let metrics = contract.get_metrics();
println!("Total wastes: {}", metrics.total_wastes);
println!("Total tokens distributed: {}", metrics.total_tokens);Error Codes: None
Get participant statistics.
Parameters:
participant(Address): Participant address
Returns: ParticipantStats struct or None
Example:
if let Some(stats) = contract.get_stats(&participant) {
println!("Wastes submitted: {}", stats.total_wastes);
}Error Codes: None
Get global supply chain statistics.
Returns: SupplyChainStats struct
Example:
let stats = contract.get_supply_chain_stats();
println!("Total recycled: {} kg", stats.total_weight);Error Codes: None
pub struct Participant {
pub address: Address,
pub role: u32,
pub name: String,
pub lat: i32,
pub lon: i32,
pub registered_at: u64,
}pub struct Waste {
pub id: u64,
pub waste_type: u32,
pub weight: u128,
pub owner: Address,
pub lat: i32,
pub lon: i32,
pub created_at: u64,
pub confirmed: bool,
pub active: bool,
}pub struct Incentive {
pub id: u64,
pub manufacturer: Address,
pub waste_type: u32,
pub reward_points: u128,
pub budget: u128,
pub spent: u128,
pub active: bool,
pub created_at: u64,
}pub struct ParticipantStats {
pub total_wastes: u64,
pub total_weight: u128,
pub total_tokens: u128,
pub verified_count: u64,
}import { Keypair, Networks, TransactionBuilder, Operation } from 'stellar-sdk';
import { ContractSpec, nativeToScVal } from '@stellar/js-stellar-sdk';
const contractId = 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4';
const keypair = Keypair.fromSecret('SBXXXXXXX...');
async function registerParticipant() {
const account = await server.getAccount(keypair.publicKey());
const tx = new TransactionBuilder(account, {
fee: '100',
networkPassphrase: Networks.TESTNET_NETWORK_PASSPHRASE,
})
.addOperation(
Operation.invokeHostFunction({
func: xdr.HostFunction.hostFunctionTypeInvokeContract([
nativeToScVal(contractId),
nativeToScVal('register_participant'),
nativeToScVal(keypair.publicKey()),
nativeToScVal(0), // Recycler
nativeToScVal('My Recycling'),
nativeToScVal(40000000),
nativeToScVal(-74000000),
]),
auth: [],
})
)
.setTimeout(30)
.build();
const signed = keypair.signTransaction(tx);
const result = await server.submitTransaction(signed);
return result;
}from stellar_sdk import Keypair, Network, TransactionBuilder, Server
from stellar_sdk.operation import InvokeHostFunction
contract_id = 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4'
keypair = Keypair.from_secret('SBXXXXXXX...')
server = Server('https://soroban-testnet.stellar.org')
def register_participant():
account = server.load_account(keypair.public_key)
tx = (
TransactionBuilder(
account,
base_fee=100,
network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE,
)
.add_text_memo('Register participant')
.set_timeout(30)
.build()
)
signed = keypair.sign_transaction(tx)
result = server.submit_transaction(signed)
return resultuse soroban_sdk::{Address, Env, String};
pub fn register_participant(
env: &Env,
contract: &ContractClient,
address: Address,
role: u32,
name: &str,
lat: i32,
lon: i32,
) {
contract.register_participant(
&address,
&role,
&String::from_slice(env, name),
&lat,
&lon,
);
}Import this collection into Postman for easy API testing:
{
"info": {
"name": "Scavngr Contract API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Register Participant",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"address\": \"GXXXXXXX\",\n \"role\": 0,\n \"name\": \"My Recycling\",\n \"lat\": 40000000,\n \"lon\": -74000000\n}"
},
"url": {
"raw": "{{CONTRACT_URL}}/register_participant",
"host": ["{{CONTRACT_URL}}"],
"path": ["register_participant"]
}
}
}
]
}All responses include rate limit information:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1619827200
For issues or questions, visit the GitHub repository or contact the development team.