@venlyfinance/settlement-mcp 0.1.1 → 0.3.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/CHANGELOG.md +89 -0
- package/README.md +98 -31
- package/dist/client/sdk-client.d.ts +67 -0
- package/dist/client/sdk-client.js +183 -0
- package/dist/constants.d.ts +8 -4
- package/dist/constants.js +16 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +15 -7
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +43 -0
- package/dist/reconcile.d.ts +7 -0
- package/dist/reconcile.js +26 -2
- package/dist/resources.d.ts +2 -0
- package/dist/resources.js +106 -0
- package/dist/results.d.ts +18 -0
- package/dist/results.js +25 -0
- package/dist/safety.d.ts +7 -1
- package/dist/safety.js +28 -4
- package/dist/server.js +15 -1
- package/dist/staging-smoke.d.ts +17 -0
- package/dist/staging-smoke.js +199 -0
- package/dist/tools/read-tools.js +91 -14
- package/dist/tools/write-tools.js +246 -22
- package/dist/types.d.ts +69 -127
- package/dist/types.js +5 -13
- package/package.json +18 -10
- package/scripts/staging-smoke.mjs +13 -0
- package/skills/build-international-account.md +26 -0
- package/skills/mock-to-staging.md +11 -0
- package/dist/client/http-client.d.ts +0 -58
- package/dist/client/http-client.js +0 -163
package/dist/types.d.ts
CHANGED
|
@@ -1,108 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Generated API contracts + the injectable VenlyClient interface.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
|
158
|
-
* implementation; tests inject a mock. The MCP layer depends
|
|
159
|
-
*
|
|
160
|
-
*
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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 {};
|
package/dist/types.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Generated API contracts + the injectable VenlyClient interface.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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.
|
|
4
|
-
"description": "Venly
|
|
3
|
+
"version": "0.3.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": ">=
|
|
27
|
+
"node": ">=20"
|
|
23
28
|
},
|
|
24
29
|
"dependencies": {
|
|
25
|
-
"@
|
|
30
|
+
"@venlyfinance/sdk": "^0.2.0",
|
|
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/
|
|
42
|
+
"url": "git+https://github.com/Venly/venly-settlement-sdk.git",
|
|
37
43
|
"directory": "settlement-mcp"
|
|
38
44
|
},
|
|
39
|
-
"homepage": "https://github.com/
|
|
45
|
+
"homepage": "https://github.com/Venly/venly-settlement-sdk/tree/main/settlement-mcp#readme",
|
|
40
46
|
"bugs": {
|
|
41
|
-
"url": "https://github.com/
|
|
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,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, CreatePayInSessionRequest, ListRampRequestsParams, OptimisticLockingBody, Party, PaymentSession, 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
|
-
createPayInSession(accountId: string, body: CreatePayInSessionRequest): Promise<PaymentSession>;
|
|
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
|
-
createPayInSession(accountId, body) {
|
|
161
|
-
return this.request(this.financeBaseUrl, "POST", `/accounts/${accountId}/fiat-to-crypto/payment-sessions`, { body });
|
|
162
|
-
}
|
|
163
|
-
}
|