@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/CHANGELOG.md +54 -0
- package/README.md +104 -35
- package/dist/client/sdk-client.d.ts +67 -0
- package/dist/client/sdk-client.js +183 -0
- package/dist/constants.d.ts +9 -5
- package/dist/constants.js +14 -5
- package/dist/index.d.ts +5 -4
- package/dist/index.js +8 -7
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +43 -0
- package/dist/reconcile.js +6 -0
- 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 +5 -1
- package/dist/safety.js +27 -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 +83 -12
- package/dist/tools/write-tools.js +245 -29
- package/dist/types.d.ts +70 -125
- 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/skills/{payment-link-lifecycle.md → payment-session-lifecycle.md} +8 -7
- 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 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
|
-
/**
|
|
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 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
|
|
155
|
-
* implementation; tests inject a mock. The MCP layer depends
|
|
156
|
-
*
|
|
157
|
-
*
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
120
|
+
createPayInSession(accountId: string, body: CreatePayInSessionRequest): Promise<PaymentSession>;
|
|
177
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.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": ">=
|
|
27
|
+
"node": ">=20"
|
|
23
28
|
},
|
|
24
29
|
"dependencies": {
|
|
25
|
-
"@
|
|
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/
|
|
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,6 +1,6 @@
|
|
|
1
|
-
# Skill: Create and track a payment
|
|
1
|
+
# Skill: Create and track a fiat-to-crypto payment session
|
|
2
2
|
|
|
3
|
-
Stand up a
|
|
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
|
-
- `
|
|
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
|
|
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. `
|
|
27
|
-
|
|
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
|
|
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
|
-
}
|