Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
a9d32a1
Add Mykobo API client and EURC token config to shared
ebma May 21, 2026
e75b499
Route EVM SEPA offramp quotes through Mykobo
ebma May 21, 2026
4e19525
Wire Mykobo EUR offramp on Base end-to-end
ebma May 21, 2026
c1efcc5
Add Mykobo EUR offramp sandbox-contract integration test
ebma May 21, 2026
9d56d54
Add Mykobo onramp shared scaffolding and EURC Base constants
ebma May 21, 2026
65012d2
Dispatch EVM-ephemeral phase handlers by nablaSwapEvm metadata
ebma May 21, 2026
69f02ea
Add Mykobo onramp quote initialize engine and validation
ebma May 21, 2026
a1f7896
Add Mykobo onramp quote strategy with EURC-USDC Nabla swap
ebma May 21, 2026
44f69be
feat(api): route EURC onramp through Mykobo→Base strategy
ebma May 21, 2026
c7878d5
feat(api): wire Mykobo EUR onramp into ramp transaction preparation
ebma May 21, 2026
32dc0a8
refactor(api,shared): remove Monerium integration in favor of Mykobo
ebma May 21, 2026
c80843e
refactor(frontend): remove Monerium UI and wiring
ebma May 21, 2026
fd75c50
feat(api): add Mykobo onramp deposit-watch handler
ebma May 21, 2026
f89554d
refactor(api,shared): remove Stellar+Spacewalk+ARS backend infrastruc…
ebma May 22, 2026
82761ba
chore(db): add migration to remove stellar anchor rows and XLM subsidy
ebma May 22, 2026
126c0e6
refactor(frontend): remove Stellar KYC, SEP-10/24, and ARS backend wi…
ebma May 22, 2026
28b4699
refactor(sdk): remove stellar ephemeral support
ebma May 22, 2026
f68c62e
fix(api): replace non-null assertion with typed guard in mykobo-to-ev…
ebma May 22, 2026
ecce475
fix(shared,api): register EURC in freeTokenConfig and drop Stellar ep…
ebma May 22, 2026
2081f79
test(api): add Mykobo EUR onramp sandbox integration test
ebma May 22, 2026
c08b7b7
feat(api,shared): add Mykobo profile create+get endpoints for KYC sub…
ebma May 22, 2026
0b47617
feat(frontend): add Mykobo KYC machine, service, and ramp wiring
ebma May 22, 2026
4d22f9e
feat(frontend): add Mykobo KYC UI, widget mount, progress message, an…
ebma May 22, 2026
0defc3a
docs(security-spec): add Mykobo EUR-on-Base spec and update ramp phas…
ebma May 22, 2026
0cfcc2a
docs(security-spec): mark Monerium and Stellar-EUR integrations depre…
ebma May 22, 2026
c6c22ca
docs(security-spec): cross-doc cleanup for Mykobo EUR migration
ebma May 22, 2026
dc7a4a5
docs(security-spec): reconcile audit results and findings for Mykobo
ebma May 22, 2026
040845d
docs(security-spec): unify on/off-ramp diagrams and remove ARS
ebma May 22, 2026
410d1db
docs(security-spec): file F-NEW findings from Mykobo audit
ebma May 22, 2026
c3a871f
fix(api): require Supabase auth on Mykobo profile endpoints (F-068)
ebma May 22, 2026
9652bc3
fix(api): wire fundEphemeral into Mykobo EUR onramp and patch SELL EU…
ebma May 22, 2026
b85b554
fix(shared): enforce HTTPS on MYKOBO_BASE_URL and debounce auth-failu…
ebma May 22, 2026
d63aa77
docs(security-spec): mark F-068..F-071 fixed and update audit summary
ebma May 22, 2026
16925f9
docs(security-spec): sync specs with F-068 + EUR onramp fundEphemeral…
ebma May 22, 2026
2c61b7f
fix(api): bind Mykobo profile endpoints to authenticated user and red…
ebma May 22, 2026
3038a7a
fix(api): remove dead Polygon EURC branch and guard squidRouterSwapHa…
ebma May 22, 2026
a1fb453
fix(api): mark Mykobo payout timeout as recoverable
ebma May 22, 2026
4cb28c3
fix(frontend): remove PII-leaking console.logs from KYC state machine
ebma May 22, 2026
88dbf9f
fix(api): make substrate ephemeral requirement explicit per offramp r…
ebma May 22, 2026
2f8536a
chore: remove dead Stellar/Monerium/SEP24 code paths
ebma May 22, 2026
c85db8a
mykobo base frontend code quality improvements
Sharqiewicz May 22, 2026
0f290d3
fix Mykobo Base KYC Done Screen
Sharqiewicz May 22, 2026
762bdef
unblock onramp fiat selector, progress polling, and KYC routing
Sharqiewicz May 25, 2026
4e5bab1
dedupe RampSubmitButton checks and tidy KYC helpers
Sharqiewicz May 25, 2026
4801be5
remove dead state ops and harden persistence boundaries
Sharqiewicz May 25, 2026
7766d85
fix(api): target Base USDC (not USDT) for EVM offramp Squid bridge
ebma May 26, 2026
a270f10
refactor(api): split Mykobo offramp initializer into its own file
ebma May 26, 2026
47e8c69
refactor(api): drop 'Brl' from shared Squid-to-Base engine class name
ebma May 26, 2026
4eb126c
refactor(api): split Avenia and Alfredpay offramp initializers into d…
ebma May 26, 2026
0f048e3
fix(api): price Squid network fee in the source chain's native token
ebma May 26, 2026
7d294bb
Fix issue with IP address
ebma May 26, 2026
7c8cac0
fix(api): add user transfer leg to Mykobo Base USDC offramp
Sharqiewicz May 27, 2026
c319163
fix(frontend): keep Mykobo KYC failure screen interactive
Sharqiewicz May 27, 2026
a4c955b
fix(frontend): EUR icon and IBAN display on Payment Summary
Sharqiewicz May 27, 2026
cb029de
apps/frontend/src/components/RampSubmitButton/RampSubmitButton.tsx
Sharqiewicz May 27, 2026
4913dae
Merge branch 'mykobo-base-evm-api' of github.com:pendulum-chain/vorte…
Sharqiewicz May 27, 2026
d7eaedc
extract transferAbi to shared module, harden and tidy Mykobo Base off…
Sharqiewicz May 27, 2026
1716324
fix abi declaration
Sharqiewicz May 27, 2026
88784ee
feat(api): implement direct USDC transfer for ephemeral funding on Ba…
ebma May 27, 2026
7997666
fix: wire Mykobo EUR offramp email into register payload and return 4…
ebma May 27, 2026
e2d24bb
Use email instead of wallet address for get-profile requests
ebma May 27, 2026
19d81f2
Make signing box steps dynamic
ebma May 27, 2026
d0aa6f0
Don't require PEN funding for EUR ramps
ebma May 27, 2026
438862c
Fix progress page for EUR offramps
ebma May 27, 2026
2804dac
Fix onramp to native token
ebma May 27, 2026
bfab795
Use public IP also when on localhost
ebma May 27, 2026
e93b9c0
Refactor nonce handling for destination transfers to ensure correct s…
ebma May 27, 2026
f5f76ed
Refactor SquidRouterPhaseHandler to streamline client retrieval and t…
ebma May 28, 2026
02404aa
Floor Mykobo intent value to 2 decimals for accurate EURC transfer am…
ebma May 28, 2026
990e357
Update spec documents
ebma May 28, 2026
41db8d6
refactor Alfredpay error validation
Sharqiewicz May 28, 2026
075077d
Merge branch 'staging' into mykobo-base-evm-api
ebma May 29, 2026
b9fd2dc
Amend merge conflicts
ebma May 29, 2026
db8d489
feat: implement getNablaBasePool for dynamic router and quoter selection
ebma May 29, 2026
3829bff
Address review feedback: use toJSON() in xcmParsers, throw APIError i…
Copilot May 29, 2026
c0ba9e9
refactor: simplify actor reference types and improve selector logic
ebma May 29, 2026
53fed07
Merge pull request #1159 from pendulum-chain/mykobo-base-evm-api
ebma May 29, 2026
fb3a9a2
feat(api): add direct EUR→EURC Base on-ramp bypass
ebma Jun 1, 2026
e49d86c
fix(api): prevent finalSettlementSubsidy over-subsidy race
ebma Jun 1, 2026
a8a7837
fix(api): correct same-chain destinationTransfer nonce ordering
ebma Jun 1, 2026
20ff068
fix(api): fail fast on destinationTransfer nonce gap
ebma Jun 1, 2026
03e65d6
docs(security-spec): document EUR→EURC Base corridor edge cases
ebma Jun 1, 2026
3397431
fix(api): execute swap for AlfredPay non-USDT Polygon onramps
ebma Jun 1, 2026
0c595e6
fix(api): add direct BRL→BRLA Base on-ramp bypass
ebma Jun 1, 2026
69f50fa
docs(security-spec): document AlfredPay + BRL→BRLA Base same-chain ed…
ebma Jun 1, 2026
471d697
Add Mykobo customer table and sync it with user profile status
ebma Jun 1, 2026
2780462
Show transaction reference in onramp details
ebma Jun 1, 2026
b3d4bc2
Replace hydration rpc endpoint
ebma Jun 1, 2026
73364be
Adjust edge case handling for same fiat/crypto rails
ebma Jun 1, 2026
4b761ec
Show IBAN in payment details for EUR offramps
ebma Jun 1, 2026
cf14a3a
Fix issue with partner not showing
ebma Jun 1, 2026
33fbcb1
Address pull request review comments
ebma Jun 1, 2026
72f7786
Merge pull request #1171 from pendulum-chain/1069-improve-error-messa…
ebma Jun 1, 2026
3d3541d
Change blocktag for nonce check to 'pending'
ebma Jun 1, 2026
2aa64ee
Adjust security-spec
ebma Jun 1, 2026
bc4c581
Merge pull request #1179 from pendulum-chain/mykobo-base-improvements
ebma Jun 1, 2026
9db6c87
feat: allow negative targetDiscount as rate floor
ebma Jun 1, 2026
7e88d4d
Add rampEphemerals localStorage backup for ephemeral key failsafe
ebma Jun 1, 2026
b05131e
Merge pull request #1181 from pendulum-chain/feat/ramp-ephemerals-loc…
ebma Jun 1, 2026
47e5746
Merge pull request #1180 from pendulum-chain/feat/negative-target-dis…
ebma Jun 1, 2026
6790ba0
Delete expired quotes that are older than 90 days
ebma Jun 1, 2026
ba4bd85
Merge pull request #1182 from pendulum-chain/delete-expired-quotes
ebma Jun 1, 2026
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ dist-ssr
**/failedRampStateRecovery.json
**/lastRampState.json
**/lastRampStateOnramp.json
**/lastRampStateMykoboEur.json
**/lastRampStateMykoboEurOnramp.json


*storybook.log
Expand Down
4 changes: 0 additions & 4 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ COINGECKO_API_KEY=your-coingecko-api-key
COINGECKO_API_URL=https://pro-api.coingecko.com/api/v3
SUBSCAN_API_KEY=your-subscan-api-key

# EUR / Monerium
MONERIUM_CLIENT_ID_APP=your-monerium-client-id
MONERIUM_CLIENT_SECRET=your-monerium-client-secret

# Price Feed Cache Configuration
CRYPTO_CACHE_TTL_MS=300000
FIAT_CACHE_TTL_MS=300000
Expand Down
2 changes: 2 additions & 0 deletions apps/api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ jspm_packages
*/failedRampStateRecovery.json
*/lastRampState.json
*/lastRampStateOnramp.json
*/lastRampStateMykoboEur.json
*/lastRampStateMykoboEurOnramp.json
31 changes: 0 additions & 31 deletions apps/api/src/api/controllers/monerium.controller.ts

This file was deleted.

131 changes: 131 additions & 0 deletions apps/api/src/api/controllers/mykobo.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { MykoboApiError, MykoboApiService, MykoboProfile } from "@vortexfi/shared";
import { Request, Response } from "express";
import httpStatus from "http-status";
import { isAddress } from "viem";
import logger from "../../config/logger";
import { upsertMykoboCustomerFromProfile } from "../services/mykobo/mykobo-customer.service";

const PROFILE_TEXT_FIELDS = [
"first_name",
"last_name",
"additional_name",
"email_address",
"mobile_number",
"birth_date",
"birth_country_code",
"address_line_1",
"city",
"id_country_code",
"id_type",
"bank_account_number",
"bank_number",
"wallet_address",
"source_of_funds",
"tax_country",
"tax_id",
"tax_id_name",
"memo"
] as const;

const PROFILE_FILE_FIELDS = ["front", "back", "face", "utility_bill"] as const;

const toFrontendProfile = (p: MykoboProfile) => ({
bankAccountNumber: p.bank_account_number,
createdAt: p.created_at,
emailAddress: p.email_address,
firstName: p.first_name,
kycStatus: {
receivedAt: p.kyc_status.received_at,
reviewStatus: p.kyc_status.review_status
},
lastName: p.last_name
});

const emailsMatch = (a: string | undefined, b: string | undefined): boolean =>
!!a && !!b && a.trim().toLowerCase() === b.trim().toLowerCase();

export const getProfileController = async (req: Request, res: Response): Promise<void> => {
const { email, memo } = req.query;
const userEmail = req.userEmail;

if (!userEmail) {
res.status(httpStatus.UNAUTHORIZED).json({ error: "Authenticated user email missing" });
return;
}
if (!email || typeof email !== "string" || !emailsMatch(email, userEmail)) {
res.status(httpStatus.BAD_REQUEST).json({ error: "Invalid email parameter" });
return;
}

try {
const memoParam = typeof memo === "string" && memo.length > 0 ? memo : undefined;
const { profile } = await MykoboApiService.getInstance().getProfileByEmail(userEmail, memoParam);
if (!emailsMatch(profile.email_address, userEmail)) {
res.status(httpStatus.NOT_FOUND).json({ error: "Profile not found" });
return;
}
if (req.userId) {
try {
await upsertMykoboCustomerFromProfile(req.userId, userEmail, profile);
} catch (mirrorError) {
logger.error("Failed to update Mykobo customer mirror in getProfileController:", mirrorError);
}
}
res.json({ profile: toFrontendProfile(profile) });
} catch (error) {
if (error instanceof MykoboApiError && error.status === 404) {
res.status(httpStatus.NOT_FOUND).json({ error: "Profile not found" });
return;
}
logger.error("Error in getProfileController:", error);
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: "Internal Server Error" });
}
};

export const createProfileController = async (req: Request, res: Response): Promise<void> => {
const userEmail = req.userEmail;
if (!userEmail) {
res.status(httpStatus.UNAUTHORIZED).json({ error: "Authenticated user email missing" });
return;
}

try {
const body = (req.body ?? {}) as Record<string, unknown>;

const walletAddress = body.wallet_address;
if (typeof walletAddress !== "string" || !isAddress(walletAddress)) {
res.status(httpStatus.BAD_REQUEST).json({ error: "Invalid wallet_address" });
return;
}

const formData = new FormData();
for (const field of PROFILE_TEXT_FIELDS) {
if (field === "email_address") continue;
const value = body[field];
if (typeof value === "string" && value.length > 0) {
formData.append(field, value);
}
}
formData.append("email_address", userEmail);

const files = (req as Request & { files?: Record<string, Express.Multer.File[]> }).files;
if (files && typeof files === "object") {
for (const fieldname of PROFILE_FILE_FIELDS) {
const file = files[fieldname]?.[0];
if (!file) continue;
formData.append(fieldname, new Blob([new Uint8Array(file.buffer)], { type: file.mimetype }), file.originalname);
}
}

const { profile } = await MykoboApiService.getInstance().createProfile(formData);
res.status(httpStatus.CREATED).json({ profile: toFrontendProfile(profile) });
} catch (error) {
if (error instanceof MykoboApiError) {
logger.warn(`Mykobo /profiles upstream error: status=${error.status}`);
res.status(error.status).json({ error: "Mykobo profile creation failed" });
return;
}
logger.error("Error in createProfileController:", error);
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: "Internal Server Error" });
}
};
3 changes: 1 addition & 2 deletions apps/api/src/api/controllers/pendulum.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
PendulumFundEphemeralErrorResponse,
PendulumFundEphemeralRequest,
PendulumFundEphemeralResponse,
StellarTokenConfig,
TOKEN_CONFIG,
XCMTokenConfig
} from "@vortexfi/shared";
Expand Down Expand Up @@ -66,7 +65,7 @@ export const sendStatusWithPk = async (): Promise<StatusResponse> => {

// Wait for all required token balances check.
await Promise.all(
Object.entries(TOKEN_CONFIG).map(async ([token, tokenConfig]: [string, StellarTokenConfig | XCMTokenConfig]) => {
Object.entries(TOKEN_CONFIG).map(async ([token, tokenConfig]: [string, XCMTokenConfig]) => {
logger.info(`Checking token ${token} balance...`);
if (!tokenConfig.pendulumCurrencyId) {
throw new Error(`Token ${token} does not have a currency id.`);
Expand Down
6 changes: 4 additions & 2 deletions apps/api/src/api/controllers/ramp.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { NextFunction, Request, Response } from "express";
import httpStatus from "http-status";
import logger from "../../config/logger";
import { APIError } from "../errors/api-error";
import { enrichAdditionalDataWithClientIp } from "../helpers/clientIp";
import { assertQuoteOwnership, assertRampOwnership } from "../middlewares/ownershipAuth";
import rampService from "../services/ramp/ramp.service";

Expand All @@ -36,9 +37,10 @@ export const registerRamp = async (req: Request, res: Response<RampProcess>, nex

await assertQuoteOwnership(req, quoteId);

// Start ramping process
const enrichedAdditionalData = await enrichAdditionalDataWithClientIp(additionalData, req);

const ramp = await rampService.registerRamp({
additionalData,
additionalData: enrichedAdditionalData,
quoteId,
signingAccounts,
userId: req.userId
Expand Down
127 changes: 0 additions & 127 deletions apps/api/src/api/controllers/stellar.controller.ts

This file was deleted.

20 changes: 1 addition & 19 deletions apps/api/src/api/controllers/storage.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,6 @@ export const DUMP_SHEET_COMMON_HEADERS = [
"outputTokenType"
];

export const DUMP_SHEET_HEADER_VALUES_ASSETHUB_TO_STELLAR = [
...DUMP_SHEET_COMMON_HEADERS,
"offramperAddress",
"stellarEphemeralPublicKey",
"spacewalkRedeemTx",
"stellarOfframpTx",
"stellarCleanupTx"
];

export const DUMP_SHEET_HEADER_VALUES_EVM_TO_STELLAR = [
...DUMP_SHEET_COMMON_HEADERS,
"offramperAddress",
"squidRouterReceiverId",
"squidRouterReceiverHash"
];

export const DUMP_SHEET_HEADER_VALUES_ASSETHUB_TO_BRLA = [
...DUMP_SHEET_COMMON_HEADERS,
"offramperAddress",
Expand Down Expand Up @@ -65,11 +49,9 @@ export const DUMP_SHEET_HEADER_VALUES_BRLA_TO_ASSETHUB = [

export const FLOW_HEADERS: Record<FlowType, FlowHeaders> = {
"assethub-to-brla": DUMP_SHEET_HEADER_VALUES_ASSETHUB_TO_BRLA,
"assethub-to-stellar": DUMP_SHEET_HEADER_VALUES_ASSETHUB_TO_STELLAR,
"brla-to-assethub": DUMP_SHEET_HEADER_VALUES_BRLA_TO_ASSETHUB,
"brla-to-evm": DUMP_SHEET_HEADER_VALUES_BRLA_TO_EVM,
"evm-to-brla": DUMP_SHEET_HEADER_VALUES_EVM_TO_BRLA,
"evm-to-stellar": DUMP_SHEET_HEADER_VALUES_EVM_TO_STELLAR
"evm-to-brla": DUMP_SHEET_HEADER_VALUES_EVM_TO_BRLA
};
export const storeData = async (req: Request, res: Response<StoreDataResponse | StoreDataErrorResponse>): Promise<void> => {
if (!spreadsheet.storageSheetId) {
Expand Down
Loading
Loading