@ar-agents/treasury 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/dist/index.cjs +843 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +611 -0
- package/dist/index.d.ts +611 -0
- package/dist/index.js +808 -0
- package/dist/index.js.map +1 -0
- package/dist/tools.cjs +278 -0
- package/dist/tools.cjs.map +1 -0
- package/dist/tools.d.cts +132 -0
- package/dist/tools.d.ts +132 -0
- package/dist/tools.js +274 -0
- package/dist/tools.js.map +1 -0
- package/package.json +102 -0
- package/tools.manifest.json +158 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MantecaOffRampAdapter — the real PSAV off-ramp behind the treasury rail.
|
|
3
|
+
*
|
|
4
|
+
* Manteca (manteca.dev) is an Argentine crypto-fiat infrastructure provider. We
|
|
5
|
+
* integrate it as a registered-PSAV off-ramp; we never custody the conversion
|
|
6
|
+
* ourselves (CNV RG 1058/2025). This is a thin, typed client over Manteca's
|
|
7
|
+
* documented v2 API.
|
|
8
|
+
*
|
|
9
|
+
* GROUNDING — what is verified vs. what to confirm at onboarding
|
|
10
|
+
* ─────────────────────────────────────────────────────────────
|
|
11
|
+
* VERIFIED from docs.manteca.dev / developers.manteca.dev (jun-2026):
|
|
12
|
+
* - Auth header is `md-api-key`.
|
|
13
|
+
* - GET /v2/prices/direct/{ticker} (a direct pair price)
|
|
14
|
+
* - POST /v2/price-locks {userAnyId,side,asset,against} -> {code,price}
|
|
15
|
+
* - POST /v2/synthetics/ramp-off {userId,sellAmount,sellAsset,withdrawAsset,
|
|
16
|
+
* bankAccountId,externalId?} -> {id,status,stages[],...}
|
|
17
|
+
* - GET /v2/synthetics/{id} (synthetic status)
|
|
18
|
+
* - POST /v2/onboarding-actions/add-bank-account (register a CBU/CVU/alias; needs legalId)
|
|
19
|
+
* - 429 responses carry internalStatus: "RATE_LIMITED".
|
|
20
|
+
* - `externalId` is the idempotency key on synthetics/orders/withdraws.
|
|
21
|
+
*
|
|
22
|
+
* CONFIRM with your account before going to prod (all are config, one-line fixes):
|
|
23
|
+
* - `baseUrl`: defaults to https://api.manteca.dev. The PUBLIC DOCS host is
|
|
24
|
+
* developers.manteca.dev; the live API host ships with your credentials.
|
|
25
|
+
* - `ticker`: defaults to "USDC_ARS"; confirm the exact pair string.
|
|
26
|
+
* - The JSON shape of the price response and the synthetic status enum — both
|
|
27
|
+
* are parsed DEFENSIVELY here and normalized; verify against a sandbox call.
|
|
28
|
+
*
|
|
29
|
+
* NOT yet integration-tested: Manteca onboarding is sales-gated (no self-serve
|
|
30
|
+
* keys), so this client is unit-tested against mocked HTTP (the request contract
|
|
31
|
+
* is pinned exactly). LIVE-PROBED 2026-06-24: api.manteca.dev is reachable but the
|
|
32
|
+
* live API host + price path ship per-account (GET /v2/prices/direct/USDC_ARS ->
|
|
33
|
+
* 404 unauth), so there is no public proving surface — confirm the host + ticker
|
|
34
|
+
* at onboarding. Going live = set the 3 config items above. See
|
|
35
|
+
* ../../TREASURY-FISCAL-RAIL.md §5. Run: `scripts/live-offramp.mjs manteca`.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
interface MantecaConfig {
|
|
39
|
+
/** API key, sent as the `md-api-key` header. */
|
|
40
|
+
apiKey: string;
|
|
41
|
+
/** Manteca user/company id that owns the crypto balance + the bank account. */
|
|
42
|
+
userId: string;
|
|
43
|
+
/** Registered destination bank account id (the society's CVU) for ARS payout. */
|
|
44
|
+
bankAccountId: string;
|
|
45
|
+
/**
|
|
46
|
+
* API base URL. Default https://api.manteca.dev. CONFIRM at onboarding — the
|
|
47
|
+
* public docs live at developers.manteca.dev; the live API host comes with
|
|
48
|
+
* your credentials.
|
|
49
|
+
*/
|
|
50
|
+
baseUrl?: string;
|
|
51
|
+
/** Crypto asset sold. Default "USDC". */
|
|
52
|
+
sellAsset?: string;
|
|
53
|
+
/** Fiat received + withdrawn. Default "ARS". */
|
|
54
|
+
fiatAsset?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Pair ticker for quote(). Default `${sellAsset}_${fiatAsset}` ("USDC_ARS").
|
|
57
|
+
* Confirm the exact string accepted by /v2/prices/direct for your account.
|
|
58
|
+
*/
|
|
59
|
+
ticker?: string;
|
|
60
|
+
/** Injectable fetch (tests / non-global-fetch runtimes). Default global fetch. */
|
|
61
|
+
fetchImpl?: typeof fetch;
|
|
62
|
+
/** Injectable clock for the externalId fallback. Default Date.now. */
|
|
63
|
+
now?: () => number;
|
|
64
|
+
}
|
|
65
|
+
/** A non-2xx (or transport) failure from the Manteca API. */
|
|
66
|
+
declare class MantecaApiError extends Error {
|
|
67
|
+
readonly status: number;
|
|
68
|
+
readonly body?: unknown | undefined;
|
|
69
|
+
constructor(message: string, status: number, body?: unknown | undefined);
|
|
70
|
+
}
|
|
71
|
+
/** 401/403 — bad or unauthorized api key. */
|
|
72
|
+
declare class MantecaAuthError extends MantecaApiError {
|
|
73
|
+
constructor(message: string, status: number, body?: unknown);
|
|
74
|
+
}
|
|
75
|
+
/** 429 — per-user rate limit (internalStatus RATE_LIMITED). */
|
|
76
|
+
declare class MantecaRateLimitError extends MantecaApiError {
|
|
77
|
+
constructor(message: string, status: number, body?: unknown);
|
|
78
|
+
}
|
|
79
|
+
declare class MantecaOffRampAdapter implements OffRampAdapter {
|
|
80
|
+
private readonly config;
|
|
81
|
+
private readonly baseUrl;
|
|
82
|
+
private readonly sellAsset;
|
|
83
|
+
private readonly fiatAsset;
|
|
84
|
+
private readonly ticker;
|
|
85
|
+
private readonly fetchImpl;
|
|
86
|
+
private readonly now;
|
|
87
|
+
constructor(config: MantecaConfig);
|
|
88
|
+
private request;
|
|
89
|
+
/** Quote a USDC->ARS off-ramp using the direct sell-side price. */
|
|
90
|
+
quote(amountUsd: Usd): Promise<OffRampQuote>;
|
|
91
|
+
/**
|
|
92
|
+
* Fire the off-ramp synthetic: sell `amountUsd` of crypto and withdraw the ARS
|
|
93
|
+
* to the configured CVU. IRREVERSIBLE — gate behind requireConfirmation (RFC-001)
|
|
94
|
+
* and write to the signed audit log. Returns immediately with the synthetic id;
|
|
95
|
+
* the ARS settles asynchronously (poll getStatus). `arsReceived` is the EXPECTED
|
|
96
|
+
* amount at submission (from a fresh quote); the settled figure comes from getStatus.
|
|
97
|
+
*/
|
|
98
|
+
convert(amountUsd: Usd, opts: {
|
|
99
|
+
externalId: string;
|
|
100
|
+
}): Promise<OffRampReceipt>;
|
|
101
|
+
/** Poll a ramp-off synthetic and normalize its settlement state. */
|
|
102
|
+
getStatus(txId: string): Promise<OffRampStatusReport>;
|
|
103
|
+
/**
|
|
104
|
+
* One-time onboarding helper: register the society's CBU/CVU/alias as a payout
|
|
105
|
+
* destination. The Manteca user must have `legalId` set first. Returns the
|
|
106
|
+
* created account id to use as `bankAccountId`.
|
|
107
|
+
*/
|
|
108
|
+
registerBankAccount(input: {
|
|
109
|
+
cbuOrCvuOrAlias: string;
|
|
110
|
+
label?: string;
|
|
111
|
+
}): Promise<{
|
|
112
|
+
bankAccountId: string;
|
|
113
|
+
raw: unknown;
|
|
114
|
+
}>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* RipioOffRampAdapter — the second registered-PSAV off-ramp behind the treasury
|
|
119
|
+
* rail, proving the OffRampAdapter interface is provider-agnostic (no single-PSAV
|
|
120
|
+
* lock-in). Ripio's operating entity (Moonbird SRL) is a CNV-registered PSAV.
|
|
121
|
+
*
|
|
122
|
+
* Ripio's off-ramp is a SESSION model (unlike Manteca's sell-from-balance): you
|
|
123
|
+
* create a session bound to a fiat account (the society's CVU), Ripio returns a
|
|
124
|
+
* deposit ADDRESS, the society sends USDC there, and Ripio converts + pays ARS to
|
|
125
|
+
* the CVU. So convert() creates the session and returns the deposit address (on
|
|
126
|
+
* the receipt); completion needs an on-chain transfer the society makes from its
|
|
127
|
+
* wallet, then getStatus() tracks it.
|
|
128
|
+
*
|
|
129
|
+
* GROUNDING (docs.ripio.com, jun-2026):
|
|
130
|
+
* - Auth: OAuth2 client-credentials. POST /oauth2/token/ with
|
|
131
|
+
* `Authorization: Basic base64(clientId:clientSecret)` + body
|
|
132
|
+
* `grant_type=client_credentials` -> { access_token, token_type, expires_in }.
|
|
133
|
+
* Then `Authorization: Bearer <token>`.
|
|
134
|
+
* - POST /api/v1/quotes/ { fromCurrency, toCurrency, fromAmount, chain, paymentMethodType }
|
|
135
|
+
* -> { quoteId, rate, toAmount, finalToAmount, fees[], expiration }
|
|
136
|
+
* - POST /api/v1/fiatAccounts/ { customerId, paymentMethodType, accountFields:{ alias_or_cvu_destination } }
|
|
137
|
+
* -> { id, status } (one-time CVU registration)
|
|
138
|
+
* - POST /api/v1/offrampSession/ { fiatAccountId, ... } -> { sessionId, depositAddresses:[{chain,address}], ... }
|
|
139
|
+
* - GET /api/v1/offrampSession/{id} -> session status
|
|
140
|
+
*
|
|
141
|
+
* CONFIRM at onboarding (all config, sales-gated credentials):
|
|
142
|
+
* - baseUrl: defaults to the sandbox; pass prod to go live.
|
|
143
|
+
* - `chain`: defaults to "BASE" — Ripio's public docs only enumerate ETHEREUM
|
|
144
|
+
* in static examples; confirm Base/USDC is enabled via GET /api/v1/depositNetworks/.
|
|
145
|
+
* - The exact offrampSession request body + status enum (parsed defensively here).
|
|
146
|
+
*
|
|
147
|
+
* Request contract pinned + unit-tested vs mocked HTTP. LIVE-PROBED 2026-06-24:
|
|
148
|
+
* the sandbox host is up and `POST /oauth2/token/` returns `{error:"invalid_client"}`
|
|
149
|
+
* (401) while `POST /api/v1/quotes/` returns 401 — the exact wire + error shapes
|
|
150
|
+
* this adapter handles (RipioAuthError), verified up to the credential boundary.
|
|
151
|
+
* Only sales-gated client creds remain; Ripio is the soonest path to a full live
|
|
152
|
+
* run (open sandbox + deposit simulation). Run: `scripts/live-offramp.mjs ripio`.
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
declare const RIPIO_SANDBOX = "https://sandbox-b2b.ripio.com";
|
|
156
|
+
declare const RIPIO_PROD = "https://b2b-api.ripio.com";
|
|
157
|
+
interface RipioConfig {
|
|
158
|
+
clientId: string;
|
|
159
|
+
clientSecret: string;
|
|
160
|
+
/** Ripio B2B customer id (the KYC'd society). */
|
|
161
|
+
customerId: string;
|
|
162
|
+
/** Pre-registered fiat account id (the society's CVU). See registerFiatAccount. */
|
|
163
|
+
fiatAccountId: string;
|
|
164
|
+
/** API base URL. Default = sandbox. Pass RIPIO_PROD for live. */
|
|
165
|
+
baseUrl?: string;
|
|
166
|
+
/** Settlement chain. Default "BASE". CONFIRM Base is enabled for your account. */
|
|
167
|
+
chain?: string;
|
|
168
|
+
/** Crypto sold. Default "USDC". */
|
|
169
|
+
fromCurrency?: string;
|
|
170
|
+
/** Fiat received. Default "ARS". */
|
|
171
|
+
toCurrency?: string;
|
|
172
|
+
/** Default "bank_transfer". */
|
|
173
|
+
paymentMethodType?: string;
|
|
174
|
+
fetchImpl?: typeof fetch;
|
|
175
|
+
now?: () => number;
|
|
176
|
+
}
|
|
177
|
+
declare class RipioApiError extends Error {
|
|
178
|
+
readonly status: number;
|
|
179
|
+
readonly body?: unknown | undefined;
|
|
180
|
+
constructor(message: string, status: number, body?: unknown | undefined);
|
|
181
|
+
}
|
|
182
|
+
declare class RipioAuthError extends RipioApiError {
|
|
183
|
+
constructor(message: string, status: number, body?: unknown);
|
|
184
|
+
}
|
|
185
|
+
declare class RipioRateLimitError extends RipioApiError {
|
|
186
|
+
constructor(message: string, status: number, body?: unknown);
|
|
187
|
+
}
|
|
188
|
+
/** Normalize Ripio's off-ramp session status into our cross-PSAV enum. */
|
|
189
|
+
declare function normalizeRipioStatus(raw: string | undefined): OffRampStatus;
|
|
190
|
+
declare class RipioOffRampAdapter implements OffRampAdapter {
|
|
191
|
+
private readonly config;
|
|
192
|
+
private readonly baseUrl;
|
|
193
|
+
private readonly chain;
|
|
194
|
+
private readonly fromCurrency;
|
|
195
|
+
private readonly toCurrency;
|
|
196
|
+
private readonly paymentMethodType;
|
|
197
|
+
private readonly fetchImpl;
|
|
198
|
+
private readonly now;
|
|
199
|
+
private token;
|
|
200
|
+
constructor(config: RipioConfig);
|
|
201
|
+
private getToken;
|
|
202
|
+
private request;
|
|
203
|
+
quote(amountUsd: Usd): Promise<OffRampQuote>;
|
|
204
|
+
/**
|
|
205
|
+
* Create an off-ramp session for `amountUsd`. Returns a receipt whose
|
|
206
|
+
* `depositAddress` is where the society must send the USDC to complete the
|
|
207
|
+
* payout; `txId` is the session id for getStatus. `arsReceived` is the EXPECTED
|
|
208
|
+
* amount (from a fresh quote); the settled figure comes from getStatus.
|
|
209
|
+
*/
|
|
210
|
+
convert(amountUsd: Usd, opts: {
|
|
211
|
+
externalId: string;
|
|
212
|
+
}): Promise<OffRampReceipt>;
|
|
213
|
+
private pickDepositAddress;
|
|
214
|
+
getStatus(txId: string): Promise<OffRampStatusReport>;
|
|
215
|
+
/**
|
|
216
|
+
* One-time onboarding helper: register the society's CBU/CVU/alias as a fiat
|
|
217
|
+
* account payout destination. Returns the id to use as `fiatAccountId`.
|
|
218
|
+
*/
|
|
219
|
+
registerFiatAccount(input: {
|
|
220
|
+
cbuOrCvuOrAlias: string;
|
|
221
|
+
}): Promise<{
|
|
222
|
+
fiatAccountId: string;
|
|
223
|
+
raw: unknown;
|
|
224
|
+
}>;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* MuralOffRampAdapter — the self-onboard off-ramp behind the treasury rail.
|
|
229
|
+
*
|
|
230
|
+
* Mural (muralpay.com) is a stablecoin->fiat payouts API with deep LatAm rails.
|
|
231
|
+
* Unlike a sell-from-balance exchange (Manteca) or a deposit-session PSAV (Ripio),
|
|
232
|
+
* Mural is a PAYOUT model: you fund a Mural Account with USDC (on Base), then
|
|
233
|
+
* create + execute a Payout Request that converts USDC->ARS and pays a bank
|
|
234
|
+
* account (CBU/CVU/alias). We integrate it as the off-ramp; we never custody the
|
|
235
|
+
* conversion ourselves. Chosen as the proving path because onboarding is
|
|
236
|
+
* self-driven KYB (no sales gate) and it covers the full USDC->ARS-to-bank loop.
|
|
237
|
+
*
|
|
238
|
+
* GROUNDING — verified against Mural's OpenAPI spec + docs (jun-2026):
|
|
239
|
+
* - Base URLs: prod https://api.muralpay.com ; sandbox https://api-staging.muralpay.com
|
|
240
|
+
* - Auth: `authorization: Bearer <apiKey>` on all calls; `transfer-api-key:
|
|
241
|
+
* <transferApiKey>` additionally on /execute (and /cancel). `on-behalf-of:
|
|
242
|
+
* <organizationId>` scopes org operations.
|
|
243
|
+
* - POST /api/payouts/fees/token-to-fiat { tokenFeeRequests:[{amount:{tokenAmount,
|
|
244
|
+
* tokenSymbol},fiatAndRailCode:"ars"}] } -> [{type:"success",exchangeRate,
|
|
245
|
+
* exchangeFeePercentage,estimatedFiatAmount:{fiatAmount,fiatCurrencyCode},
|
|
246
|
+
* feeTotal:{tokenAmount,tokenSymbol},...} | {type:"error",message,...}]
|
|
247
|
+
* - POST /api/payouts/payout { sourceAccountId, memo, payouts:[{amount:{tokenAmount,
|
|
248
|
+
* tokenSymbol:"USDC"}, payoutDetails:{type:"fiat",bankName,bankAccountOwner,
|
|
249
|
+
* fiatAndRailDetails:{type:"ars",symbol:"ARS",bankAccountNumber,documentNumber,
|
|
250
|
+
* bankAccountNumberType:"CVU"|"CBU"|"ALIAS"}}, recipientInfo:{type:"business"|
|
|
251
|
+
* "individual",...,physicalAddress}}] } -> { id, status:"AWAITING_EXECUTION" }
|
|
252
|
+
* - POST /api/payouts/payout/{id}/execute (transfer-api-key) -> status PENDING
|
|
253
|
+
* - GET /api/payouts/payout/{id} -> { status:AWAITING_EXECUTION|PENDING|EXECUTED|
|
|
254
|
+
* FAILED|CANCELED, payouts:[{details:{fiatPayoutStatus:{type},fiatAmount:{fiatAmount}}}] }
|
|
255
|
+
* - USDC fund chain enum includes BASE; ARS rail code = "ars".
|
|
256
|
+
*
|
|
257
|
+
* CONFIRM at onboarding (config, one-line fixes):
|
|
258
|
+
* - baseUrl: defaults to prod; set the sandbox while testing.
|
|
259
|
+
* - The exact `recipientInfo.physicalAddress` shape your account requires
|
|
260
|
+
* (passed through verbatim) and whether `on-behalf-of` is needed on payouts.
|
|
261
|
+
* - Whether the fees `amount` field wants the {tokenAmount,tokenSymbol} object
|
|
262
|
+
* (sent here) or a bare number — parsed/sent defensively; verify in sandbox.
|
|
263
|
+
*
|
|
264
|
+
* NOT yet live-integration-tested: Mural API keys need a Mural Organization +
|
|
265
|
+
* self-driven KYB (no self-serve key minting, but no sales gate either). Going
|
|
266
|
+
* live = KYB -> keys -> set the config -> run scripts/live-offramp.mjs mural.
|
|
267
|
+
* convert() moves real money; gate behind requireConfirmation (RFC-001).
|
|
268
|
+
*/
|
|
269
|
+
|
|
270
|
+
declare const MURAL_PROD = "https://api.muralpay.com";
|
|
271
|
+
declare const MURAL_SANDBOX = "https://api-staging.muralpay.com";
|
|
272
|
+
interface MuralConfig {
|
|
273
|
+
/** General API key — sent as `authorization: Bearer`. */
|
|
274
|
+
apiKey: string;
|
|
275
|
+
/** Transfer API key — sent as `transfer-api-key`, required to execute payouts. */
|
|
276
|
+
transferApiKey: string;
|
|
277
|
+
/** Mural Account id holding the USDC balance (the payout source). */
|
|
278
|
+
sourceAccountId: string;
|
|
279
|
+
/** Organization id; sent as `on-behalf-of` when present. */
|
|
280
|
+
organizationId?: string;
|
|
281
|
+
/** Destination bank (the society's ARS account). */
|
|
282
|
+
bankName: string;
|
|
283
|
+
bankAccountOwner: string;
|
|
284
|
+
/** CBU / CVU / alias value -> `bankAccountNumber`. */
|
|
285
|
+
cvu: string;
|
|
286
|
+
/** How `cvu` is identified. Default "CVU". */
|
|
287
|
+
cvuType?: "CVU" | "CBU" | "ALIAS";
|
|
288
|
+
/** Recipient tax/ID number (e.g. the society's CUIT) -> `documentNumber`. */
|
|
289
|
+
documentNumber: string;
|
|
290
|
+
/** Recipient identity for the payout (the society). */
|
|
291
|
+
recipient: {
|
|
292
|
+
type?: "business" | "individual";
|
|
293
|
+
/** Business name (type=business). */
|
|
294
|
+
name?: string;
|
|
295
|
+
/** Person name (type=individual). */
|
|
296
|
+
firstName?: string;
|
|
297
|
+
lastName?: string;
|
|
298
|
+
email?: string;
|
|
299
|
+
/** Passed through verbatim as recipientInfo.physicalAddress. */
|
|
300
|
+
physicalAddress: unknown;
|
|
301
|
+
};
|
|
302
|
+
/** API base URL. Default = prod. Pass MURAL_SANDBOX while testing. */
|
|
303
|
+
baseUrl?: string;
|
|
304
|
+
/** Crypto sold. Default "USDC". */
|
|
305
|
+
tokenSymbol?: string;
|
|
306
|
+
/** Fiat rail code. Default "ars". */
|
|
307
|
+
fiatRailCode?: string;
|
|
308
|
+
/** Injectable fetch (tests / non-global-fetch runtimes). */
|
|
309
|
+
fetchImpl?: typeof fetch;
|
|
310
|
+
/** Injectable clock for the externalId fallback. Default Date.now. */
|
|
311
|
+
now?: () => number;
|
|
312
|
+
}
|
|
313
|
+
/** A non-2xx (or transport) failure from the Mural API. */
|
|
314
|
+
declare class MuralApiError extends Error {
|
|
315
|
+
readonly status: number;
|
|
316
|
+
readonly body?: unknown | undefined;
|
|
317
|
+
constructor(message: string, status: number, body?: unknown | undefined);
|
|
318
|
+
}
|
|
319
|
+
/** 401/403 — bad or unauthorized api key (or ToS not accepted). */
|
|
320
|
+
declare class MuralAuthError extends MuralApiError {
|
|
321
|
+
constructor(message: string, status: number, body?: unknown);
|
|
322
|
+
}
|
|
323
|
+
/** 429 — rate limited. */
|
|
324
|
+
declare class MuralRateLimitError extends MuralApiError {
|
|
325
|
+
constructor(message: string, status: number, body?: unknown);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Normalize Mural's status into our cross-PSAV enum. Prefers the per-payout
|
|
329
|
+
* `fiatPayoutStatus.type` (created/pending/on-hold/completed/failed/canceled/
|
|
330
|
+
* refund*) when present; else falls back to the request-level status. Note: a
|
|
331
|
+
* request status of EXECUTED only means the on-chain leg ran — the fiat may still
|
|
332
|
+
* be settling, so EXECUTED maps to PROCESSING unless the fiat leg is completed.
|
|
333
|
+
*/
|
|
334
|
+
declare function normalizeMuralStatus(requestStatus: string | undefined, fiatPayoutType?: string): OffRampStatus;
|
|
335
|
+
declare class MuralOffRampAdapter implements OffRampAdapter {
|
|
336
|
+
private readonly config;
|
|
337
|
+
private readonly baseUrl;
|
|
338
|
+
private readonly tokenSymbol;
|
|
339
|
+
private readonly fiatRailCode;
|
|
340
|
+
private readonly fetchImpl;
|
|
341
|
+
private readonly now;
|
|
342
|
+
constructor(config: MuralConfig);
|
|
343
|
+
private request;
|
|
344
|
+
/** Quote a USDC->ARS off-ramp via the token-to-fiat fees endpoint. */
|
|
345
|
+
quote(amountUsd: Usd): Promise<OffRampQuote>;
|
|
346
|
+
/**
|
|
347
|
+
* Create + execute the off-ramp payout: convert `amountUsd` USDC and pay ARS to
|
|
348
|
+
* the configured CBU/CVU. IRREVERSIBLE — gate behind requireConfirmation (RFC-001)
|
|
349
|
+
* and write to the signed audit log. Returns the payout-request id as txId;
|
|
350
|
+
* `arsReceived` is the EXPECTED amount (from a fresh quote), the settled figure
|
|
351
|
+
* comes from getStatus.
|
|
352
|
+
*/
|
|
353
|
+
convert(amountUsd: Usd, opts: {
|
|
354
|
+
externalId: string;
|
|
355
|
+
}): Promise<OffRampReceipt>;
|
|
356
|
+
/** Poll a payout request and normalize its settlement state. */
|
|
357
|
+
getStatus(txId: string): Promise<OffRampStatusReport>;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* The AFIP/ARCA fiscal layer of the treasury rail.
|
|
362
|
+
*
|
|
363
|
+
* (ARCA = Agencia de Recaudación y Control Aduanero, the continuator of AFIP per
|
|
364
|
+
* Decreto 953/2024, eff. 5-nov-2024. afip.gob.ar URLs still resolve.)
|
|
365
|
+
*
|
|
366
|
+
* THE HONEST FINDING (verified jun-2026): there is NO fully-autonomous, official
|
|
367
|
+
* channel for a private entity to pay its taxes at pay-time. So this module does
|
|
368
|
+
* NOT pretend to "pay AFIP." It does the parts that are real:
|
|
369
|
+
* 1. Compute the obligation (monotributo cuota here; cedular lives in index.ts).
|
|
370
|
+
* 2. Describe the settlement honestly — what is automatable vs. human-required.
|
|
371
|
+
* The treasury brain (index.ts) sizes + funds the ARS buffer so that whatever
|
|
372
|
+
* settlement rail the society configured can succeed on time.
|
|
373
|
+
*
|
|
374
|
+
* Why no autonomy (all verified against ARCA + MercadoPago official docs):
|
|
375
|
+
* - WSCREATEVEP (create a VEP via web service) is enabled ONLY for public
|
|
376
|
+
* organisms, not private taxpayers. Do not build on it.
|
|
377
|
+
* - Monotributo débito automático CANNOT be enrolled via API — it is a one-time
|
|
378
|
+
* portal/bank step. Once enrolled it runs PASSIVELY (the monthly cuota debits
|
|
379
|
+
* itself). The agent cannot trigger it; it can only ensure the CVU is funded.
|
|
380
|
+
* - There is NO MercadoPago API to pay a VEP — it is a manual in-app flow
|
|
381
|
+
* (scan QR, or enter CUIT + VEP number).
|
|
382
|
+
* Full sourcing: ../../TREASURY-FISCAL-RAIL.md §3.
|
|
383
|
+
*/
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Guard constant: documents (so nobody re-adds it) why WSCREATEVEP is unusable.
|
|
387
|
+
*/
|
|
388
|
+
declare const WSCREATEVEP_IS_GOV_ONLY: string;
|
|
389
|
+
declare const MONOTRIBUTO_TABLE_EFFECTIVE = "2026-02-01";
|
|
390
|
+
type MonotributoCategory = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K";
|
|
391
|
+
type MonotributoActivity = "servicios" | "bienes";
|
|
392
|
+
interface MonotributoRow {
|
|
393
|
+
category: MonotributoCategory;
|
|
394
|
+
/** Annual gross-income ceiling for the category, ARS. */
|
|
395
|
+
annualCapArs: Ars;
|
|
396
|
+
/** Monthly total cuota for a services taxpayer, ARS. */
|
|
397
|
+
cuotaServicios: Ars;
|
|
398
|
+
/** Monthly total cuota for a goods taxpayer, ARS. */
|
|
399
|
+
cuotaBienes: Ars;
|
|
400
|
+
/** I/J/K are only available when selling goods. */
|
|
401
|
+
bienesOnly: boolean;
|
|
402
|
+
}
|
|
403
|
+
declare const MONOTRIBUTO_2026: readonly MonotributoRow[];
|
|
404
|
+
/** Monthly monotributo cuota for a category + activity, ARS. */
|
|
405
|
+
declare function monotributoCuota(category: MonotributoCategory, activity: MonotributoActivity): Ars;
|
|
406
|
+
/**
|
|
407
|
+
* The category an annual gross income falls into, for the given activity. A
|
|
408
|
+
* services taxpayer caps at H; returns null if income exceeds the regime ceiling
|
|
409
|
+
* (the taxpayer must move to the general regime).
|
|
410
|
+
*/
|
|
411
|
+
declare function categoryForAnnualIncome(annualArs: Ars, activity: MonotributoActivity): MonotributoCategory | null;
|
|
412
|
+
type SettlementMethod = "debito_automatico" | "vep_manual" | "mp_manual";
|
|
413
|
+
/**
|
|
414
|
+
* - `passive`: a one-time human enrolment makes future payments run themselves;
|
|
415
|
+
* the agent only has to keep the CVU funded (débito automático).
|
|
416
|
+
* - `human-required`: a human must act for THIS payment (generate + pay a VEP).
|
|
417
|
+
*/
|
|
418
|
+
type SettlementAutonomy = "passive" | "human-required";
|
|
419
|
+
interface SettlementPlan {
|
|
420
|
+
method: SettlementMethod;
|
|
421
|
+
autonomy: SettlementAutonomy;
|
|
422
|
+
/**
|
|
423
|
+
* Whether the agent can settle with zero human action at pay-time. Typed as the
|
|
424
|
+
* literal `false`: in jun-2026 NO official channel allows fully-autonomous tax
|
|
425
|
+
* payment by a private entity. The rail funds + instructs; it does not pay.
|
|
426
|
+
*/
|
|
427
|
+
canAutoExecute: false;
|
|
428
|
+
amountArs: Ars;
|
|
429
|
+
/** Epoch ms the obligation is due. */
|
|
430
|
+
dueAtMs: number;
|
|
431
|
+
/** What must happen for this obligation to actually be paid. */
|
|
432
|
+
instruction: string;
|
|
433
|
+
/** One-time human setup this method needs (empty string if none). */
|
|
434
|
+
oneTimeSetup: string;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Describe how a given obligation gets settled under the chosen method. Pure +
|
|
438
|
+
* honest: the agent's autonomous part is funding the CVU (see index.ts
|
|
439
|
+
* fundTaxBuffer); the settlement itself is passive (débito) or human (VEP/MP).
|
|
440
|
+
*/
|
|
441
|
+
declare function settlementPlan(obligation: Obligation, method: SettlementMethod): SettlementPlan;
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* @ar-agents/treasury — the fiscal/treasury rail for a Sociedad Automatizada.
|
|
445
|
+
*
|
|
446
|
+
* The moat half of the crypto<->fiat bridge: an autonomous society earns in
|
|
447
|
+
* crypto (USDC on Base) but must pay AFIP in pesos. This module is the pure,
|
|
448
|
+
* deterministic BRAIN of that loop: track balances, size the peso tax buffer,
|
|
449
|
+
* plan a just-in-time USDC->ARS conversion, and account for the Ganancias
|
|
450
|
+
* cedular tax on each disposal. The actual off-ramp (USDC->ARS payout to a CVU)
|
|
451
|
+
* is done by an OffRampAdapter wrapping a registered PSAV (Manteca / Ripio B2B);
|
|
452
|
+
* we integrate one, we do not become one (CNV RG 1058/2025).
|
|
453
|
+
*
|
|
454
|
+
* Pure functions (clock + fx injected, never read) so they are unit-testable and
|
|
455
|
+
* deterministic. Irreversible moves (convert, pay) must be gated by the agent's
|
|
456
|
+
* requireConfirmation (RFC-001) and written to the signed audit log by the caller.
|
|
457
|
+
*/
|
|
458
|
+
/** Stablecoin balance, USDC (the society earns here). */
|
|
459
|
+
type Usd = number;
|
|
460
|
+
/** Peso balance, ARS (held in a CVU; what AFIP is paid from). */
|
|
461
|
+
type Ars = number;
|
|
462
|
+
interface TreasuryState {
|
|
463
|
+
/** USDC balance (on Base). */
|
|
464
|
+
usd: Usd;
|
|
465
|
+
/** ARS balance (in the society's CVU). */
|
|
466
|
+
ars: Ars;
|
|
467
|
+
/**
|
|
468
|
+
* Average USD cost basis per USDC unit currently held. For USDC ~= 1, but the
|
|
469
|
+
* society may have acquired crypto that appreciated; cedular is on the gain.
|
|
470
|
+
*/
|
|
471
|
+
costBasisPerUsd: number;
|
|
472
|
+
}
|
|
473
|
+
declare const ZERO_STATE: TreasuryState;
|
|
474
|
+
type Denomination = "ARS" | "FOREIGN";
|
|
475
|
+
declare const CEDULAR_RATE: Record<Denomination, number>;
|
|
476
|
+
/**
|
|
477
|
+
* Cedular tax owed (in ARS) on disposing `amountUsd` of crypto with the given
|
|
478
|
+
* per-unit cost basis, at `fxRate` (ARS per USD). Taxed on the gain only; 0 if no gain.
|
|
479
|
+
*/
|
|
480
|
+
declare function cedularTax(amountUsd: Usd, costBasisPerUsd: number, fxRate: number, denom?: Denomination): Ars;
|
|
481
|
+
type ObligationKind = "monotributo" | "vep" | "iibb" | "cedular";
|
|
482
|
+
interface Obligation {
|
|
483
|
+
id: string;
|
|
484
|
+
kind: ObligationKind;
|
|
485
|
+
amountArs: Ars;
|
|
486
|
+
/** Epoch ms when it is due. */
|
|
487
|
+
dueAtMs: number;
|
|
488
|
+
}
|
|
489
|
+
/** The next obligation due at/after `nowMs`, or null. */
|
|
490
|
+
declare function nextObligation(obligations: Obligation[], nowMs: number): Obligation | null;
|
|
491
|
+
/**
|
|
492
|
+
* ARS needed to cover every obligation due within `horizonMs` from now, times a
|
|
493
|
+
* safety multiple (default 1.1) so a small fx move does not leave AFIP short.
|
|
494
|
+
*/
|
|
495
|
+
declare function requiredArsBuffer(obligations: Obligation[], nowMs: number, horizonMs: number, safety?: number): Ars;
|
|
496
|
+
interface ConversionPlan {
|
|
497
|
+
/** How much USDC to convert now (0 = do nothing). */
|
|
498
|
+
convertUsd: Usd;
|
|
499
|
+
/** ARS expected from that conversion, net of spread. */
|
|
500
|
+
expectedArs: Ars;
|
|
501
|
+
reason: string;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Plan a just-in-time conversion: if the ARS balance is below `requiredArs`,
|
|
505
|
+
* convert only enough USDC (at `fxRate` net of `spread`) to top the buffer back
|
|
506
|
+
* up, capped by available USDC. Never over-converts (minimizes taxable disposals
|
|
507
|
+
* + fx exposure). Pure.
|
|
508
|
+
*/
|
|
509
|
+
declare function planConversion(state: TreasuryState, requiredArs: Ars, fxRate: number, spread?: number): ConversionPlan;
|
|
510
|
+
interface OffRampReceipt {
|
|
511
|
+
amountUsd: Usd;
|
|
512
|
+
arsReceived: Ars;
|
|
513
|
+
/** ARS per USD actually realized (net of spread). */
|
|
514
|
+
rate: number;
|
|
515
|
+
txId: string;
|
|
516
|
+
/**
|
|
517
|
+
* Set by session-model PSAVs (Ripio): the on-chain address the society must
|
|
518
|
+
* send `amountUsd` USDC to in order to complete the off-ramp. Manteca (which
|
|
519
|
+
* sells from the platform balance) leaves it undefined.
|
|
520
|
+
*/
|
|
521
|
+
depositAddress?: string;
|
|
522
|
+
}
|
|
523
|
+
/** Apply a completed off-ramp conversion to the state (USDC down, ARS up). */
|
|
524
|
+
declare function applyConversion(state: TreasuryState, receipt: OffRampReceipt): TreasuryState;
|
|
525
|
+
/** Apply a tax/obligation payment (ARS down). Throws if it would overdraw the CVU. */
|
|
526
|
+
declare function applyPayment(state: TreasuryState, amountArs: Ars): TreasuryState;
|
|
527
|
+
interface OffRampQuote {
|
|
528
|
+
amountUsd: Usd;
|
|
529
|
+
arsOut: Ars;
|
|
530
|
+
/** Gross ARS per USD before spread. */
|
|
531
|
+
rate: number;
|
|
532
|
+
spread: number;
|
|
533
|
+
}
|
|
534
|
+
/** Settlement state of an off-ramp, normalized across PSAVs. */
|
|
535
|
+
type OffRampStatus = "PENDING" | "PROCESSING" | "COMPLETED" | "FAILED" | "UNKNOWN";
|
|
536
|
+
interface OffRampStatusReport {
|
|
537
|
+
txId: string;
|
|
538
|
+
status: OffRampStatus;
|
|
539
|
+
/** ARS actually settled to the CVU once COMPLETED, if the PSAV reports it. */
|
|
540
|
+
arsSettled?: Ars;
|
|
541
|
+
/** Provider-native status string, kept for the signed audit log / forensics. */
|
|
542
|
+
raw?: string;
|
|
543
|
+
}
|
|
544
|
+
interface OffRampAdapter {
|
|
545
|
+
/** Quote a USDC->ARS conversion (net of spread). No side effects. */
|
|
546
|
+
quote(amountUsd: Usd): Promise<OffRampQuote>;
|
|
547
|
+
/**
|
|
548
|
+
* Execute the conversion + payout of ARS to the society's CVU. IRREVERSIBLE:
|
|
549
|
+
* the caller MUST gate this behind requireConfirmation (RFC-001) and log it.
|
|
550
|
+
* `opts.externalId` is a REQUIRED idempotency key — a STABLE id derived from the
|
|
551
|
+
* payment (e.g. obligation id + period + amount). Reuse the SAME key on retry so
|
|
552
|
+
* the PSAV deduplicates it and a retried convert never double-spends.
|
|
553
|
+
*/
|
|
554
|
+
convert(amountUsd: Usd, opts: {
|
|
555
|
+
externalId: string;
|
|
556
|
+
}): Promise<OffRampReceipt>;
|
|
557
|
+
/**
|
|
558
|
+
* Poll the settlement of a prior convert(). A real off-ramp is ASYNCHRONOUS:
|
|
559
|
+
* the PSAV sells the crypto, then settles ARS to the CVU over seconds-to-minutes
|
|
560
|
+
* (Manteca models this as a multi-stage "synthetic"). Optional because the
|
|
561
|
+
* in-memory adapter settles instantly.
|
|
562
|
+
*/
|
|
563
|
+
getStatus?(txId: string): Promise<OffRampStatusReport>;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Deterministic in-memory off-ramp for tests/dev (no network, no PSAV). A real
|
|
567
|
+
* adapter wraps Manteca's API Cripto / API Rampa (or Ripio B2B): fund a wallet
|
|
568
|
+
* with USDC -> POST a conversion -> ARS settles to the CVU -> webhook confirms.
|
|
569
|
+
*/
|
|
570
|
+
declare class InMemoryOffRampAdapter implements OffRampAdapter {
|
|
571
|
+
private readonly rate;
|
|
572
|
+
private readonly spread;
|
|
573
|
+
private readonly settled;
|
|
574
|
+
constructor(rate: number, spread?: number);
|
|
575
|
+
quote(amountUsd: Usd): Promise<OffRampQuote>;
|
|
576
|
+
convert(amountUsd: Usd, opts: {
|
|
577
|
+
externalId: string;
|
|
578
|
+
}): Promise<OffRampReceipt>;
|
|
579
|
+
/** The in-memory adapter settles instantly: any tx it issued is COMPLETED. */
|
|
580
|
+
getStatus(txId: string): Promise<OffRampStatusReport>;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* One-shot helper: given the current state, the obligations, and an off-ramp,
|
|
584
|
+
* compute + (optionally) execute the conversion needed to fund the buffer. Returns
|
|
585
|
+
* the plan; if `offramp` is provided it also performs the conversion and returns
|
|
586
|
+
* the receipt + the resulting state. The convert() call is irreversible: only pass
|
|
587
|
+
* `offramp` from a path already behind requireConfirmation.
|
|
588
|
+
*/
|
|
589
|
+
declare function fundTaxBuffer(args: {
|
|
590
|
+
state: TreasuryState;
|
|
591
|
+
obligations: Obligation[];
|
|
592
|
+
nowMs: number;
|
|
593
|
+
horizonMs: number;
|
|
594
|
+
fxRate: number;
|
|
595
|
+
spread?: number;
|
|
596
|
+
safety?: number;
|
|
597
|
+
offramp?: OffRampAdapter;
|
|
598
|
+
/**
|
|
599
|
+
* Idempotency key for the off-ramp convert. Defaults to a deterministic id from
|
|
600
|
+
* the obligations being funded + the amount, so a retried fundTaxBuffer with the
|
|
601
|
+
* same inputs is deduplicated by the PSAV and never double-spends. Pass an
|
|
602
|
+
* explicit stable id to override.
|
|
603
|
+
*/
|
|
604
|
+
externalId?: string;
|
|
605
|
+
}): Promise<{
|
|
606
|
+
plan: ConversionPlan;
|
|
607
|
+
receipt?: OffRampReceipt;
|
|
608
|
+
state: TreasuryState;
|
|
609
|
+
}>;
|
|
610
|
+
|
|
611
|
+
export { type Ars, CEDULAR_RATE, type ConversionPlan, type Denomination, InMemoryOffRampAdapter, MONOTRIBUTO_2026, MONOTRIBUTO_TABLE_EFFECTIVE, MURAL_PROD, MURAL_SANDBOX, MantecaApiError, MantecaAuthError, type MantecaConfig, MantecaOffRampAdapter, MantecaRateLimitError, type MonotributoActivity, type MonotributoCategory, type MonotributoRow, MuralApiError, MuralAuthError, type MuralConfig, MuralOffRampAdapter, MuralRateLimitError, type Obligation, type ObligationKind, type OffRampAdapter, type OffRampQuote, type OffRampReceipt, type OffRampStatus, type OffRampStatusReport, RIPIO_PROD, RIPIO_SANDBOX, RipioApiError, RipioAuthError, type RipioConfig, RipioOffRampAdapter, RipioRateLimitError, type SettlementAutonomy, type SettlementMethod, type SettlementPlan, type TreasuryState, type Usd, WSCREATEVEP_IS_GOV_ONLY, ZERO_STATE, applyConversion, applyPayment, categoryForAnnualIncome, cedularTax, fundTaxBuffer, monotributoCuota, nextObligation, normalizeMuralStatus, normalizeRipioStatus, planConversion, requiredArsBuffer, settlementPlan };
|