Cardano Pythathon: Factura Ya — Invoice Factoring Marketplace#97
Cardano Pythathon: Factura Ya — Invoice Factoring Marketplace#97qmarquez wants to merge 8 commits intopyth-network:mainfrom
Conversation
Hackathon submission for the Cardano Pythathon. Factura Ya is an on-chain invoice factoring marketplace on Cardano. SMEs tokenize outstanding invoices as NFTs, deposit collateral, and sell collection rights to investors at a discount. Pyth's ADA/USD price feed provides real-time currency conversion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| fn get_validity_start(tx: Transaction) -> Int { | ||
| when tx.validity_range.lower_bound.bound_type is { | ||
| Finite(time) -> time | ||
| _ -> 0 | ||
| } | ||
| } |
There was a problem hiding this comment.
🔴 get_validity_start returns 0 for non-Finite bounds, disabling due-date validation in invoice minting
When a transaction does not set a finite lower validity bound (e.g., NegInf), get_validity_start returns 0 (invoice_mint.ak:153). The mint validation check at line 53 becomes datum.due_date_posix_ms > 0, which is trivially true for any positive timestamp. This effectively disables the "due date must be in the future" invariant, allowing invoices with past due dates to be minted. The function should fail or expect Finite(time) instead of silently returning 0.
Note: the same function in escrow.ak:72 is safe because there the check direction is reversed (lower >= due_date), so returning 0 makes the check stricter, not weaker.
| fn get_validity_start(tx: Transaction) -> Int { | |
| when tx.validity_range.lower_bound.bound_type is { | |
| Finite(time) -> time | |
| _ -> 0 | |
| } | |
| } | |
| fn get_validity_start(tx: Transaction) -> Int { | |
| expect Finite(time) = tx.validity_range.lower_bound.bound_type | |
| time | |
| } | |
Was this helpful? React with 👍 or 👎 to provide feedback.
| const INVOICE = { | ||
| amount: ${params.amount}, | ||
| days: ${params.days}, | ||
| debtor: "${params.debtor}", | ||
| contact: "${params.contact}", | ||
| policyId: "${scripts.invoiceMint.hash}", | ||
| escrowHash: "${scripts.escrow.hash}", | ||
| marketplaceHash: "${scripts.marketplace.hash}", | ||
| invoiceMintCbor: "${scripts.invoiceMint.cbor}", | ||
| }; |
There was a problem hiding this comment.
🔴 XSS via unsanitized user input interpolated into JavaScript in registerPage
In registerPage, URL query parameters debtor and contact are interpolated directly into JavaScript string literals without escaping. A crafted URL like /register?debtor=";alert(document.cookie);// would break out of the JS string and execute arbitrary JavaScript in the user's browser. While this server runs on localhost, it's accessible to any page that can make requests to it.
Vulnerable template interpolation
const INVOICE = {
amount: ${params.amount}, // numeric injection possible
days: ${params.days}, // numeric injection possible
debtor: "${params.debtor}", // XSS
contact: "${params.contact}", // XSS
};Prompt for agents
In lazer/cardano/factura-ya/offchain/src/deploy-server.ts, the registerPage function (starting around line 461) interpolates user-supplied URL query parameters directly into a JavaScript template string at lines 497-506. The params.debtor, params.contact, params.amount, and params.days values come from URL query parameters and need to be sanitized before interpolation into JavaScript.
Add a helper function to escape strings for safe JavaScript string literal inclusion, escaping characters like backslash, quotes, angle brackets, and newlines. Apply it to params.debtor and params.contact at lines 500-501. For params.amount and params.days (lines 498-499), parse them as numbers with parseInt/parseFloat and provide safe defaults (e.g., 0) to prevent code injection through numeric fields.
Was this helpful? React with 👍 or 👎 to provide feedback.
| app.get("/invoices/all", (_req, res) => { | ||
| res.json({ invoices: [...invoices.values()], total: invoices.size }); | ||
| }); |
There was a problem hiding this comment.
🟡 Express route /invoices/all is shadowed by /invoices/:id and unreachable
The route GET /invoices/all at line 55 is registered after GET /invoices/:id at line 45. Express matches routes in registration order, so a request to /invoices/all matches /invoices/:id with req.params.id = "all", which looks up "all" in the invoices Map and returns a 404. The /invoices/all handler is never reached.
Prompt for agents
In lazer/cardano/factura-ya/indexer/src/api.ts, move the route definition for GET /invoices/all (currently at lines 55-57) to BEFORE the GET /invoices/:id route (currently at lines 45-52). Express matches routes in registration order, so the more specific /invoices/all must come before the parameterized /invoices/:id.
Was this helpful? React with 👍 or 👎 to provide feedback.
Pyth Examples Contribution
Team Name: Facturas Ya
Submission Name: Factura Ya
Team Members: Dario Fasolino, Macarena Carabajal
Contact: dario.a.fasolino@gmail.com, macacarabajal3@gmail.com
Type of Contribution
Project Information
Project/Example Name: Factura Ya
Pyth Product Used:
Blockchain/Platform:
Description
What does this contribution do?
Factura Ya is an on-chain invoice factoring marketplace built on Cardano. Latin American SMEs tokenize their outstanding invoices as NFTs, deposit collateral as a good-faith guarantee, and sell the collection rights to investors at a discount. The SME gets paid immediately; the investor collects the full amount at maturity.
How does it integrate with Pyth?
Pyth is central to the entire valuation pipeline:
pyth_oracle.akcallspyth.get_updates()to read the verified ADA/USD price feed (feed ID 16) from the Pyth withdraw-script redeemer. Price freshness is enforced (max 60s).usd_to_lovelace()converts invoice values using the real-time price.PythPriceClientsubscribes to Pyth Pro WebSocket for live updates. Transaction construction uses@pythnetwork/pyth-lazer-cardano-js(getPythState(),getPythScriptHash()) with the PreProd Policy IDd799d287105dea9377cdf9ea8502a83d2b9eb2d2050a8aea800a21e6.What problem does it solve or demonstrate?
SMEs in Argentina wait 60-90 days to collect on invoices. Traditional factoring charges 3-5% monthly. Factura Ya enables instant liquidity through on-chain invoice tokenization with transparent pricing powered by Pyth oracles — no banks, no intermediaries.
Architecture
Testing & Verification
Prerequisites
Setup & Run Instructions
Deployment Information
Target network: Cardano PreProd
Pyth Policy ID: d799d287105dea9377cdf9ea8502a83d2b9eb2d2050a8aea800a21e6
Checklist
Code Quality
Testing
Notes for Reviewers
Hackathon submission for the Cardano Pythathon (2026-03-22). This creates the first lazer/cardano/ directory in the repo.