@venlyfinance/settlement-mcp 0.1.0 → 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/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 vendored OpenAPI specs at
5
- * projects/venly-docs-rebuild/api-reference/finance.yaml (servers:
6
- * https://api.venlyfinance.com/api/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 payment link (finance PaymentLink). */
98
- export interface PaymentLink {
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,51 +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 link POST (finance CreatePaymentLinkRequest). */
145
- export interface CreatePaymentLinkRequest {
146
- inAmount: string;
147
- inCurrency: string;
148
- outCryptocurrency?: string;
149
- redirectUrl?: string;
150
- externalRef?: string;
151
- metadata?: Record<string, string>;
71
+ /** Preserved across the dry-run preview and the live call when supplied. */
72
+ idempotencyKey?: string;
152
73
  }
153
74
  /**
154
- * The injectable Venly transport. HttpVenlyClient is the real fetch-based
155
- * implementation; tests inject a mock. The MCP layer depends ONLY on this
156
- * interface, never on a concrete transport, which is what makes the fail-closed
157
- * 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.
158
79
  */
159
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;
160
85
  listRampRequests(params?: ListRampRequestsParams): Promise<RampRequestListItem[]>;
161
86
  getRampRequest(id: string): Promise<RampRequestDto>;
87
+ listAccounts(params?: {
88
+ page?: number;
89
+ size?: number;
90
+ }): Promise<Account[]>;
162
91
  getAccount(accountId: string): Promise<Account>;
92
+ listWallets(accountId: string, params?: {
93
+ page?: number;
94
+ size?: number;
95
+ }): Promise<Wallet[]>;
163
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[]>;
164
102
  getTransfer(accountId: string, transferId: string): Promise<Transfer>;
165
103
  listParties(params?: {
166
104
  page?: number;
167
105
  size?: number;
168
106
  }): Promise<Party[]>;
169
- getSupportedChains(): Promise<unknown[]>;
170
- getFiatCurrencies(): Promise<unknown[]>;
171
- getCryptocurrencies(): Promise<unknown[]>;
172
- 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>;
173
115
  createFiatTransfer(senderAccountId: string, body: CreateFiatTransferInput): Promise<Transfer>;
116
+ createCurrentFiatTransfer(senderAccountId: string, body: CurrentCreateFiatTransferInput): Promise<Transfer>;
117
+ createCryptoTransfer(senderAccountId: string, body: CreateCryptoTransferInput): Promise<Transfer>;
174
118
  approveRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
175
119
  rejectRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
176
- createPaymentLink(accountId: string, body: CreatePaymentLinkRequest): Promise<PaymentLink>;
120
+ createPayInSession(accountId: string, body: CreatePayInSessionRequest): Promise<PaymentSession>;
177
121
  }
122
+ export {};
package/dist/types.js CHANGED
@@ -1,17 +1,9 @@
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 vendored OpenAPI specs at
5
- * projects/venly-docs-rebuild/api-reference/finance.yaml (servers:
6
- * https://api.venlyfinance.com/api/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
9
  export {};
package/package.json CHANGED
@@ -1,28 +1,34 @@
1
1
  {
2
2
  "name": "@venlyfinance/settlement-mcp",
3
- "version": "0.1.0",
4
- "description": "Venly Settlement MCP server. Human-gated operator surface over the Venly Finance and Fundflow APIs, plus an x402 machine-to-machine quote stub. Read-only by default, write tools fail closed.",
3
+ "version": "0.2.0",
4
+ "description": "Venly Finance MCP: SDK-backed tools, resources and prompts for building international money products safely.",
5
5
  "type": "module",
6
6
  "bin": {
7
+ "venly-finance-mcp": "dist/index.js",
7
8
  "venly-settlement-mcp": "dist/index.js"
8
9
  },
9
10
  "main": "dist/index.js",
10
11
  "files": [
11
12
  "dist",
13
+ "scripts",
12
14
  "skills",
13
- "README.md"
15
+ "README.md",
16
+ "CHANGELOG.md"
14
17
  ],
15
18
  "scripts": {
16
- "build": "tsc -p tsconfig.json",
19
+ "build": "rm -rf dist && tsc -p tsconfig.json",
17
20
  "start": "node dist/index.js",
21
+ "smoke:staging": "npm run build && node scripts/staging-smoke.mjs",
18
22
  "test": "node --import tsx --test test/*.test.ts",
19
- "typecheck": "tsc -p tsconfig.json --noEmit"
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "typecheck:test": "tsc -p tsconfig.test.json"
20
25
  },
21
26
  "engines": {
22
- "node": ">=18"
27
+ "node": ">=20"
23
28
  },
24
29
  "dependencies": {
25
- "@modelcontextprotocol/sdk": "^1.29.0",
30
+ "@venlyfinance/sdk": "^0.1.1",
31
+ "@modelcontextprotocol/sdk": "^1.30.0",
26
32
  "zod": "^3.23.8"
27
33
  },
28
34
  "devDependencies": {
@@ -33,12 +39,12 @@
33
39
  "license": "MIT",
34
40
  "repository": {
35
41
  "type": "git",
36
- "url": "git+https://github.com/timdierckxsens/venly-sdk.git",
42
+ "url": "git+https://github.com/Venly/venly-settlement-sdk.git",
37
43
  "directory": "settlement-mcp"
38
44
  },
39
- "homepage": "https://github.com/timdierckxsens/venly-sdk/tree/main/settlement-mcp#readme",
45
+ "homepage": "https://github.com/Venly/venly-settlement-sdk/tree/main/settlement-mcp#readme",
40
46
  "bugs": {
41
- "url": "https://github.com/timdierckxsens/venly-sdk/issues"
47
+ "url": "https://github.com/Venly/venly-settlement-sdk/issues"
42
48
  },
43
49
  "keywords": [
44
50
  "mcp",
@@ -46,6 +52,8 @@
46
52
  "stablecoin",
47
53
  "settlement",
48
54
  "payments",
55
+ "finance-api",
56
+ "ai-agents",
49
57
  "x402",
50
58
  "venly",
51
59
  "fintech",
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { sanitizeErrorMessage } from "../dist/results.js";
4
+ import { runStagingSmoke } from "../dist/staging-smoke.js";
5
+
6
+ try {
7
+ await runStagingSmoke();
8
+ console.log("\nSTAGING SMOKE PASSED: discovery and reads succeeded; writes stayed dry-run.");
9
+ } catch (error) {
10
+ const message = error instanceof Error ? error.message : String(error);
11
+ console.error(`\nSTAGING SMOKE FAILED: ${sanitizeErrorMessage(message)}`);
12
+ process.exitCode = 1;
13
+ }
@@ -0,0 +1,26 @@
1
+ # Build an international account experience
2
+
3
+ Use this skill when a user asks an AI coding agent to build an international account,
4
+ stablecoin account or neobank-like customer experience with Venly Finance.
5
+
6
+ ## Rules
7
+
8
+ 1. Read `venly://capabilities`, `venly://safety` and
9
+ `venly://workflows/international-account` when MCP resources are supported.
10
+ 2. Start with `VENLY_ENV=mock`; label every simulated state.
11
+ 3. Use `@venlyfinance/sdk` only in server-side application code. Never expose Venly
12
+ credentials or access tokens to the browser.
13
+ 4. Assemble atomic capabilities: party, account, auto-provisioned wallet/balances, EUR
14
+ receiving account, transfer, status and reconciliation.
15
+ 5. Creating a party does not complete KYC/KYB. Show returned compliance states.
16
+ 6. Venly provides infrastructure through regulated partners. Do not imply a bank
17
+ charter, deposit insurance or unsupported geographic/currency coverage.
18
+ 7. Card issuing is not exposed by the current Finance OpenAPI contract.
19
+ 8. Ask for an explicit decision before moving to staging or arming a write. Dry-run
20
+ live-environment mutations before confirmation.
21
+
22
+ ## Outcome
23
+
24
+ Produce a credible customer-facing money-product experience and a README documenting
25
+ the mock setup and explicit staging transition. Do not build a generic infrastructure
26
+ dashboard or hide financial mutations inside one autonomous operation.
@@ -0,0 +1,11 @@
1
+ # Move a Venly Finance build from mock to staging
2
+
3
+ 1. Keep the working mock flow and its visible environment label.
4
+ 2. Set `VENLY_ENV=staging` and provide client credentials through server-side secret
5
+ storage.
6
+ 3. Confirm enabled custody model, chains, assets and regulated-partner coverage.
7
+ 4. Use a documented KYC-verified staging account before provisioning a EUR vIBAN.
8
+ 5. Run read-only smoke checks before setting `VENLY_MCP_LIVE=1`.
9
+ 6. Dry-run each intended write, review the normalized request, then explicitly confirm.
10
+ 7. Never fall back implicitly to mock when staging authentication or capability checks
11
+ fail.
@@ -1,6 +1,6 @@
1
- # Skill: Create and track a payment link
1
+ # Skill: Create and track a fiat-to-crypto payment session
2
2
 
3
- Stand up a fiat-to-crypto payment link for an account, hand the URL to a payer,
3
+ Stand up a hosted pay-in session for an account, hand the URL to a payer,
4
4
  and follow the payment through to settlement.
5
5
 
6
6
  ## When to use
@@ -13,7 +13,7 @@ collections, top-ups.
13
13
 
14
14
  - `get_account` (read)
15
15
  - `list_virtual_bank_accounts` (read)
16
- - `create_payment_link` (write, disarmed by default)
16
+ - `create_payment_session` (write, disarmed by default)
17
17
  - `get_transfer` (read)
18
18
 
19
19
  ## Steps
@@ -21,10 +21,11 @@ collections, top-ups.
21
21
  1. `get_account` for the collecting account. Confirm `status: "ACTIVE"` - an
22
22
  unverified or suspended account cannot collect.
23
23
  2. Optional context: `list_virtual_bank_accounts` shows the account's existing
24
- collection surfaces (IBAN + referenceCode); a payment link is the hosted
24
+ collection surfaces (IBAN + referenceCode); a payment session is the hosted
25
25
  alternative for payers who won't do a bank transfer.
26
- 3. `create_payment_link` with the `accountId` and an `externalRef` your own
27
- system can reconcile on later.
26
+ 3. `create_payment_session` with the `accountId`, a `callbackUrl` your system
27
+ will receive the completion webhook on, and an `externalRef` you can
28
+ reconcile on later. An `idempotencyKey` is generated when you don't pass one.
28
29
  - By default the tool returns a dry-run object with the exact POST it would
29
30
  send. Review it.
30
31
  - Live execution needs all three: `confirm: true`, `VENLY_MCP_LIVE=1`, and
@@ -36,7 +37,7 @@ collections, top-ups.
36
37
 
37
38
  ## Notes
38
39
 
39
- - Payment links expire (`expiresAt`); a link that was never paid ends at
40
+ - Payment sessions expire (`expiresAt`); a session that was never paid ends at
40
41
  `EXPIRED`, not `FAILED`.
41
42
  - Statuses walk `CREATED → PENDING_PAYMENT → PAYMENT_RECEIVED → CONVERTING →
42
43
  MINTING → COMPLETED`, with `FAILED`, `CANCELLED`, `REFUNDING`, `REFUNDED` as
@@ -1,58 +0,0 @@
1
- /**
2
- * HttpVenlyClient: a minimal fetch-based transport implementing VenlyClient.
3
- *
4
- * TRANSPORT NOTE
5
- * --------------
6
- * Minimal by design: OAuth2 client credentials, lazy token fetch, staging
7
- * defaults. A future release replaces this with a thin adapter over
8
- * `@venlyfinance/sdk` (single-flight token refresh, automatic idempotency
9
- * keys, retry/backoff, richer errors) with no change to the tool interface.
10
- *
11
- * Safety invariants honored here:
12
- * - credentials are read from env ONLY, never logged, never returned in output.
13
- * - no request is issued at construction time; tokens are fetched lazily.
14
- * - this class does not know about the write gate. It only issues a live call
15
- * when a write method is invoked, and write methods are invoked only after
16
- * the gate in safety.ts is armed. Read-only by default is enforced upstream.
17
- */
18
- import type { Account, CreateFiatTransferInput, CreatePaymentLinkRequest, ListRampRequestsParams, OptimisticLockingBody, Party, PaymentLink, RampRequestDto, RampRequestListItem, Transfer, VenlyClient, VirtualBankAccount } from "../types.js";
19
- export interface HttpVenlyClientConfig {
20
- financeBaseUrl?: string;
21
- fundflowBaseUrl?: string;
22
- tokenUrl?: string;
23
- clientId?: string;
24
- clientSecret?: string;
25
- /** Injectable for tests; defaults to global fetch. */
26
- fetchImpl?: typeof fetch;
27
- }
28
- export declare class HttpVenlyClient implements VenlyClient {
29
- private readonly financeBaseUrl;
30
- private readonly fundflowBaseUrl;
31
- private readonly tokenUrl;
32
- private readonly clientId?;
33
- private readonly clientSecret?;
34
- private readonly fetchImpl;
35
- private token;
36
- constructor(config?: HttpVenlyClientConfig);
37
- /** Build a client from environment variables. Never logs credentials. */
38
- static fromEnv(env?: NodeJS.ProcessEnv): HttpVenlyClient;
39
- private getAccessToken;
40
- private request;
41
- getAccount(accountId: string): Promise<Account>;
42
- listVirtualBankAccounts(accountId: string): Promise<VirtualBankAccount[]>;
43
- getTransfer(accountId: string, transferId: string): Promise<Transfer>;
44
- listParties(params?: {
45
- page?: number;
46
- size?: number;
47
- }): Promise<Party[]>;
48
- listRampRequests(params?: ListRampRequestsParams): Promise<RampRequestListItem[]>;
49
- getRampRequest(id: string): Promise<RampRequestDto>;
50
- getSupportedChains(): Promise<unknown[]>;
51
- getFiatCurrencies(): Promise<unknown[]>;
52
- getCryptocurrencies(): Promise<unknown[]>;
53
- getCompanyFees(): Promise<unknown>;
54
- createFiatTransfer(senderAccountId: string, body: CreateFiatTransferInput): Promise<Transfer>;
55
- approveRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
56
- rejectRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
57
- createPaymentLink(accountId: string, body: CreatePaymentLinkRequest): Promise<PaymentLink>;
58
- }
@@ -1,163 +0,0 @@
1
- /**
2
- * HttpVenlyClient: a minimal fetch-based transport implementing VenlyClient.
3
- *
4
- * TRANSPORT NOTE
5
- * --------------
6
- * Minimal by design: OAuth2 client credentials, lazy token fetch, staging
7
- * defaults. A future release replaces this with a thin adapter over
8
- * `@venlyfinance/sdk` (single-flight token refresh, automatic idempotency
9
- * keys, retry/backoff, richer errors) with no change to the tool interface.
10
- *
11
- * Safety invariants honored here:
12
- * - credentials are read from env ONLY, never logged, never returned in output.
13
- * - no request is issued at construction time; tokens are fetched lazily.
14
- * - this class does not know about the write gate. It only issues a live call
15
- * when a write method is invoked, and write methods are invoked only after
16
- * the gate in safety.ts is armed. Read-only by default is enforced upstream.
17
- */
18
- import { DEFAULT_FINANCE_BASE_URL, DEFAULT_FUNDFLOW_BASE_URL, DEFAULT_TOKEN_URL, } from "../constants.js";
19
- /** Unwrap the Venly `{ success, result, pagination }` envelope. */
20
- function unwrap(payload) {
21
- if (payload && typeof payload === "object" && "result" in payload) {
22
- return payload.result;
23
- }
24
- return payload;
25
- }
26
- export class HttpVenlyClient {
27
- financeBaseUrl;
28
- fundflowBaseUrl;
29
- tokenUrl;
30
- clientId;
31
- clientSecret;
32
- fetchImpl;
33
- token = null;
34
- constructor(config = {}) {
35
- this.financeBaseUrl = config.financeBaseUrl ?? DEFAULT_FINANCE_BASE_URL;
36
- this.fundflowBaseUrl = config.fundflowBaseUrl ?? DEFAULT_FUNDFLOW_BASE_URL;
37
- this.tokenUrl = config.tokenUrl ?? DEFAULT_TOKEN_URL;
38
- this.clientId = config.clientId;
39
- this.clientSecret = config.clientSecret;
40
- this.fetchImpl = config.fetchImpl ?? fetch;
41
- }
42
- /** Build a client from environment variables. Never logs credentials. */
43
- static fromEnv(env = process.env) {
44
- return new HttpVenlyClient({
45
- financeBaseUrl: env.VENLY_FINANCE_BASE_URL,
46
- fundflowBaseUrl: env.VENLY_FUNDFLOW_BASE_URL,
47
- tokenUrl: env.VENLY_TOKEN_URL,
48
- clientId: env.VENLY_CLIENT_ID,
49
- clientSecret: env.VENLY_CLIENT_SECRET,
50
- });
51
- }
52
- async getAccessToken() {
53
- const now = Date.now();
54
- if (this.token && this.token.expiresAt > now + 5_000) {
55
- return this.token.accessToken;
56
- }
57
- if (!this.clientId || !this.clientSecret) {
58
- // Do not include any credential material in the error.
59
- throw new Error("Missing Venly credentials. Set VENLY_CLIENT_ID and VENLY_CLIENT_SECRET.");
60
- }
61
- const body = new URLSearchParams({
62
- grant_type: "client_credentials",
63
- client_id: this.clientId,
64
- client_secret: this.clientSecret,
65
- });
66
- const res = await this.fetchImpl(this.tokenUrl, {
67
- method: "POST",
68
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
69
- body,
70
- });
71
- if (!res.ok) {
72
- // Never echo the request body (contains the secret).
73
- throw new Error(`Token request failed with status ${res.status}`);
74
- }
75
- const json = (await res.json());
76
- const expiresInMs = (json.expires_in ?? 300) * 1000;
77
- this.token = {
78
- accessToken: json.access_token,
79
- expiresAt: now + expiresInMs,
80
- };
81
- return json.access_token;
82
- }
83
- async request(base, method, path, opts = {}) {
84
- const token = await this.getAccessToken();
85
- const url = new URL(path.replace(/^\//, ""), base.endsWith("/") ? base : base + "/");
86
- if (opts.query) {
87
- for (const [k, v] of Object.entries(opts.query)) {
88
- if (v !== undefined && v !== null)
89
- url.searchParams.set(k, String(v));
90
- }
91
- }
92
- const headers = {
93
- Authorization: `Bearer ${token}`,
94
- Accept: "application/json",
95
- };
96
- if (opts.body !== undefined) {
97
- headers["Content-Type"] = "application/json";
98
- // Idempotency for writes. Production SDK does this automatically.
99
- headers["Idempotency-Key"] = crypto.randomUUID();
100
- }
101
- const res = await this.fetchImpl(url.toString(), {
102
- method,
103
- headers,
104
- body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
105
- });
106
- if (!res.ok) {
107
- throw new Error(`Venly API ${method} ${path} failed with status ${res.status}`);
108
- }
109
- const json = await res.json();
110
- return unwrap(json);
111
- }
112
- // ----- READ (finance) -----
113
- getAccount(accountId) {
114
- return this.request(this.financeBaseUrl, "GET", `/accounts/${accountId}`);
115
- }
116
- listVirtualBankAccounts(accountId) {
117
- return this.request(this.financeBaseUrl, "GET", `/accounts/${accountId}/virtual-bank-accounts`);
118
- }
119
- getTransfer(accountId, transferId) {
120
- return this.request(this.financeBaseUrl, "GET", `/accounts/${accountId}/transfers/${transferId}`);
121
- }
122
- listParties(params = {}) {
123
- return this.request(this.financeBaseUrl, "GET", "/parties", { query: params });
124
- }
125
- // ----- READ (fundflow) -----
126
- listRampRequests(params = {}) {
127
- return this.request(this.fundflowBaseUrl, "GET", "/v1/ramp-requests", {
128
- query: params,
129
- });
130
- }
131
- getRampRequest(id) {
132
- return this.request(this.fundflowBaseUrl, "GET", `/v1/ramp-requests/${id}`);
133
- }
134
- getSupportedChains() {
135
- return this.request(this.fundflowBaseUrl, "GET", "/v1/chains");
136
- }
137
- getFiatCurrencies() {
138
- return this.request(this.fundflowBaseUrl, "GET", "/v1/fiat-currencies");
139
- }
140
- getCryptocurrencies() {
141
- return this.request(this.fundflowBaseUrl, "GET", "/v1/crypto-currencies");
142
- }
143
- getCompanyFees() {
144
- return this.request(this.fundflowBaseUrl, "GET", "/v1/fees");
145
- }
146
- // ----- WRITE -----
147
- createFiatTransfer(senderAccountId, body) {
148
- return this.request(this.financeBaseUrl, "POST", `/accounts/${senderAccountId}/transfers/fiat`, { body });
149
- }
150
- approveRampRequest(id, body) {
151
- return this.request(this.fundflowBaseUrl, "POST", `/v1/ramp-requests/${id}/approve`, {
152
- body,
153
- });
154
- }
155
- rejectRampRequest(id, body) {
156
- return this.request(this.fundflowBaseUrl, "POST", `/v1/ramp-requests/${id}/reject`, {
157
- body,
158
- });
159
- }
160
- createPaymentLink(accountId, body) {
161
- return this.request(this.financeBaseUrl, "POST", `/accounts/${accountId}/fiat-to-crypto/payment-links`, { body });
162
- }
163
- }