@venlyfinance/settlement-mcp 0.1.1 → 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.
@@ -3,18 +3,7 @@
3
3
  */
4
4
  import { z } from "zod";
5
5
  import { reconcileByReferenceCode } from "../reconcile.js";
6
- /** Serialize a result as a text-content tool response. */
7
- function jsonResult(data) {
8
- return {
9
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
10
- };
11
- }
12
- function errorResult(message) {
13
- return {
14
- content: [{ type: "text", text: `Error: ${message}` }],
15
- isError: true,
16
- };
17
- }
6
+ import { errorResult, jsonResult } from "../results.js";
18
7
  const READ_ONLY = {
19
8
  readOnlyHint: true,
20
9
  destructiveHint: false,
@@ -71,6 +60,23 @@ export function registerReadTools(server, client) {
71
60
  return errorResult(e.message);
72
61
  }
73
62
  });
63
+ server.registerTool("list_accounts", {
64
+ title: "List accounts",
65
+ description: "List Venly Finance accounts before creating duplicates (finance GET /accounts). Read-only.",
66
+ inputSchema: {
67
+ page: z.number().int().min(1).optional(),
68
+ size: z.number().int().min(1).max(200).optional(),
69
+ },
70
+ annotations: READ_ONLY,
71
+ }, async (params) => {
72
+ try {
73
+ const result = await client.listAccounts(params);
74
+ return jsonResult({ count: result.length, accounts: result });
75
+ }
76
+ catch (e) {
77
+ return errorResult(e.message);
78
+ }
79
+ });
74
80
  server.registerTool("get_account", {
75
81
  title: "Get account",
76
82
  description: "Fetch a settlement account (finance GET /accounts/{accountId}). Read-only.",
@@ -84,6 +90,24 @@ export function registerReadTools(server, client) {
84
90
  return errorResult(e.message);
85
91
  }
86
92
  });
93
+ server.registerTool("list_wallets", {
94
+ title: "List account wallets",
95
+ description: "List wallets auto-provisioned for a Finance account (finance GET /accounts/{accountId}/wallets). Read-only.",
96
+ inputSchema: {
97
+ accountId: z.string().describe("Account UUID"),
98
+ page: z.number().int().min(1).optional(),
99
+ size: z.number().int().min(1).max(200).optional(),
100
+ },
101
+ annotations: READ_ONLY,
102
+ }, async ({ accountId, page, size }) => {
103
+ try {
104
+ const result = await client.listWallets(accountId, { page, size });
105
+ return jsonResult({ count: result.length, wallets: result });
106
+ }
107
+ catch (e) {
108
+ return errorResult(e.message);
109
+ }
110
+ });
87
111
  server.registerTool("list_virtual_bank_accounts", {
88
112
  title: "List virtual bank accounts",
89
113
  description: "List the EUR vIBANs on an account, each with its reconciliation " +
@@ -99,6 +123,22 @@ export function registerReadTools(server, client) {
99
123
  return errorResult(e.message);
100
124
  }
101
125
  });
126
+ server.registerTool("get_virtual_bank_account", {
127
+ title: "Get virtual bank account",
128
+ description: "Fetch receiving-account details including status, IBAN/BIC and referenceCode. Read-only.",
129
+ inputSchema: {
130
+ accountId: z.string().describe("Account UUID"),
131
+ virtualBankAccountId: z.string().describe("Virtual bank account UUID"),
132
+ },
133
+ annotations: READ_ONLY,
134
+ }, async ({ accountId, virtualBankAccountId }) => {
135
+ try {
136
+ return jsonResult(await client.getVirtualBankAccount(accountId, virtualBankAccountId));
137
+ }
138
+ catch (e) {
139
+ return errorResult(e.message);
140
+ }
141
+ });
102
142
  server.registerTool("reconcile_by_reference_code", {
103
143
  title: "Reconcile by referenceCode",
104
144
  description: "Match observed incoming bank transactions on an account's EUR vIBANs to " +
@@ -132,6 +172,24 @@ export function registerReadTools(server, client) {
132
172
  return errorResult(e.message);
133
173
  }
134
174
  });
175
+ server.registerTool("list_transfers", {
176
+ title: "List transfers",
177
+ description: "List transfer history for an account (finance GET /accounts/{accountId}/transfers). Read-only.",
178
+ inputSchema: {
179
+ accountId: z.string().describe("Account UUID"),
180
+ page: z.number().int().min(1).optional(),
181
+ size: z.number().int().min(1).max(200).optional(),
182
+ },
183
+ annotations: READ_ONLY,
184
+ }, async ({ accountId, page, size }) => {
185
+ try {
186
+ const result = await client.listTransfers(accountId, { page, size });
187
+ return jsonResult({ count: result.length, transfers: result });
188
+ }
189
+ catch (e) {
190
+ return errorResult(e.message);
191
+ }
192
+ });
135
193
  server.registerTool("get_transfer", {
136
194
  title: "Get transfer",
137
195
  description: "Fetch a transfer by id (finance GET /accounts/{accountId}/transfers/{transferId}). Read-only.",
@@ -165,6 +223,19 @@ export function registerReadTools(server, client) {
165
223
  return errorResult(e.message);
166
224
  }
167
225
  });
226
+ server.registerTool("get_party", {
227
+ title: "Get party",
228
+ description: "Fetch an individual or organisation party, including KYC/KYB state when present. Read-only.",
229
+ inputSchema: { partyId: z.string().describe("Party UUID") },
230
+ annotations: READ_ONLY,
231
+ }, async ({ partyId }) => {
232
+ try {
233
+ return jsonResult(await client.getParty(partyId));
234
+ }
235
+ catch (e) {
236
+ return errorResult(e.message);
237
+ }
238
+ });
168
239
  server.registerTool("get_reference_data", {
169
240
  title: "Get reference data",
170
241
  description: "Fetch settlement reference data: supported chains, fiat currencies, " +
@@ -8,16 +8,14 @@
8
8
  */
9
9
  import { z } from "zod";
10
10
  import { buildDryRun, evaluateWriteGate } from "../safety.js";
11
- function jsonResult(data) {
12
- return {
13
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
14
- };
15
- }
16
- function errorResult(message) {
17
- return {
18
- content: [{ type: "text", text: `Error: ${message}` }],
19
- isError: true,
20
- };
11
+ import { errorResult, jsonResult } from "../results.js";
12
+ import { normalizeLegacyFiatTransfer } from "../client/sdk-client.js";
13
+ function executionResult(gate, result) {
14
+ return jsonResult({
15
+ mode: gate.environment === "mock" ? "mock" : "live",
16
+ environment: gate.environment,
17
+ result,
18
+ });
21
19
  }
22
20
  const WRITE_ANNOTATIONS = {
23
21
  readOnlyHint: false,
@@ -30,18 +28,215 @@ const confirmField = z
30
28
  .default(false)
31
29
  .describe("Must be true to attempt a live call. Even then, VENLY_MCP_LIVE=1 and " +
32
30
  "credentials are also required, otherwise the tool dry-runs.");
31
+ const addressSchema = z
32
+ .object({
33
+ addressLine1: z.string().optional(),
34
+ addressLine2: z.string().optional(),
35
+ city: z.string().optional(),
36
+ state: z.string().optional(),
37
+ postalCode: z.string().optional(),
38
+ country: z.string().regex(/^[A-Z]{2}$/).optional(),
39
+ })
40
+ .optional();
41
+ const partySchema = z.object({
42
+ partyType: z.enum(["INDIVIDUAL", "ORGANISATION"]),
43
+ externalId: z.string().optional(),
44
+ firstName: z.string().optional(),
45
+ lastName: z.string().optional(),
46
+ name: z.string().optional(),
47
+ vatNumber: z.string().optional(),
48
+ address: addressSchema,
49
+ });
50
+ function partyValidationError(party) {
51
+ if (party.partyType === "INDIVIDUAL" && (!party.firstName || !party.lastName)) {
52
+ return "INDIVIDUAL parties require firstName and lastName";
53
+ }
54
+ if (party.partyType === "ORGANISATION" && !party.name) {
55
+ return "ORGANISATION parties require name";
56
+ }
57
+ return undefined;
58
+ }
33
59
  export function registerWriteTools(server, client, env) {
60
+ server.registerTool("create_party", {
61
+ title: "Create a customer or organisation party",
62
+ description: "Create a Finance party. This creates the party record; it does not complete KYC/KYB. Dry-run by default outside explicit mock mode.",
63
+ inputSchema: {
64
+ partyType: z.enum(["INDIVIDUAL", "ORGANISATION"]),
65
+ externalId: z.string().optional(),
66
+ firstName: z.string().optional(),
67
+ lastName: z.string().optional(),
68
+ name: z.string().optional(),
69
+ vatNumber: z.string().optional(),
70
+ address: addressSchema,
71
+ confirm: confirmField,
72
+ },
73
+ annotations: WRITE_ANNOTATIONS,
74
+ }, async ({ confirm, ...party }) => {
75
+ const validationError = partyValidationError(party);
76
+ if (validationError)
77
+ return errorResult(validationError);
78
+ const gate = evaluateWriteGate(confirm, env);
79
+ if (!gate.armed) {
80
+ return jsonResult(buildDryRun("create_party", "POST", "finance", "/parties", party, gate));
81
+ }
82
+ try {
83
+ return executionResult(gate, await client.createParty(party));
84
+ }
85
+ catch (e) {
86
+ return errorResult(e.message);
87
+ }
88
+ });
89
+ server.registerTool("create_account", {
90
+ title: "Create an account and provision its wallet",
91
+ description: "Create a Finance account. Venly auto-provisions its wallet on the selected chain. Supply partyId or an inline party. Dry-run by default.",
92
+ inputSchema: {
93
+ externalId: z.string().min(1),
94
+ name: z.string().optional(),
95
+ chain: z.enum(["AVALANCHE", "BASE", "POLYGON"]),
96
+ address: z.string().optional().describe("Required for SELF_CUSTODY tenants"),
97
+ partyId: z.string().optional(),
98
+ party: partySchema.optional(),
99
+ confirm: confirmField,
100
+ },
101
+ annotations: WRITE_ANNOTATIONS,
102
+ }, async ({ confirm, ...body }) => {
103
+ if (!body.partyId && !body.party) {
104
+ return errorResult("create_account requires partyId or an inline party");
105
+ }
106
+ if (body.party) {
107
+ const validationError = partyValidationError(body.party);
108
+ if (validationError)
109
+ return errorResult(validationError);
110
+ }
111
+ const gate = evaluateWriteGate(confirm, env);
112
+ if (!gate.armed) {
113
+ return jsonResult(buildDryRun("create_account", "POST", "finance", "/accounts", body, gate));
114
+ }
115
+ try {
116
+ return executionResult(gate, await client.createAccount(body));
117
+ }
118
+ catch (e) {
119
+ return errorResult(e.message);
120
+ }
121
+ });
122
+ server.registerTool("create_virtual_bank_account", {
123
+ title: "Create a EUR receiving account",
124
+ description: "Provision a EUR SEPA virtual bank account and conversion target. Outside mock mode the Finance account must have KYC status VERIFIED. Dry-run by default.",
125
+ inputSchema: {
126
+ accountId: z.string(),
127
+ name: z.string().min(1),
128
+ inCurrency: z.literal("EUR"),
129
+ targetCryptocurrency: z.enum(["USDC", "EURC", "USDT", "USDS"]),
130
+ idempotencyKey: z.string().min(1).optional(),
131
+ confirm: confirmField,
132
+ },
133
+ annotations: WRITE_ANNOTATIONS,
134
+ }, async ({ accountId, confirm, ...input }) => {
135
+ const body = {
136
+ ...input,
137
+ idempotencyKey: input.idempotencyKey ?? crypto.randomUUID(),
138
+ };
139
+ const gate = evaluateWriteGate(confirm, env);
140
+ if (!gate.armed) {
141
+ const dryRun = buildDryRun("create_virtual_bank_account", "POST", "finance", `/accounts/${accountId}/virtual-bank-accounts`, body, gate);
142
+ dryRun.note += " The account must have KYC status VERIFIED before live provisioning.";
143
+ return jsonResult(dryRun);
144
+ }
145
+ try {
146
+ return executionResult(gate, await client.createVirtualBankAccount(accountId, body));
147
+ }
148
+ catch (e) {
149
+ return errorResult(e.message);
150
+ }
151
+ });
152
+ server.registerTool("create_fiat_transfer", {
153
+ title: "Create a fiat-denominated internal transfer",
154
+ description: "Create an account-to-account transfer using the current Finance OpenAPI fields. Dry-run by default outside explicit mock mode.",
155
+ inputSchema: {
156
+ senderAccountId: z.string(),
157
+ receiverAccountId: z.string().optional(),
158
+ receiverExternalId: z.string().optional(),
159
+ currency: z.enum(["EUR", "GBP", "USD"]),
160
+ amount: z.number(),
161
+ description: z.string().optional(),
162
+ merchantReference: z.string().optional(),
163
+ idempotencyKey: z.string().min(1).optional(),
164
+ confirm: confirmField,
165
+ },
166
+ annotations: WRITE_ANNOTATIONS,
167
+ }, async ({ senderAccountId, confirm, ...input }) => {
168
+ if (!input.receiverAccountId && !input.receiverExternalId) {
169
+ return errorResult("A receiverAccountId or receiverExternalId is required");
170
+ }
171
+ const body = {
172
+ ...input,
173
+ idempotencyKey: input.idempotencyKey ?? crypto.randomUUID(),
174
+ };
175
+ const gate = evaluateWriteGate(confirm, env);
176
+ if (!gate.armed) {
177
+ return jsonResult(buildDryRun("create_fiat_transfer", "POST", "finance", `/accounts/${senderAccountId}/transfers/fiat`, body, gate));
178
+ }
179
+ try {
180
+ return executionResult(gate, await client.createCurrentFiatTransfer(senderAccountId, body));
181
+ }
182
+ catch (e) {
183
+ return errorResult(e.message);
184
+ }
185
+ });
186
+ server.registerTool("create_crypto_transfer", {
187
+ title: "Create a crypto-denominated internal transfer",
188
+ description: "Create an account-to-account asset transfer using the current Finance OpenAPI fields. Dry-run by default outside explicit mock mode.",
189
+ inputSchema: {
190
+ senderAccountId: z.string(),
191
+ receiverAccountId: z.string().optional(),
192
+ receiverExternalId: z.string().optional(),
193
+ chain: z.enum(["AVALANCHE", "BASE", "POLYGON"]),
194
+ asset: z.string().min(1),
195
+ amount: z.number(),
196
+ description: z.string().optional(),
197
+ merchantReference: z.string().optional(),
198
+ idempotencyKey: z.string().min(1).optional(),
199
+ confirm: confirmField,
200
+ },
201
+ annotations: WRITE_ANNOTATIONS,
202
+ }, async ({ senderAccountId, confirm, ...input }) => {
203
+ if (!input.receiverAccountId && !input.receiverExternalId) {
204
+ return errorResult("A receiverAccountId or receiverExternalId is required");
205
+ }
206
+ const body = {
207
+ ...input,
208
+ idempotencyKey: input.idempotencyKey ?? crypto.randomUUID(),
209
+ };
210
+ const gate = evaluateWriteGate(confirm, env);
211
+ if (!gate.armed) {
212
+ return jsonResult(buildDryRun("create_crypto_transfer", "POST", "finance", `/accounts/${senderAccountId}/transfers/crypto`, body, gate));
213
+ }
214
+ try {
215
+ return executionResult(gate, await client.createCryptoTransfer(senderAccountId, body));
216
+ }
217
+ catch (e) {
218
+ return errorResult(e.message);
219
+ }
220
+ });
34
221
  server.registerTool("stage_transfer", {
35
222
  title: "Stage a fiat transfer (dry-run by default)",
36
223
  description: "Stage a fiat-to-crypto transfer (finance POST /accounts/{senderAccountId}/transfers/fiat). " +
37
- "DISARMED by default: returns the exact request it would send unless " +
38
- "confirm:true AND VENLY_MCP_LIVE=1 AND credentials are present.",
224
+ "Legacy fiatAmount/fiatCurrency inputs are normalized to the current OpenAPI fields; " +
225
+ "the dry-run shows the exact normalized request. DISARMED by default: returns that " +
226
+ "request without sending unless confirm:true AND VENLY_MCP_LIVE=1 AND credentials are present.",
39
227
  inputSchema: {
40
228
  senderAccountId: z.string().describe("Account initiating the transfer"),
41
229
  receiverAccountId: z.string(),
42
- fiatAmount: z.string().describe("Decimal string, e.g. \"1000.00\""),
230
+ fiatAmount: z
231
+ .string()
232
+ .refine((value) => value.trim() !== "" && Number.isFinite(Number(value)), "fiatAmount must be a numeric decimal string")
233
+ .describe("Decimal string, e.g. \"1000.00\""),
43
234
  fiatCurrency: z.string().describe("e.g. EUR"),
44
- cryptocurrency: z.string().optional(),
235
+ cryptocurrency: z
236
+ .string()
237
+ .optional()
238
+ .describe("Retired: rejected with guidance. The current contract resolves the fiat " +
239
+ "amount to the account's settlement asset; use create_crypto_transfer instead."),
45
240
  description: z.string().optional(),
46
241
  merchantReference: z.string().optional(),
47
242
  confirm: confirmField,
@@ -49,20 +244,31 @@ export function registerWriteTools(server, client, env) {
49
244
  annotations: WRITE_ANNOTATIONS,
50
245
  }, async ({ senderAccountId, confirm, ...rest }) => {
51
246
  const gate = evaluateWriteGate(confirm, env);
52
- const body = {
247
+ const legacyInput = {
53
248
  receiverAccountId: rest.receiverAccountId,
54
249
  fiatAmount: rest.fiatAmount,
55
250
  fiatCurrency: rest.fiatCurrency,
56
251
  cryptocurrency: rest.cryptocurrency,
57
252
  description: rest.description,
58
253
  merchantReference: rest.merchantReference,
254
+ idempotencyKey: crypto.randomUUID(),
59
255
  };
256
+ // Normalize BEFORE the gate branch so the dry-run preview is byte-for-byte
257
+ // the request a live call would send (and the retired cryptocurrency field
258
+ // is rejected instead of silently dropped).
259
+ let body;
260
+ try {
261
+ body = normalizeLegacyFiatTransfer(legacyInput);
262
+ }
263
+ catch (e) {
264
+ return errorResult(e.message);
265
+ }
60
266
  if (!gate.armed) {
61
267
  return jsonResult(buildDryRun("stage_transfer", "POST", "finance", `/accounts/${senderAccountId}/transfers/fiat`, body, gate));
62
268
  }
63
269
  try {
64
- const result = await client.createFiatTransfer(senderAccountId, body);
65
- return jsonResult({ mode: "live", result });
270
+ const result = await client.createFiatTransfer(senderAccountId, legacyInput);
271
+ return executionResult(gate, result);
66
272
  }
67
273
  catch (e) {
68
274
  return errorResult(e.message);
@@ -91,7 +297,7 @@ export function registerWriteTools(server, client, env) {
91
297
  }
92
298
  try {
93
299
  const result = await client.approveRampRequest(id, body);
94
- return jsonResult({ mode: "live", result });
300
+ return executionResult(gate, result);
95
301
  }
96
302
  catch (e) {
97
303
  return errorResult(e.message);
@@ -118,7 +324,7 @@ export function registerWriteTools(server, client, env) {
118
324
  }
119
325
  try {
120
326
  const result = await client.rejectRampRequest(id, body);
121
- return jsonResult({ mode: "live", result });
327
+ return executionResult(gate, result);
122
328
  }
123
329
  catch (e) {
124
330
  return errorResult(e.message);
@@ -162,7 +368,7 @@ export function registerWriteTools(server, client, env) {
162
368
  }
163
369
  try {
164
370
  const result = await client.createPayInSession(accountId, body);
165
- return jsonResult({ mode: "live", result });
371
+ return executionResult(gate, result);
166
372
  }
167
373
  catch (e) {
168
374
  return errorResult(e.message);
package/dist/types.d.ts CHANGED
@@ -1,108 +1,37 @@
1
1
  /**
2
- * Domain types + the injectable VenlyClient interface.
2
+ * Generated API contracts + the injectable VenlyClient interface.
3
3
  *
4
- * These shapes are a minimal projection of the published OpenAPI specs
5
- * vendored in this repository under `specs/` finance.yaml (servers:
6
- * https://api.venlyfinance.com/v1) and fundflow.yaml (servers:
7
- * https://api-fundflow.venly.io). Only the fields the tools actually read or
8
- * echo are modeled. Fields are intentionally loose (optional) because this is a
9
- * thin wrapper, not a full SDK.
10
- *
11
- * TRANSPORT NOTE: the bundled HttpVenlyClient is a deliberately minimal fetch
12
- * transport (see client/http-client.ts). A future release replaces it with a
13
- * thin adapter over `@venlyfinance/sdk` with no change to this interface;
14
- * until then the minimal transport is what ships. When that lands, replace
15
- * HttpVenlyClient with a thin adapter over it and delete the vendored transport.
4
+ * Finance and Fundflow resources and requests are aliases to the types exported
5
+ * by `@venlyfinance/sdk`. Only MCP-owned inputs and compatibility shapes are
6
+ * declared locally. This prevents the MCP from silently drifting away from the
7
+ * vendored OpenAPI specifications.
16
8
  */
17
- /** Ramp request status flow, per fundflow.yaml overview. */
18
- export type RampStatus = "AWAITING_APPROVAL" | "AWAITING_FUNDS" | "PROCESSING" | "SUCCEEDED" | "FAILED" | "BLOCKED" | "DENIED" | "REJECTED" | "CANCELLED";
19
- export type RampType = "ON_RAMP" | "OFF_RAMP";
20
- /** Simplified ramp request for list views (fundflow RampRequestListItem). */
21
- export interface RampRequestListItem {
22
- id: string;
23
- paymentReference?: string;
24
- rampType?: RampType;
25
- status?: RampStatus;
26
- fiatAmount?: number;
27
- fiatCurrency?: string;
28
- cryptoAmount?: number;
29
- cryptoCurrency?: string;
30
- createdAt?: string;
31
- createdBy?: string;
32
- }
33
- /** Full ramp request detail (fundflow RampRequestDto). `version` drives the
34
- * four-eyes optimistic-locking approve/reject calls. */
35
- export interface RampRequestDto {
36
- id: string;
37
- companyId?: string;
38
- companyName?: string;
39
- rampType?: RampType;
40
- status?: RampStatus;
41
- fiatAmount?: number;
42
- fiatNetAmount?: number;
43
- cryptoAmount?: number;
44
- fiatFeeAmount?: number;
45
- exchangeRate?: number;
46
- feePercentage?: number;
47
- paymentReference?: string;
48
- paymentReceived?: boolean;
49
- blockchainTransactionHash?: string;
50
- createdAt?: string;
51
- createdBy?: string;
52
- version?: number;
53
- }
54
- /** Finance Account (finance getAccount). */
55
- export interface Account {
56
- id: string;
57
- status?: string;
58
- reference?: string;
59
- createdAt?: string;
60
- updatedAt?: string;
61
- [key: string]: unknown;
62
- }
63
- /** Finance VirtualBankAccount. `referenceCode` is the reconciliation key. */
64
- export interface VirtualBankAccount {
65
- id: string;
66
- accountId?: string;
67
- bankAccountType?: string;
68
- name?: string;
69
- status?: string;
70
- currency?: string;
71
- targetCryptocurrency?: string;
72
- iban?: string;
73
- bic?: string;
74
- bankName?: string;
75
- beneficiaryName?: string;
76
- referenceCode?: string;
77
- createdAt?: string;
78
- updatedAt?: string;
79
- }
80
- /** Finance Transfer (finance getTransfer). */
81
- export interface Transfer {
82
- id: string;
83
- status?: string;
84
- fiatAmount?: string | number;
85
- fiatCurrency?: string;
86
- cryptocurrency?: string;
87
- createdAt?: string;
88
- [key: string]: unknown;
89
- }
90
- /** Finance Party (finance listParties). */
91
- export interface Party {
92
- id: string;
93
- type?: string;
94
- status?: string;
95
- [key: string]: unknown;
96
- }
97
- /** A fiat-to-crypto payment session (finance PaymentSession). */
98
- export interface PaymentSession {
99
- id: string;
100
- accountId?: string;
101
- paymentUrl?: string;
102
- externalRef?: string;
103
- status?: string;
104
- [key: string]: unknown;
105
- }
9
+ import type { FinanceComponents, FundflowComponents } from "@venlyfinance/sdk";
10
+ import type { VenlyEnvironment } from "./constants.js";
11
+ type FinanceSchemas = FinanceComponents["schemas"];
12
+ type FundflowSchemas = FundflowComponents["schemas"];
13
+ export type AddressInput = FinanceSchemas["Address"];
14
+ export type Party = FinanceSchemas["Party"];
15
+ export type CreatePartyInput = FinanceSchemas["CreatePartyRequest"];
16
+ export type Account = FinanceSchemas["Account"];
17
+ export type CreateAccountInput = FinanceSchemas["CreateAccountRequest"];
18
+ export type Wallet = FinanceSchemas["Wallet"];
19
+ export type VirtualBankAccount = FinanceSchemas["VirtualBankAccount"];
20
+ export type CreateVirtualBankAccountInput = FinanceSchemas["CreateVirtualBankAccountRequest"];
21
+ export type PaymentSession = FinanceSchemas["PaymentSession"];
22
+ export type CreatePayInSessionRequest = FinanceSchemas["CreatePayInSessionRequest"];
23
+ export type Transfer = FinanceSchemas["Transfer"];
24
+ export type CurrentCreateFiatTransferInput = FinanceSchemas["CreateFiatTransferInput"];
25
+ export type CreateCryptoTransferInput = FinanceSchemas["CreateCryptoTransferInput"];
26
+ export type RampRequestDto = FundflowSchemas["RampRequestDto"];
27
+ export type RampRequestListItem = FundflowSchemas["RampRequestListItem"];
28
+ export type OptimisticLockingBody = FundflowSchemas["UpdateWithOptimisticLockingRequest"];
29
+ export type SupportedChains = FundflowSchemas["SupportedChainsDto"];
30
+ export type FiatCurrency = FundflowSchemas["FiatCurrencyDto"];
31
+ export type CryptoCurrency = FundflowSchemas["CryptoCurrencyDto"];
32
+ export type VenlyFee = FundflowSchemas["FeeDto"];
33
+ export type RampStatus = NonNullable<RampRequestDto["status"]>;
34
+ export type RampType = NonNullable<RampRequestDto["rampType"]>;
106
35
  /**
107
36
  * An observed incoming bank transaction on a vIBAN. This is operator- or
108
37
  * bank-feed-supplied data (there is no list-vIBAN-transactions endpoint in the
@@ -127,54 +56,67 @@ export interface ListRampRequestsParams {
127
56
  page?: number;
128
57
  size?: number;
129
58
  }
130
- /** Body for the fiat transfer POST (finance CreateFiatTransferInput). */
59
+ /** Legacy stage_transfer body, normalized to the current finance
60
+ * CreateFiatTransferInput before any call (see normalizeLegacyFiatTransfer). */
131
61
  export interface CreateFiatTransferInput {
132
62
  receiverAccountId: string;
133
63
  receiverExternalId?: string;
134
64
  fiatAmount: string;
135
65
  fiatCurrency: string;
66
+ /** Retired: the current contract has no such field. Normalization rejects it
67
+ * instead of silently dropping it. */
136
68
  cryptocurrency?: string;
137
69
  description?: string;
138
70
  merchantReference?: string;
139
- }
140
- /** Body for approve/reject (fundflow UpdateWithOptimisticLockingRequest). */
141
- export interface OptimisticLockingBody {
142
- version: number;
143
- }
144
- /** Body for the payment session POST (finance CreatePayInSessionRequest). */
145
- export interface CreatePayInSessionRequest {
146
- inAmount: string;
147
- inCurrency: string;
148
- outCryptocurrency: string;
149
- callbackUrl: string;
150
- idempotencyKey: string;
151
- successRedirectUrl?: string;
152
- failureRedirectUrl?: string;
153
- externalRef?: string;
154
- metadata?: Record<string, string>;
71
+ /** Preserved across the dry-run preview and the live call when supplied. */
72
+ idempotencyKey?: string;
155
73
  }
156
74
  /**
157
- * The injectable Venly transport. HttpVenlyClient is the real fetch-based
158
- * implementation; tests inject a mock. The MCP layer depends ONLY on this
159
- * interface, never on a concrete transport, which is what makes the fail-closed
160
- * write path testable without a network.
75
+ * The injectable Venly client contract. SdkVenlyClient is the production
76
+ * implementation; tests inject a lightweight mock. The MCP layer depends only
77
+ * on this interface, keeping the fail-closed write path testable without a
78
+ * network.
161
79
  */
162
80
  export interface VenlyClient {
81
+ /** The environment this client actually targets. When present, createServer
82
+ * refuses to start if it disagrees with the VENLY_ENV the write gate reads –
83
+ * the mock gate auto-arms writes, so the two must never diverge. */
84
+ readonly environment?: VenlyEnvironment;
163
85
  listRampRequests(params?: ListRampRequestsParams): Promise<RampRequestListItem[]>;
164
86
  getRampRequest(id: string): Promise<RampRequestDto>;
87
+ listAccounts(params?: {
88
+ page?: number;
89
+ size?: number;
90
+ }): Promise<Account[]>;
165
91
  getAccount(accountId: string): Promise<Account>;
92
+ listWallets(accountId: string, params?: {
93
+ page?: number;
94
+ size?: number;
95
+ }): Promise<Wallet[]>;
166
96
  listVirtualBankAccounts(accountId: string): Promise<VirtualBankAccount[]>;
97
+ getVirtualBankAccount(accountId: string, virtualBankAccountId: string): Promise<VirtualBankAccount>;
98
+ listTransfers(accountId: string, params?: {
99
+ page?: number;
100
+ size?: number;
101
+ }): Promise<Transfer[]>;
167
102
  getTransfer(accountId: string, transferId: string): Promise<Transfer>;
168
103
  listParties(params?: {
169
104
  page?: number;
170
105
  size?: number;
171
106
  }): Promise<Party[]>;
172
- getSupportedChains(): Promise<unknown[]>;
173
- getFiatCurrencies(): Promise<unknown[]>;
174
- getCryptocurrencies(): Promise<unknown[]>;
175
- getCompanyFees(): Promise<unknown>;
107
+ getParty(partyId: string): Promise<Party>;
108
+ getSupportedChains(): Promise<SupportedChains[]>;
109
+ getFiatCurrencies(): Promise<FiatCurrency[]>;
110
+ getCryptocurrencies(): Promise<CryptoCurrency[]>;
111
+ getCompanyFees(): Promise<VenlyFee[]>;
112
+ createParty(body: CreatePartyInput): Promise<Party>;
113
+ createAccount(body: CreateAccountInput): Promise<Account>;
114
+ createVirtualBankAccount(accountId: string, body: CreateVirtualBankAccountInput): Promise<VirtualBankAccount>;
176
115
  createFiatTransfer(senderAccountId: string, body: CreateFiatTransferInput): Promise<Transfer>;
116
+ createCurrentFiatTransfer(senderAccountId: string, body: CurrentCreateFiatTransferInput): Promise<Transfer>;
117
+ createCryptoTransfer(senderAccountId: string, body: CreateCryptoTransferInput): Promise<Transfer>;
177
118
  approveRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
178
119
  rejectRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
179
120
  createPayInSession(accountId: string, body: CreatePayInSessionRequest): Promise<PaymentSession>;
180
121
  }
122
+ export {};