Skip to content
Merged
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
11 changes: 11 additions & 0 deletions backend/migrations/003_create_tokens_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- Migration: Create tokens table
CREATE TABLE IF NOT EXISTS tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
address VARCHAR(255) UNIQUE NOT NULL,
symbol VARCHAR(32),
name VARCHAR(128),
decimals INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_tokens_address ON tokens(address);
6 changes: 6 additions & 0 deletions backend/src/models/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
const { sequelize } = require('../database/connection');

const ClaimsHistory = require('./claimsHistory');
const Vault = require('./vault');
const SubSchedule = require('./subSchedule');
const TVL = require('./tvl');
const Beneficiary = require('./beneficiary');
const Organization = require('./organization');
const { Token, initTokenModel } = require('./token');


initTokenModel(sequelize);

const models = {
ClaimsHistory,
Expand All @@ -13,6 +18,7 @@ const models = {
TVL,
Beneficiary,
Organization,
Token,
sequelize,
};

Expand Down
44 changes: 44 additions & 0 deletions backend/src/models/token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { DataTypes, Model } = require('sequelize');

class Token extends Model {}

function initTokenModel(sequelize) {
Token.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
address: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
symbol: {
type: DataTypes.STRING(32),
},
name: {
type: DataTypes.STRING(128),
},
decimals: {
type: DataTypes.INTEGER,
},
createdAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
updatedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
},
{
sequelize,
tableName: 'tokens',
indexes: [{ fields: ['address'] }],
}
);
}

module.exports = { Token, initTokenModel };
62 changes: 62 additions & 0 deletions backend/src/services/tokenMetadataWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const { Token } = require('../models/token');
const Vault = require('../models/vault');
const axios = require('axios');

const SOROBAN_RPC_URL = process.env.SOROBAN_RPC_URL || 'https://soroban-rpc.testnet.stellar.org';

/**
* Worker to detect new token addresses and fetch/store their metadata.
*/
class TokenMetadataWorker {
constructor(sequelize) {
this.sequelize = sequelize;
}

async detectAndFetchNewTokens() {
// 1. Get all unique token addresses from Vaults
const vaults = await Vault.findAll({ attributes: ['token_address'] });
const addresses = vaults.map(function(v) { return v.token_address; });
const uniqueAddresses = Array.from(new Set(addresses));

// 2. For each address, check if it exists in tokens table
for (let i = 0; i < uniqueAddresses.length; i++) {
const address = uniqueAddresses[i];
const exists = await Token.findOne({ where: { address } });
if (!exists) {
// 3. Fetch metadata from Stellar
try {
const meta = await this.fetchTokenMetadata(address);
if (meta) {
await Token.create({
address: address,
symbol: meta.symbol,
name: meta.name,
decimals: meta.decimals,
});
console.log(`Token metadata stored for ${address}`);
}
} catch (err) {
console.error(`Failed to fetch/store metadata for ${address}:`, err);
}
}
}
}

async fetchTokenMetadata(address) {
// Example: Replace with actual Soroban RPC call
try {
const response = await axios.post(`${SOROBAN_RPC_URL}/getTokenMetadata`, { address: address });
const symbol = response.data.symbol;
const name = response.data.name;
const decimals = response.data.decimals;
if (symbol && name && typeof decimals === 'number') {
return { symbol: symbol, name: name, decimals: decimals };
}
return null;
} catch (err) {
return null;
}
}
}

module.exports = { TokenMetadataWorker };
10 changes: 10 additions & 0 deletions backend/src/workers/tokenMetaWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { sequelize } = require('../models');
const { TokenMetadataWorker } = require('../services/tokenMetadataWorker');

async function runWorker() {
const worker = new TokenMetadataWorker(sequelize);
await worker.detectAndFetchNewTokens();
process.exit(0);
}

runWorker();