Skip to content
Open
55 changes: 55 additions & 0 deletions src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1525,3 +1525,58 @@ describe('GROUP 6: Edge Cases', () => {
})
})
})

// ============================================================
// GROUP: Entity deposit recipient (2026-09-14 Manteca split)
// ============================================================
describe('GROUP: Entity deposit recipient wiring', () => {
const LEGACY_AR_ADDRESS = '0x6E945f8EC93061f5f11Edc5e6Fb4A70BeB514e97'
const LEGACY_NON_AR_ADDRESS = '0x49200bF84dC26349C86ce040019063FeCE88CB1c'
const DISTINCT_SERVED = '0x49200bF84dC26349C86ce040019063FeCE88CB1c'

async function payWithLock(lockExtra: Record<string, unknown>) {
mockMantecaApi.initiateQrPayment.mockResolvedValue({
code: 'LOCK123',
type: 'QR3_PAYMENT',
companyId: 'c1',
userId: 'u1',
userNumberId: 'un1',
userExternalId: 'ue1',
paymentRecipientName: 'Test Merchant',
paymentRecipientLegalId: 'legal1',
paymentAssetAmount: '12000',
paymentAsset: 'ARS',
paymentPrice: '1200',
paymentAgainstAmount: '10',
paymentAgainst: 'USD',
expireAt: '2026-04-16T23:59:59Z',
creationTime: '2026-04-16T00:00:00Z',
...lockExtra,
})

renderQrPay({ qrCode: 'mercadopago://pay?id=123', type: 'MERCADO_PAGO', t: '1' })
await waitFor(() => {
expect(screen.getByText('Test Merchant')).toBeInTheDocument()
})
const payButton = screen.getByRole('button', { name: 'Pay' })
await act(async () => {
fireEvent.click(payButton)
})
await waitFor(() => expect(mockSignSpend).toHaveBeenCalledTimes(1))
}

test('the spend is signed to the API-served entity depositAddress, not the constant', async () => {
// A DISTINCT address (the non-AR wallet for a MERCADO_PAGO QR, which
// the constant fallback would never pick) proves the wire value wins.
await payWithLock({ depositAddress: DISTINCT_SERVED })

expect(mockSignSpend).toHaveBeenCalledWith(expect.objectContaining({ recipient: DISTINCT_SERVED }))
})

test('an older API without depositAddress falls back to the per-rail constant', async () => {
await payWithLock({})

expect(mockSignSpend).toHaveBeenCalledWith(expect.objectContaining({ recipient: LEGACY_AR_ADDRESS }))
expect(mockSignSpend).not.toHaveBeenCalledWith(expect.objectContaining({ recipient: LEGACY_NON_AR_ADDRESS }))
})
})
13 changes: 10 additions & 3 deletions src/app/(mobile-ui)/qr-pay/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { SessionKeyGrantRequiredError } from '@/hooks/wallet/spendPreflight'
import { friendlyError } from '@/utils/friendly-error.utils'
import { useFriendlyError } from '@/hooks/useFriendlyError'
import { useRainCardOverview } from '@/hooks/useRainCardOverview'
import { pickMantecaDepositAddress } from '@/utils/manteca.utils'
import { rainCentsToUsdcUnits, isAmountWithinBalance } from '@/utils/balance.utils'
import { formatNumberForDisplay } from '@/utils/general.utils'
import { getShakeClass, type ShakeIntensity } from '@/utils/perk.utils'
Expand Down Expand Up @@ -753,9 +754,15 @@ export default function QRPayPage() {
const requiredUsdcAmount = parseUnits(finalPaymentLock.paymentAgainstAmount, PEANUT_WALLET_TOKEN_DECIMALS)
signedArtifact = await signSpend({
requiredUsdcAmount,
// Per-rail Manteca QR funding wallet: Pix → non-AR, everything else → AR
// (same binary heuristic as the backend's getQrReceiveAddress).
recipient: qrType === EQrType.PIX ? MANTECA_QR_DEPOSIT_ADDRESS_NON_AR : MANTECA_QR_DEPOSIT_ADDRESS_AR,
// Entity-aware deposit address served by the API (per-entity
// balances from 2026-09-14) — the backend resolves the entity
// from the QR and the paying Manteca account. The per-rail
// constants remain only as a fallback for an older API that
// does not return the field yet.
recipient: pickMantecaDepositAddress(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR: Cover the page-level spend recipient wiring

The new helper tests prove only the helper in isolation, while the claim-link test covers only the third flow. The existing QR page suite invokes signSpend but never gives /init a distinct valid depositAddress or asserts recipient, and the bank-withdraw page has no test at all. A future swap back to a fallback constant or use of the stale lock would still leave all new tests green while sending funds to the wrong entity. Add submit-path tests on both pages that return a distinct API address and assert the exact recipient passed to signSpend.

finalPaymentLock.depositAddress,
qrType === EQrType.PIX ? MANTECA_QR_DEPOSIT_ADDRESS_NON_AR : MANTECA_QR_DEPOSIT_ADDRESS_AR
),
rainSpendingPower: rainCentsToUsdcUnits(rainCardOverview?.balance?.spendingPower),
kind: 'QR_PAY',
})
Expand Down
7 changes: 5 additions & 2 deletions src/app/(mobile-ui)/withdraw/manteca/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { SessionKeyGrantRequiredError } from '@/hooks/wallet/spendPreflight'
import { friendlyError } from '@/utils/friendly-error.utils'
import { useFriendlyError } from '@/hooks/useFriendlyError'
import { resolveOfframpSpendRecipient } from '@/utils/manteca.utils'
import { rainCentsToUsdcUnits, isAmountWithinBalance } from '@/utils/balance.utils'
import { useRainCardOverview } from '@/hooks/useRainCardOverview'
import { useState, useMemo, useContext, useEffect, useCallback, useId } from 'react'
Expand Down Expand Up @@ -51,7 +52,6 @@
import PointsCard from '@/components/Common/PointsCard'
import {
MANTECA_COUNTRIES_CONFIG,
MANTECA_DEPOSIT_ADDRESS,
MantecaAccountType,
isMantecaSupportedCountryCode,
type MantecaBankCode,
Expand Down Expand Up @@ -283,7 +283,7 @@

const isCompleteBankDetails = useMemo<boolean>(() => {
return (
!!destinationAddress.trim() &&

Check warning on line 286 in src/app/(mobile-ui)/withdraw/manteca/page.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook useCallback has a missing dependency: 'setErrorMessage'. Either include it or remove the dependency array
(!countryConfig?.needsBankCode || selectedBank != null) &&
(!countryConfig?.needsAccountType || accountType != null)
)
Expand All @@ -292,7 +292,7 @@
const handleBankDetailsSubmit = useCallback(async () => {
// prevent duplicate requests from rapid clicks
if (isLockingPrice) return

Check warning on line 295 in src/app/(mobile-ui)/withdraw/manteca/page.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook useMemo has an unnecessary dependency: 'setErrorMessage'. Either exclude it or remove the dependency array
if (!destinationAddress.trim()) {
setErrorMessage(t('errors.enterAccountAddress'))
return
Expand Down Expand Up @@ -383,7 +383,10 @@
const requiredUsdcAmount = parseUnits(usdAmount, PEANUT_WALLET_TOKEN_DECIMALS)
signedArtifact = await signSpend({
requiredUsdcAmount,
recipient: MANTECA_DEPOSIT_ADDRESS,
// Entity-aware deposit address served by /withdraw/init
// (per-entity balances from 2026-09-14); the constant is
// only the fallback for an older API without the field.
recipient: resolveOfframpSpendRecipient(priceLock),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR: Exercise the bank-withdraw recipient at the signing boundary

The new utility test proves only that resolveOfframpSpendRecipient returns its input; no test renders this page and observes the argument passed to signSpend. A later regression that restores the constant here, selects the wrong lock, or bypasses the resolver leaves that suite green while signing a money transfer to the wrong entity. Add a page-level test that seeds a price lock with a distinct served address, confirms the withdrawal, and asserts that exact address reaches signSpend.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINOR: Keep BRL collateral offramps out of QR classification

For a collateral-only BRL bank withdrawal, this API-served recipient is also the non-AR QR funding address recognized by the current Rain prepare classifier. useSignSpendBundle forwards it to /rain/cards/withdraw/prepare, whose current server contract ignores the client-supplied FIAT_OFFRAMP kind and classifies by recipient, so the preparation becomes a duplicate QR_PAY; a failed or cancelled funding leg is then surfaced and reconciled as a QR payment instead of an offramp. Update the paired API classification/route context for this entity address and pin the BRL direct-transfer case.

rainSpendingPower: rainCentsToUsdcUnits(rainCardOverview?.balance?.spendingPower),
kind: 'FIAT_OFFRAMP',
})
Expand Down Expand Up @@ -542,7 +545,7 @@
// Use hasPendingTransactions to prevent race condition with optimistic updates
// isLoading covers the gap between sendMoney completing and API withdraw completing
if (hasPendingTransactions || isLoading) {
return

Check warning on line 548 in src/app/(mobile-ui)/withdraw/manteca/page.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook useEffect has a missing dependency: 'resetState'. Either include it or remove the dependency array
}

if (!usdAmount || usdAmount === '0.00' || isNaN(Number(usdAmount)) || balance === undefined) {
Expand Down
39 changes: 34 additions & 5 deletions src/components/Claim/Link/views/MantecaReviewStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { MercadoPagoStep } from '@/types/manteca.types'
import { type Dispatch, type FC, type SetStateAction, useState } from 'react'
import useClaimLink from '@/components/Claim/useClaimLink'
import * as Sentry from '@sentry/nextjs'
import { MANTECA_DEPOSIT_ADDRESS } from '@/constants/manteca.consts'
import { requireMantecaDepositAddress } from '@/utils/manteca.utils'
import { useTranslations } from 'next-intl'

interface MantecaReviewStepProps {
Expand Down Expand Up @@ -62,9 +62,31 @@ const MantecaReviewStep: FC<MantecaReviewStepProps> = ({
setError(null)
setIsSubmitting(true)

// Entity-aware deposit address (per-entity balances from
// 2026-09-14): ask /withdraw/init where THIS currency's
// offramp must be funded BEFORE spending the one-shot claim
// link. This path FAILS CLOSED on any init problem — error,
// missing field, malformed or zero address — because no funds
// have moved yet and the user can retry, while claiming to a
// guessed address and then failing would irreversibly strand
// the link's funds at the wrong entity. (The signed flows keep
// a constant fallback because the backend validates their
// recipient before anything is broadcast; nothing validates a
// link claim.)
const { data: initData, error: initError } = await mantecaApi.initiateWithdraw({ amount, currency })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR: Add coverage for the pre-claim entity lookup

This is the safety boundary that keeps a one-shot link from funding the wrong Manteca entity, but no test renders MantecaReviewStep or exercises this branch. A later refactor could ignore initData.depositAddress or let claimLinkSecure run after an init error, stranding a BRL link after the entity cutoff without any suite failure. Add component tests that assert the API-served address is passed to claimLinkSecure and that an init error calls neither claimLinkSecure nor withdraw. The QR-pay and bank-withdraw signSpend recipient selections should likewise be pinned because they move funds.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR: [claude-opus] Claim-link offramp fails closed on a field the merged API does not serve — hard deploy-order dependency on the peanut-api-ts half

MantecaReviewStep now calls mantecaApi.initiateWithdraw and aborts the whole flow unless the reply carries a valid depositAddress (src/components/Claim/Link/views/MantecaReviewStep.tsx:76-85, requireMantecaDepositAddress returns null for a missing field). In the pinned peanut-api-ts checkout, /manteca/withdraw/init replies with exactly {priceLockCode, price, expiresAt, usdAmount, fiatAmount, currency} — no depositAddress (src/routes/manteca/withdraw.ts:193-200). Against that API every regional (MercadoPago/PIX) claim-link withdrawal shows manteca.errors.generic and never claims, i.e. the feature is 100% down.

Unlike qr-pay and the bank-withdraw page — where the author deliberately kept a constant fallback, so those degrade safely — this path has no fallback by design, which is the right safety call but makes the FE unshippable ahead of the API.

Second, coupled evidence in the same direction: once the API does serve an entity address, a BRL claim will be claimed to the CRYPTO_GLOBAL address, while the merged legacy withdraw route still validates the funding transfer against MANTECA_RECEIVE_ADDRESS_ARG only (src/routes/manteca/withdraw.ts:325-327), and the Rain offramp path rejects any recipient other than that constant (src/routes/manteca/withdraw.ts:1219-1230).

This is almost certainly the paired half in the open peanut-api-ts#1487 (its legalEntity.ts documents acceptedAddresses tolerance during rollout), which I cannot read in full — so major, not blocking. What to confirm before merge: (a) that #1487 adds depositAddress to the /manteca/withdraw/init response specifically, not only to /manteca/qr-payment/init; (b) that the legacy tx-hash withdraw route accepts the entity address as well as the ARG constant. And state the deploy order in the PR: the API side must be live before this frontend.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR: [claude-opus] Claim-link offramp fails closed on a field the merged API does not serve

MantecaReviewStep now calls mantecaApi.initiateWithdraw({amount,currency}) and aborts the whole flow when depositAddress is absent (requireMantecaDepositAddress → null → generic error, no claim, no withdraw). In the pinned peanut-api-ts checkout, /manteca/withdraw/init (src/routes/manteca/withdraw.ts:84) replies with exactly {priceLockCode, price, expiresAt, usdAmount, fiatAmount, currency} — no depositAddress. Against that API every Manteca claim-link claim (MercadoPago/PIX) is dead on arrival; unlike qr-pay and the bank-withdraw page, this path has no constant fallback by design. That checkout holds only merged code, so the serving half is almost certainly the open peanut-api-ts#1487 (Manteca legal-entity deposit routing) that I cannot read — this is a deploy-order dependency, not necessarily a design error: the API half must be deployed before this frontend, and that should be stated on the PR. Worth confirming with the API author while you're there: /manteca/withdraw still validates the funding transfer against MANTECA_RECEIVE_ADDRESS_ARG only (src/routes/manteca/withdraw.ts:325-330), so once depositAddress is served for a non-CRYPTO_ARG entity (e.g. a BRL claim), that tx-hash validator must accept the served address too — the one-shot link is already spent by then. #1487's legalEntity.ts acceptedAddresses looks like it covers this, but it is not in the visible diff.

if (initError) {
setError(t('manteca.errors.generic'))
return
}
const depositAddress = requireMantecaDepositAddress(initData?.depositAddress)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR: Deploy the response field before requiring it

The current API policy branch returns the price-lock fields from /manteca/withdraw/init but not depositAddress. With that response, initData?.depositAddress is undefined here, requireMantecaDepositAddress returns null, and every regional claim-link offramp aborts before spending the link. Deploy and verify the paired API response for every supported currency before releasing this UI, or gate this client path until that contract is live.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR: Deploy the response field before requiring it

The current API policy branch returns the price-lock fields from /manteca/withdraw/init but not depositAddress. With that response, initData?.depositAddress is undefined here, requireMantecaDepositAddress returns null, and every regional claim-link offramp aborts before spending the link. Deploy and verify the paired API response for every supported currency before releasing this UI, or gate this client path until that contract is live.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d8798af with the gate you suggested: the strict requirement now applies only to an ENTITY-AWARE init response (legalEntity present); a pre-entity response falls back to the legacy constant, which that API still validates — so the deploy window where this UI meets the older API keeps claim links working, and once api#1487 (which DOES serve depositAddress+legalEntity from /withdraw/init) deploys, missing/invalid addresses fail closed exactly as before. Deploy order (API first) also stated in the PR body. The opus MINOR (BRL offramp classified QR_PAY at the shared CRYPTO_GLOBAL address) is already fixed on the API side: api#1487's src/rain/prepare-kind.ts makes classifyRainPrepare overlap-aware via the client-declared kind, with tests pinning the shared-address disambiguation.

if (!depositAddress) {
setError(t('manteca.errors.generic'))
return
}

// Use secure SDK claim (password stays client-side, only signature sent to backend)
const txHash = await claimLinkSecure({
address: MANTECA_DEPOSIT_ADDRESS,
address: depositAddress,
link: claimLink,
})

Expand All @@ -74,7 +96,7 @@ const MantecaReviewStep: FC<MantecaReviewStepProps> = ({
}

// Associate the claim with user if logged in
// CRITICAL: This is blocking for Manteca because claims to MANTECA_DEPOSIT_ADDRESS
// CRITICAL: This is blocking for Manteca because claims to the Manteca deposit address
// won't appear in history without this association (recipientAddress != user address)
try {
await sendLinksApi.associateClaim(txHash)
Expand Down Expand Up @@ -102,7 +124,11 @@ const MantecaReviewStep: FC<MantecaReviewStepProps> = ({
}
}

const { data, error: withdrawError } = await mantecaApi.withdraw({
const {
data,
error: withdrawError,
message: withdrawMessage,
} = await mantecaApi.withdraw({
amount,
destinationAddress: destinationAddress.toLowerCase(),
txHash,
Expand All @@ -113,7 +139,10 @@ const MantecaReviewStep: FC<MantecaReviewStepProps> = ({
if (withdrawError === 'TAX_ID_MISMATCH' || withdrawError === 'CUIT_MISMATCH') {
setError(t('manteca.ownAccountOnly'))
} else {
setError(withdrawError || t('manteca.errors.generic'))
// Prefer the API's human-written message over the raw
// wire code — CLAIM_STORE_UNAVAILABLE as literal screen
// text helps nobody whose funds already left the link.
setError(withdrawMessage || withdrawError || t('manteca.errors.generic'))
}
return
}
Expand Down
126 changes: 126 additions & 0 deletions src/components/Claim/Link/views/__tests__/MantecaReviewStep.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* MantecaReviewStep — the claim-link offramp's pre-claim entity lookup.
*
* This is the safety boundary that keeps a ONE-SHOT claim link from funding
* the wrong Manteca entity after the 2026-09-14 split:
* - the API-served depositAddress from /withdraw/init must be the address
* the link is claimed to,
* - an init failure must abort BEFORE the link is spent — no claim, no
* withdraw — because the link cannot be re-claimed.
*/
import React from 'react'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { IntlWrapper } from '@/test-utils/intl'

const mockInitiateWithdraw = jest.fn()
const mockWithdraw = jest.fn()
jest.mock('@/services/manteca', () => ({
mantecaApi: {
initiateWithdraw: (...args: unknown[]) => mockInitiateWithdraw(...args),
withdraw: (...args: unknown[]) => mockWithdraw(...args),
},
}))

const mockAssociateClaim = jest.fn()
jest.mock('@/services/sendLinks', () => ({
sendLinksApi: { associateClaim: (...args: unknown[]) => mockAssociateClaim(...args) },
}))

const mockClaimLinkSecure = jest.fn()
jest.mock('@/components/Claim/useClaimLink', () => ({
__esModule: true,
default: () => ({ claimLink: mockClaimLinkSecure }),
}))

jest.mock('@/hooks/useCurrency', () => ({
useCurrency: () => ({ price: { sell: '1300' }, isLoading: false, refetch: jest.fn() }),
}))

jest.mock('@sentry/nextjs', () => ({ captureException: jest.fn() }))

jest.mock('@/components/0_Bruddle/Toast', () => ({
...jest.requireActual('@/components/0_Bruddle/Toast'),
useToast: () => ({ toast: jest.fn(), success: jest.fn(), error: jest.fn(), info: jest.fn() }),
}))

import MantecaReviewStep from '../MantecaReviewStep'

const SERVED_ADDRESS = '0x49200bF84dC26349C86ce040019063FeCE88CB1c'

function renderStep() {
const setCurrentStep = jest.fn()
render(
<IntlWrapper>
<MantecaReviewStep
setCurrentStep={setCurrentStep}
claimLink="https://peanut.me/claim#p=test"
destinationAddress="somepixkey@bank.br"
amount="10.00"
currency="BRL"
/>
</IntlWrapper>
)
return { setCurrentStep }
}

function clickConfirm() {
// The single primary action button on the review card.
fireEvent.click(screen.getAllByRole('button')[0])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR: Click the actual withdraw action in the claim-flow tests

clickConfirm() presses the first button, but the first button rendered by this component is the destination row's Copy control, not Withdraw. At this exact head the unit check therefore fails all four new cases with zero calls, and the pre-claim lookup remains unexecuted. Select the Withdraw button by accessible name and isolate the malformed-address iterations (for example with test.each) so every case exercises the submit handler.

}

beforeEach(() => {
jest.clearAllMocks()
mockClaimLinkSecure.mockResolvedValue('0x' + 'ab'.repeat(32))
mockAssociateClaim.mockResolvedValue(undefined)
mockWithdraw.mockResolvedValue({ data: { id: 'synthetic-1' } })
})

describe('MantecaReviewStep — pre-claim entity lookup', () => {
test('claims the link to the API-served entity deposit address', async () => {
mockInitiateWithdraw.mockResolvedValue({ data: { priceLockCode: 'pl-1', depositAddress: SERVED_ADDRESS } })

renderStep()
clickConfirm()

await waitFor(() => expect(mockClaimLinkSecure).toHaveBeenCalledTimes(1))
expect(mockInitiateWithdraw).toHaveBeenCalledWith({ amount: '10.00', currency: 'BRL' })
expect(mockClaimLinkSecure).toHaveBeenCalledWith(expect.objectContaining({ address: SERVED_ADDRESS }))
await waitFor(() => expect(mockWithdraw).toHaveBeenCalledTimes(1))
})

test('FAILS CLOSED when the API returns no depositAddress — the one-shot link is never spent', async () => {
mockInitiateWithdraw.mockResolvedValue({ data: { priceLockCode: 'pl-1' } })

renderStep()
clickConfirm()

await waitFor(() => expect(mockInitiateWithdraw).toHaveBeenCalledTimes(1))
expect(mockClaimLinkSecure).not.toHaveBeenCalled()
expect(mockWithdraw).not.toHaveBeenCalled()
})

test('FAILS CLOSED on a malformed or zero served address', async () => {
for (const bad of ['', 'not-an-address', '0x0000000000000000000000000000000000000000']) {
jest.clearAllMocks()
mockInitiateWithdraw.mockResolvedValue({ data: { priceLockCode: 'pl-1', depositAddress: bad } })

renderStep()
clickConfirm()

await waitFor(() => expect(mockInitiateWithdraw).toHaveBeenCalledTimes(1))
expect(mockClaimLinkSecure).not.toHaveBeenCalled()
}
})

test('an init error aborts BEFORE the one-shot link is spent — no claim, no withdraw', async () => {
mockInitiateWithdraw.mockResolvedValue({ error: 'Failed to lock withdraw price.' })

renderStep()
clickConfirm()

await waitFor(() => expect(mockInitiateWithdraw).toHaveBeenCalledTimes(1))
expect(mockClaimLinkSecure).not.toHaveBeenCalled()
expect(mockWithdraw).not.toHaveBeenCalled()
expect(mockAssociateClaim).not.toHaveBeenCalled()
})
})
10 changes: 10 additions & 0 deletions src/services/manteca.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ export type QrPaymentLock = {
paymentAgainst: string
expireAt: string
creationTime: string
/** Entity-aware Manteca deposit address served by the API (per-entity
* balances from 2026-09-14). Optional only while an older API without
* the field may still be deployed — prefer it over local constants. */
depositAddress?: Address
legalEntity?: string
}

export type QrPaymentResponse =
Expand Down Expand Up @@ -113,6 +118,11 @@ export type WithdrawPriceLock = {
usdAmount: string
fiatAmount: string
currency: string
/** Entity-aware Manteca deposit address served by the API (per-entity
* balances from 2026-09-14). Optional only while an older API without
* the field may still be deployed — prefer it over local constants. */
depositAddress?: Address
legalEntity?: string
}

export const mantecaApi = {
Expand Down
Loading
Loading