Skip to content
Open
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
15 changes: 15 additions & 0 deletions .github/workflows/contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ jobs:

- name: 🧪 Live contract suites
working-directory: apps/api
# Sandbox credentials and pre-provisioned fixtures (see .env.example). A missing
# secret resolves to "" -> the suite's live half skips -> CONTRACT_EXPECT_LIVE
# fails that suite loudly instead of letting the drift detector rot as green.
env:
ALFREDPAY_BASE_URL: ${{ secrets.CONTRACT_ALFREDPAY_BASE_URL }}
ALFREDPAY_API_KEY: ${{ secrets.CONTRACT_ALFREDPAY_API_KEY }}
ALFREDPAY_API_SECRET: ${{ secrets.CONTRACT_ALFREDPAY_API_SECRET }}
ALFREDPAY_CONTRACT_CUSTOMER_ID: ${{ secrets.CONTRACT_ALFREDPAY_CUSTOMER_ID }}
ALFREDPAY_CONTRACT_FIAT_ACCOUNT_ID: ${{ secrets.CONTRACT_ALFREDPAY_FIAT_ACCOUNT_ID }}
ALFREDPAY_CONTRACT_KYC_SUBMISSION_ID: ${{ secrets.CONTRACT_ALFREDPAY_KYC_SUBMISSION_ID }}
BRLA_BASE_URL: ${{ secrets.CONTRACT_BRLA_BASE_URL }}
BRLA_API_KEY: ${{ secrets.CONTRACT_BRLA_API_KEY }}
BRLA_PRIVATE_KEY: ${{ secrets.CONTRACT_BRLA_PRIVATE_KEY }}
AVENIA_CONTRACT_SUBACCOUNT_ID: ${{ secrets.CONTRACT_AVENIA_SUBACCOUNT_ID }}
COINGECKO_API_KEY: ${{ secrets.COINGECKO_API_KEY }}
run: bun test src/tests/contracts/

# Non-blocking runs are only useful if somebody hears about failures.
Expand Down
14 changes: 14 additions & 0 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ ALFREDPAY_BASE_URL=your-alfredpay-base-url
ALFREDPAY_API_KEY=your-alfredpay-api-key
ALFREDPAY_API_SECRET=your-alfredpay-api-secret

# BRLA / Avenia
# BRLA_BASE_URL=
BRLA_API_KEY=your-brla-api-key
BRLA_PRIVATE_KEY=your-brla-private-key

# Integration test helpers (only required for phase-processor integration tests)
# BACKEND_TEST_STARTER_ACCOUNT=
# TAX_ID=

# External API contract tests (RUN_LIVE_TESTS=1, see docs/features/contract-tests.md).
# Pre-provisioned SANDBOX fixtures — the fixture-gated live tests create real (unpaid)
# sandbox transactions, so only ever point these at sandbox objects. Tests skip cleanly
# when unset.
# ALFREDPAY_CONTRACT_CUSTOMER_ID= # KYC-approved MX sandbox customer
# ALFREDPAY_CONTRACT_FIAT_ACCOUNT_ID= # SPEI fiat account of that customer
# ALFREDPAY_CONTRACT_KYC_SUBMISSION_ID= # a KYC submission of that customer
# AVENIA_CONTRACT_SUBACCOUNT_ID= # KYC-approved Avenia sandbox subaccount
3 changes: 2 additions & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"uuid": "^11.1.0",
"viem": "catalog:",
"web3": "^4.16.0",
"winston": "^3.1.0"
"winston": "^3.1.0",
"zod": "catalog:"
},
"description": "",
"devDependencies": {
Expand Down
19 changes: 19 additions & 0 deletions apps/api/src/api/services/priceFeed.schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { z } from "zod";

/**
* External API contract schema for the CoinGecko price feed consumed by
* PriceFeedService (see docs/features/contract-tests.md).
*
* GET /simple/price returns `{ [tokenId]: { [vsCurrency]: number } }`;
* getCryptoPrice reads exactly `data[tokenId][vsCurrency]` and treats a missing
* token or currency key as an error, so the consumed contract is: every entry
* present is an object of numeric prices. Which keys must be present depends on
* the request — the contract test asserts presence for the ids it requested.
*
* The Nabla AMM and DIA oracle rates the service also serves are read from
* Pendulum chain state, not HTTP — out of scope per the no-chain-fidelity
* non-goal of the contract-test PRD.
*/
export const coingeckoSimplePriceResponseSchema = z.record(z.string(), z.record(z.string(), z.number())) satisfies z.ZodType<
Record<string, Record<string, number>>
>;
6 changes: 4 additions & 2 deletions apps/api/src/test-utils/fake-world/fake-anchors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ export class FakeBrla {
payOutRate = 1;
subaccountId = "test-subaccount-id";
subaccountEvmWallet = "0x7ba99e99bc669b3508aff9cc0a898e869459f877";
/** KYC state served in subaccountInfo().accountInfo; production gates ramps on CONFIRMED. */
identityStatus: "NOT-IDENTIFIED" | "CONFIRMED" = "CONFIRMED";
/** Internal Avenia subaccount balances served by getAccountBalance; script per test. */
accountBalances = { BRLA: 0, USDC: 0, USDM: 0, USDT: 0 };
/** Status reported for every pay-in ticket by getAveniaPayinTickets. */
Expand Down Expand Up @@ -184,7 +186,7 @@ export class FakeBrla {
createPixInputTicket: async () => {
const ticket = {
brCode: `brcode-${++this.counter}`,
expiration: new Date(Date.now() + 3600_000),
expiration: new Date(Date.now() + 3600_000).toISOString(),
id: `pix-in-${this.counter}`
};
this.pixInputTickets.push(ticket);
Expand Down Expand Up @@ -219,7 +221,7 @@ export class FakeBrla {
}
}),
subaccountInfo: async () => ({
accountInfo: {},
accountInfo: { identityStatus: this.identityStatus },
brCode: "test-brcode",
createdAt: new Date().toISOString(),
id: this.subaccountId,
Expand Down
Loading
Loading