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
53 changes: 53 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: contracts-ci

# Compila y prueba los contratos Soroban. fmt/clippy se aplican a staking_pool
# (el contrato refactorizado para DeFindex); build + test corren sobre los tres.

on:
push:
branches: [main]
pull_request:

jobs:
contracts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
targets: wasm32-unknown-unknown

- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
workspaces: |
backend/contracts/staking_pool
backend/contracts/vinculo_sbt
backend/contracts/vinculo_lending

- name: Format check (staking_pool)
working-directory: backend/contracts/staking_pool
run: cargo fmt --check

- name: Clippy (staking_pool)
working-directory: backend/contracts/staking_pool
run: cargo clippy --all-targets -- -D warnings

- name: Unit tests (all contracts)
working-directory: backend/contracts
run: |
for c in staking_pool vinculo_sbt vinculo_lending; do
echo "== cargo test: $c =="
(cd "$c" && cargo test)
done

- name: Build wasm release (all contracts)
working-directory: backend/contracts
run: |
for c in staking_pool vinculo_sbt vinculo_lending; do
echo "== cargo build (wasm release): $c =="
(cd "$c" && cargo build --release --target wasm32-unknown-unknown)
done
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ backend/.env
target/
**/target/
Cargo.lock.bak
# Soroban test execution snapshots (generated by `cargo test`)
**/test_snapshots/
46 changes: 32 additions & 14 deletions api/evaluate-and-mint.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import {
Keypair,
rpc,
TransactionBuilder,
Networks,
Operation,
BASE_FEE,
import {
Keypair,
rpc,
TransactionBuilder,
Networks,
Operation,
BASE_FEE,
nativeToScVal,
scValToNative,
} from "@stellar/stellar-sdk";
import dotenv from "dotenv";
import { createLogger } from "./_logger.js";
import { validateBody, evaluateAndMintBodySchema, reportValidationError } from "./validation.js";

dotenv.config();

const HORIZON_URL = "https://horizon-testnet.stellar.org";
const RPC_URL = "https://soroban-testnet.stellar.org";
const server = new rpc.Server(RPC_URL);
// staking_pool respaldado por DeFindex. Actualizar tras el redeploy de testnet.
const STAKING_CONTRACT_ID =
process.env.STAKING_CONTRACT_ID || "CAIYBGMKSA5V5EYUFKGD5OCWWS5M34YC7MKUKE3BOQE2WZP3R7A4S2D2";

function tierNameFromValue(tier) {
if (tier === 4) return "Platino";
Expand Down Expand Up @@ -133,16 +136,31 @@ export default async function handler(req, res) {

let effectiveTotalVolume = Number(totalVolume) || fallbackVolumeFromDeposits;

// Fallback: lee la posición real on-chain (depósito + rendimiento del vault DeFindex)
// en lugar del balance nativo XLM, para que el score refleje el apartado del usuario.
if (!effectiveTotalVolume || effectiveTotalVolume <= 0) {
try {
const resp = await fetch(`${HORIZON_URL}/accounts/${userAddress}`);
if (resp.ok) {
const acct = await resp.json();
const native = (acct.balances || []).find(b => b.asset_type === 'native');
effectiveTotalVolume = Number(native?.balance) || effectiveTotalVolume;
const account = await server.getAccount(userAddress);
const tx = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: Networks.TESTNET,
})
.addOperation(
Operation.invokeContractFunction({
contract: STAKING_CONTRACT_ID,
function: "get_position",
args: [nativeToScVal(userAddress, { type: "address" })],
})
)
.setTimeout(30)
.build();
const sim = await server.simulateTransaction(tx);
if (rpc.Api.isSimulationSuccess(sim) && sim.result) {
const positionXLM = Number(scValToNative(sim.result.retval)) / 10000000;
effectiveTotalVolume = positionXLM || effectiveTotalVolume;
}
} catch (e) {
log.warn("evaluate_and_mint.balance_fetch_failed", { userAddress, err: e.message });
log.warn("evaluate_and_mint.position_fetch_failed", { userAddress, err: e.message });
}
}

Expand Down
70 changes: 40 additions & 30 deletions api/get-user-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default async function handler(req, res) {

try {
const server = new rpc.Server(RPC_URL);

let account;
try {
account = await server.getAccount(address);
Expand All @@ -33,42 +33,52 @@ export default async function handler(req, res) {
return res.status(200).json({ totalXlmDeposited: 0, depositCount: 0, nftLevel: 0 });
}

const transaction = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: Networks.TESTNET,
})
.addOperation(
Operation.invokeContractFunction({
contract: CONTRACT_ID,
function: "get_balance",
args: [nativeToScVal(address, { type: "address" })],
})
)
.setTimeout(30)
.build();
// Lee un getter del contrato vía simulación y devuelve el valor nativo.
const readContract = async (fnName) => {
const tx = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: Networks.TESTNET,
})
.addOperation(
Operation.invokeContractFunction({
contract: CONTRACT_ID,
function: fnName,
args: [nativeToScVal(address, { type: "address" })],
})
)
.setTimeout(30)
.build();

const response = await server.simulateTransaction(transaction);
const response = await server.simulateTransaction(tx);
if (rpc.Api.isSimulationSuccess(response) && response.result) {
return scValToNative(response.result.retval);
}
return 0;
};

let balanceXLM = 0;
if (rpc.Api.isSimulationSuccess(response) && response.result) {
const balanceInStroops = scValToNative(response.result.retval);
balanceXLM = Number(balanceInStroops) / 10000000;
}
// Posición real (depósito + rendimiento del vault), ingreso acumulado y nº de operaciones.
const [positionRaw, depositedRaw, txCountRaw] = await Promise.all([
readContract("get_position"),
readContract("get_total_deposited"),
readContract("get_tx_count"),
]);

let nftLevel = 0;
if (balanceXLM >= 50) nftLevel = 1;
if (balanceXLM >= 150) nftLevel = 2;
if (balanceXLM >= 500) nftLevel = 3;
if (balanceXLM >= 1000) nftLevel = 4;
const positionXLM = Number(positionRaw) / 10000000;
const totalDeposited = Number(depositedRaw) / 10000000;
const depositCount = Number(txCountRaw) || 0;

let depositCount = balanceXLM > 0 ? 1 : 0;
if (balanceXLM >= 150) depositCount = 3;
if (balanceXLM >= 500) depositCount = 10;
// El nivel del NFT se aproxima por la posición real (el score canónico lo refina).
let nftLevel = 0;
if (positionXLM >= 50) nftLevel = 1;
if (positionXLM >= 150) nftLevel = 2;
if (positionXLM >= 500) nftLevel = 3;
if (positionXLM >= 1000) nftLevel = 4;

log.info("get_user_data.done", { address, balanceXLM, nftLevel, depositCount });
log.info("get_user_data.done", { address, positionXLM, totalDeposited, nftLevel, depositCount });

return res.status(200).json({
totalXlmDeposited: balanceXLM,
totalXlmDeposited: totalDeposited,
currentPosition: positionXLM,
depositCount,
nftLevel,
});
Expand Down
Loading
Loading