@venlyfinance/react 0.1.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/AGENTS.md +23 -0
- package/CHANGELOG.md +12 -0
- package/LICENSE +20 -0
- package/README.md +129 -0
- package/dist/flows/four-eyes.d.ts +71 -0
- package/dist/flows/four-eyes.js +91 -0
- package/dist/flows/ramp-lifecycle.d.ts +96 -0
- package/dist/flows/ramp-lifecycle.js +148 -0
- package/dist/flows/staged-transfer.d.ts +114 -0
- package/dist/flows/staged-transfer.js +182 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +18 -0
- package/dist/keys.d.ts +20 -0
- package/dist/keys.js +20 -0
- package/dist/mutations.d.ts +123 -0
- package/dist/mutations.js +58 -0
- package/dist/provider.d.ts +66 -0
- package/dist/provider.js +98 -0
- package/dist/proxy.d.ts +39 -0
- package/dist/proxy.js +42 -0
- package/dist/queries.d.ts +200 -0
- package/dist/queries.js +91 -0
- package/dist/query-options.d.ts +244 -0
- package/dist/query-options.js +73 -0
- package/package.json +65 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { Transfer, VenlyFinanceClient } from "@venlyfinance/sdk";
|
|
2
|
+
import { type VenlyClients } from "../provider.js";
|
|
3
|
+
type CreateFiatBody = Parameters<VenlyFinanceClient["transfers"]["createFiat"]>[1];
|
|
4
|
+
type CreateCryptoBody = Parameters<VenlyFinanceClient["transfers"]["createCrypto"]>[1];
|
|
5
|
+
/**
|
|
6
|
+
* The API contract requires an idempotencyKey on the create body; in this
|
|
7
|
+
* machine the key is pinned by stage(), so drafts may omit it (a provided
|
|
8
|
+
* key is honoured and pinned instead).
|
|
9
|
+
*/
|
|
10
|
+
type DraftBody<B extends {
|
|
11
|
+
idempotencyKey: string;
|
|
12
|
+
}> = Omit<B, "idempotencyKey"> & {
|
|
13
|
+
idempotencyKey?: string;
|
|
14
|
+
};
|
|
15
|
+
/** What the operator is composing. Discriminated on the money rail. */
|
|
16
|
+
export type TransferDraft = {
|
|
17
|
+
kind: "fiat";
|
|
18
|
+
senderAccountId: string;
|
|
19
|
+
body: DraftBody<CreateFiatBody>;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "crypto";
|
|
22
|
+
senderAccountId: string;
|
|
23
|
+
body: DraftBody<CreateCryptoBody>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The exact request that will be sent on confirm. The idempotency key is
|
|
27
|
+
* pinned at staging time: however many times confirm() is retried (double
|
|
28
|
+
* click, flaky network, impatient operator), the API can only execute the
|
|
29
|
+
* movement once. This mirrors the settlement MCP's stage-then-confirm write
|
|
30
|
+
* gate, where the dry-run answer IS the request that later executes.
|
|
31
|
+
*/
|
|
32
|
+
export interface StagedRequest {
|
|
33
|
+
draft: TransferDraft;
|
|
34
|
+
idempotencyKey: string;
|
|
35
|
+
stagedAt: string;
|
|
36
|
+
}
|
|
37
|
+
export type StagedTransferState = {
|
|
38
|
+
phase: "draft";
|
|
39
|
+
issues: string[];
|
|
40
|
+
} | {
|
|
41
|
+
phase: "staged";
|
|
42
|
+
staged: StagedRequest;
|
|
43
|
+
} | {
|
|
44
|
+
phase: "submitting";
|
|
45
|
+
staged: StagedRequest;
|
|
46
|
+
} | {
|
|
47
|
+
phase: "pending";
|
|
48
|
+
staged: StagedRequest;
|
|
49
|
+
transfer: Transfer;
|
|
50
|
+
} | {
|
|
51
|
+
phase: "completed";
|
|
52
|
+
staged: StagedRequest;
|
|
53
|
+
transfer: Transfer;
|
|
54
|
+
} | {
|
|
55
|
+
phase: "failed";
|
|
56
|
+
staged?: StagedRequest;
|
|
57
|
+
transfer?: Transfer;
|
|
58
|
+
error?: unknown;
|
|
59
|
+
reason: "submit-error" | "transfer-failed" | "poll-timeout";
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Structural validation only: presence and sign, never business rules the
|
|
63
|
+
* API owns (limits, compliance, balance). A draft that passes here can still
|
|
64
|
+
* be rejected server-side; that surfaces as phase "failed".
|
|
65
|
+
*/
|
|
66
|
+
export declare function validateDraft(draft: TransferDraft): string[];
|
|
67
|
+
export interface StagedTransferOptions {
|
|
68
|
+
/** Poll interval for status while the transfer is PENDING. Default 1500ms. */
|
|
69
|
+
pollIntervalMs?: number;
|
|
70
|
+
/** Give up polling after this long and report "poll-timeout". Default 120s. */
|
|
71
|
+
maxPollMs?: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Framework-agnostic core of the stage-then-confirm machine, so the whole
|
|
75
|
+
* lifecycle is testable without a DOM. The React hook below is a thin
|
|
76
|
+
* subscription over this class.
|
|
77
|
+
*
|
|
78
|
+
* draft → stage() → staged → confirm() → submitting → pending → completed
|
|
79
|
+
* │ ↑ ↘ failed
|
|
80
|
+
* edit() └──────────────────────────────────────┘
|
|
81
|
+
*/
|
|
82
|
+
export declare class StagedTransferController {
|
|
83
|
+
#private;
|
|
84
|
+
private readonly clients;
|
|
85
|
+
private readonly options;
|
|
86
|
+
constructor(clients: VenlyClients, options?: StagedTransferOptions);
|
|
87
|
+
subscribe: (listener: () => void) => (() => void);
|
|
88
|
+
getSnapshot: () => StagedTransferState;
|
|
89
|
+
/** Validate and freeze the draft; pins the idempotency key. */
|
|
90
|
+
stage(draft: TransferDraft): boolean;
|
|
91
|
+
/** Back to composing; the staged request (and its key) is discarded. */
|
|
92
|
+
edit(): void;
|
|
93
|
+
reset(): void;
|
|
94
|
+
/** Execute the staged request, then poll until the transfer is terminal. */
|
|
95
|
+
confirm(): Promise<void>;
|
|
96
|
+
dispose(): void;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Stage-then-confirm transfer flow.
|
|
100
|
+
*
|
|
101
|
+
* ```tsx
|
|
102
|
+
* const t = useStagedTransfer();
|
|
103
|
+
* t.stage({ kind: "fiat", senderAccountId, body }); // review screen renders t.state.staged
|
|
104
|
+
* await t.confirm(); // executes once, then polls to terminal
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export declare function useStagedTransfer(options?: StagedTransferOptions): {
|
|
108
|
+
state: StagedTransferState;
|
|
109
|
+
stage: (draft: TransferDraft) => boolean;
|
|
110
|
+
edit: () => void;
|
|
111
|
+
confirm: () => Promise<void>;
|
|
112
|
+
reset: () => void;
|
|
113
|
+
};
|
|
114
|
+
export {};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { useEffect, useRef, useSyncExternalStore } from "react";
|
|
2
|
+
import { useVenly } from "../provider.js";
|
|
3
|
+
/**
|
|
4
|
+
* Structural validation only: presence and sign, never business rules the
|
|
5
|
+
* API owns (limits, compliance, balance). A draft that passes here can still
|
|
6
|
+
* be rejected server-side; that surfaces as phase "failed".
|
|
7
|
+
*/
|
|
8
|
+
export function validateDraft(draft) {
|
|
9
|
+
const issues = [];
|
|
10
|
+
if (!draft.senderAccountId)
|
|
11
|
+
issues.push("senderAccountId is required");
|
|
12
|
+
if (!draft.body) {
|
|
13
|
+
issues.push("body is required");
|
|
14
|
+
return issues;
|
|
15
|
+
}
|
|
16
|
+
const amount = draft.body.amount;
|
|
17
|
+
if (typeof amount === "number" && !(amount > 0)) {
|
|
18
|
+
issues.push("amount must be greater than zero");
|
|
19
|
+
}
|
|
20
|
+
if (draft.kind === "fiat" && !draft.body.currency) {
|
|
21
|
+
issues.push("currency is required for a fiat transfer");
|
|
22
|
+
}
|
|
23
|
+
if (draft.kind === "crypto") {
|
|
24
|
+
if (!draft.body.asset)
|
|
25
|
+
issues.push("asset is required for a crypto transfer");
|
|
26
|
+
if (!draft.body.chain)
|
|
27
|
+
issues.push("chain is required for a crypto transfer");
|
|
28
|
+
}
|
|
29
|
+
return issues;
|
|
30
|
+
}
|
|
31
|
+
const INITIAL = { phase: "draft", issues: [] };
|
|
32
|
+
/**
|
|
33
|
+
* Framework-agnostic core of the stage-then-confirm machine, so the whole
|
|
34
|
+
* lifecycle is testable without a DOM. The React hook below is a thin
|
|
35
|
+
* subscription over this class.
|
|
36
|
+
*
|
|
37
|
+
* draft → stage() → staged → confirm() → submitting → pending → completed
|
|
38
|
+
* │ ↑ ↘ failed
|
|
39
|
+
* edit() └──────────────────────────────────────┘
|
|
40
|
+
*/
|
|
41
|
+
export class StagedTransferController {
|
|
42
|
+
clients;
|
|
43
|
+
options;
|
|
44
|
+
#state = INITIAL;
|
|
45
|
+
#listeners = new Set();
|
|
46
|
+
#disposed = false;
|
|
47
|
+
#pollTimer;
|
|
48
|
+
constructor(clients, options = {}) {
|
|
49
|
+
this.clients = clients;
|
|
50
|
+
this.options = options;
|
|
51
|
+
}
|
|
52
|
+
subscribe = (listener) => {
|
|
53
|
+
this.#listeners.add(listener);
|
|
54
|
+
return () => this.#listeners.delete(listener);
|
|
55
|
+
};
|
|
56
|
+
getSnapshot = () => this.#state;
|
|
57
|
+
/** Validate and freeze the draft; pins the idempotency key. */
|
|
58
|
+
stage(draft) {
|
|
59
|
+
const issues = validateDraft(draft);
|
|
60
|
+
if (issues.length > 0) {
|
|
61
|
+
this.#set({ phase: "draft", issues });
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const bodyKey = draft.body.idempotencyKey;
|
|
65
|
+
this.#set({
|
|
66
|
+
phase: "staged",
|
|
67
|
+
staged: {
|
|
68
|
+
draft,
|
|
69
|
+
idempotencyKey: bodyKey ?? crypto.randomUUID(),
|
|
70
|
+
stagedAt: new Date().toISOString(),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
/** Back to composing; the staged request (and its key) is discarded. */
|
|
76
|
+
edit() {
|
|
77
|
+
if (this.#state.phase === "staged")
|
|
78
|
+
this.#set(INITIAL);
|
|
79
|
+
}
|
|
80
|
+
reset() {
|
|
81
|
+
this.#clearPoll();
|
|
82
|
+
this.#set(INITIAL);
|
|
83
|
+
}
|
|
84
|
+
/** Execute the staged request, then poll until the transfer is terminal. */
|
|
85
|
+
async confirm() {
|
|
86
|
+
if (this.#state.phase !== "staged")
|
|
87
|
+
return;
|
|
88
|
+
const staged = this.#state.staged;
|
|
89
|
+
this.#set({ phase: "submitting", staged });
|
|
90
|
+
let transfer;
|
|
91
|
+
try {
|
|
92
|
+
const { draft, idempotencyKey } = staged;
|
|
93
|
+
transfer =
|
|
94
|
+
draft.kind === "fiat"
|
|
95
|
+
? await this.clients.finance.transfers.createFiat(draft.senderAccountId, { ...draft.body, idempotencyKey })
|
|
96
|
+
: await this.clients.finance.transfers.createCrypto(draft.senderAccountId, { ...draft.body, idempotencyKey });
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
this.#set({ phase: "failed", staged, error, reason: "submit-error" });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (this.#disposed)
|
|
103
|
+
return;
|
|
104
|
+
const next = this.#applyTransfer(staged, transfer);
|
|
105
|
+
if (next.phase === "pending") {
|
|
106
|
+
this.#poll(staged, transfer, Date.now());
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
dispose() {
|
|
110
|
+
this.#disposed = true;
|
|
111
|
+
this.#clearPoll();
|
|
112
|
+
this.#listeners.clear();
|
|
113
|
+
}
|
|
114
|
+
#applyTransfer(staged, transfer) {
|
|
115
|
+
const next = transfer.status === "COMPLETED"
|
|
116
|
+
? { phase: "completed", staged, transfer }
|
|
117
|
+
: transfer.status === "FAILED"
|
|
118
|
+
? { phase: "failed", staged, transfer, reason: "transfer-failed" }
|
|
119
|
+
: { phase: "pending", staged, transfer };
|
|
120
|
+
this.#set(next);
|
|
121
|
+
return next;
|
|
122
|
+
}
|
|
123
|
+
#poll(staged, transfer, startedAt) {
|
|
124
|
+
const interval = this.options.pollIntervalMs ?? 1_500;
|
|
125
|
+
const maxPollMs = this.options.maxPollMs ?? 120_000;
|
|
126
|
+
this.#pollTimer = setTimeout(async () => {
|
|
127
|
+
if (this.#disposed)
|
|
128
|
+
return;
|
|
129
|
+
if (Date.now() - startedAt > maxPollMs) {
|
|
130
|
+
this.#set({ phase: "failed", staged, transfer, reason: "poll-timeout" });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const fresh = await this.clients.finance.transfers.get(staged.draft.senderAccountId, transfer.id ?? "");
|
|
135
|
+
if (this.#disposed)
|
|
136
|
+
return;
|
|
137
|
+
const next = this.#applyTransfer(staged, fresh);
|
|
138
|
+
if (next.phase === "pending")
|
|
139
|
+
this.#poll(staged, fresh, startedAt);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Transient read failure: keep the last known state and try again.
|
|
143
|
+
if (!this.#disposed)
|
|
144
|
+
this.#poll(staged, transfer, startedAt);
|
|
145
|
+
}
|
|
146
|
+
}, interval);
|
|
147
|
+
}
|
|
148
|
+
#clearPoll() {
|
|
149
|
+
if (this.#pollTimer !== undefined)
|
|
150
|
+
clearTimeout(this.#pollTimer);
|
|
151
|
+
this.#pollTimer = undefined;
|
|
152
|
+
}
|
|
153
|
+
#set(state) {
|
|
154
|
+
this.#state = state;
|
|
155
|
+
for (const listener of this.#listeners)
|
|
156
|
+
listener();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Stage-then-confirm transfer flow.
|
|
161
|
+
*
|
|
162
|
+
* ```tsx
|
|
163
|
+
* const t = useStagedTransfer();
|
|
164
|
+
* t.stage({ kind: "fiat", senderAccountId, body }); // review screen renders t.state.staged
|
|
165
|
+
* await t.confirm(); // executes once, then polls to terminal
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export function useStagedTransfer(options) {
|
|
169
|
+
const clients = useVenly();
|
|
170
|
+
const ref = useRef(null);
|
|
171
|
+
ref.current ??= new StagedTransferController(clients, options);
|
|
172
|
+
const controller = ref.current;
|
|
173
|
+
useEffect(() => () => controller.dispose(), [controller]);
|
|
174
|
+
const state = useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
|
|
175
|
+
return {
|
|
176
|
+
state,
|
|
177
|
+
stage: (draft) => controller.stage(draft),
|
|
178
|
+
edit: () => controller.edit(),
|
|
179
|
+
confirm: () => controller.confirm(),
|
|
180
|
+
reset: () => controller.reset(),
|
|
181
|
+
};
|
|
182
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { VenlyProvider, useVenly, useVenlyMock, type VenlyClients, type VenlyProviderProps, type VenlyReactEnvironment, } from "./provider.js";
|
|
2
|
+
export { venlyKeys } from "./keys.js";
|
|
3
|
+
export { venlyQueries, type AccountsQuery, type FeeQuoteInput, type PartiesQuery, type RampRequestsQuery, type TransfersQuery, type VirtualBankAccountsQuery, type WalletsQuery, } from "./query-options.js";
|
|
4
|
+
export { useAccount, useAccounts, useCompanyFees, useFeeQuote, useParties, useParty, useRampRequest, useRampRequests, useReferenceData, useTransfer, useTransfers, useVirtualBankAccounts, useWallets, } from "./queries.js";
|
|
5
|
+
export { useCreateAccount, useCreateParty, useCreatePaymentSession, useCreateRampRequest, useCreateVirtualBankAccount, } from "./mutations.js";
|
|
6
|
+
export { StagedTransferController, useStagedTransfer, validateDraft, type StagedRequest, type StagedTransferOptions, type StagedTransferState, type TransferDraft, } from "./flows/staged-transfer.js";
|
|
7
|
+
export { approvalCapabilities, interpretApprovalError, useFourEyesApproval, type ApprovalCapability, type ApprovalFailureKind, type FourEyesState, } from "./flows/four-eyes.js";
|
|
8
|
+
export { describeRampStatus, useRampLifecycle, type RampLifecycleOptions, type RampStatus, type RampStatusDescriptor, } from "./flows/ramp-lifecycle.js";
|
|
9
|
+
export { proxyClientOptions, VENLY_PROXY_SECRET_SENTINEL, type ProxyClientOptions, } from "./proxy.js";
|
|
10
|
+
export { FundflowClient, VenlyApiError, VenlyAuthError, VenlyFinanceClient, } from "@venlyfinance/sdk";
|
|
11
|
+
export type { Account, Party, RampRequest, Transfer, VirtualBankAccount, Wallet, } from "@venlyfinance/sdk";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Provider + context
|
|
2
|
+
export { VenlyProvider, useVenly, useVenlyMock, } from "./provider.js";
|
|
3
|
+
// Query keys + pure query factories (prefetching, loaders, tests)
|
|
4
|
+
export { venlyKeys } from "./keys.js";
|
|
5
|
+
export { venlyQueries, } from "./query-options.js";
|
|
6
|
+
// Read hooks
|
|
7
|
+
export { useAccount, useAccounts, useCompanyFees, useFeeQuote, useParties, useParty, useRampRequest, useRampRequests, useReferenceData, useTransfer, useTransfers, useVirtualBankAccounts, useWallets, } from "./queries.js";
|
|
8
|
+
// Write hooks
|
|
9
|
+
export { useCreateAccount, useCreateParty, useCreatePaymentSession, useCreateRampRequest, useCreateVirtualBankAccount, } from "./mutations.js";
|
|
10
|
+
// Flow machines: the regulated-money lifecycles
|
|
11
|
+
export { StagedTransferController, useStagedTransfer, validateDraft, } from "./flows/staged-transfer.js";
|
|
12
|
+
export { approvalCapabilities, interpretApprovalError, useFourEyesApproval, } from "./flows/four-eyes.js";
|
|
13
|
+
export { describeRampStatus, useRampLifecycle, } from "./flows/ramp-lifecycle.js";
|
|
14
|
+
// Browser-safe deployment shape
|
|
15
|
+
export { proxyClientOptions, VENLY_PROXY_SECRET_SENTINEL, } from "./proxy.js";
|
|
16
|
+
// Re-export the SDK surface consumers need alongside the hooks, so app code
|
|
17
|
+
// can import one package. The SDK remains the canonical home.
|
|
18
|
+
export { FundflowClient, VenlyApiError, VenlyAuthError, VenlyFinanceClient, } from "@venlyfinance/sdk";
|
package/dist/keys.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query-key factory. Every hook and every cache invalidation goes through
|
|
3
|
+
* this object so keys can never drift apart. Keys are plain JSON values.
|
|
4
|
+
*/
|
|
5
|
+
export declare const venlyKeys: {
|
|
6
|
+
readonly all: readonly ["venly"];
|
|
7
|
+
readonly parties: (query?: unknown) => readonly ["venly", "parties", {} | null];
|
|
8
|
+
readonly party: (partyId: string) => readonly ["venly", "party", string];
|
|
9
|
+
readonly accounts: (query?: unknown) => readonly ["venly", "accounts", {} | null];
|
|
10
|
+
readonly account: (accountId: string) => readonly ["venly", "account", string];
|
|
11
|
+
readonly wallets: (accountId: string, query?: unknown) => readonly ["venly", "account", string, "wallets", {} | null];
|
|
12
|
+
readonly virtualBankAccounts: (accountId: string, query?: unknown) => readonly ["venly", "account", string, "virtual-bank-accounts", {} | null];
|
|
13
|
+
readonly transfers: (accountId: string, query?: unknown) => readonly ["venly", "account", string, "transfers", {} | null];
|
|
14
|
+
readonly transfer: (accountId: string, transferId: string) => readonly ["venly", "account", string, "transfer", string];
|
|
15
|
+
readonly rampRequests: (query?: unknown) => readonly ["venly", "ramp-requests", {} | null];
|
|
16
|
+
readonly rampRequest: (id: string) => readonly ["venly", "ramp-request", string];
|
|
17
|
+
readonly referenceData: () => readonly ["venly", "reference-data"];
|
|
18
|
+
readonly companyFees: () => readonly ["venly", "company-fees"];
|
|
19
|
+
readonly feeQuote: (input: unknown) => readonly ["venly", "fee-quote", unknown];
|
|
20
|
+
};
|
package/dist/keys.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query-key factory. Every hook and every cache invalidation goes through
|
|
3
|
+
* this object so keys can never drift apart. Keys are plain JSON values.
|
|
4
|
+
*/
|
|
5
|
+
export const venlyKeys = {
|
|
6
|
+
all: ["venly"],
|
|
7
|
+
parties: (query) => ["venly", "parties", query ?? null],
|
|
8
|
+
party: (partyId) => ["venly", "party", partyId],
|
|
9
|
+
accounts: (query) => ["venly", "accounts", query ?? null],
|
|
10
|
+
account: (accountId) => ["venly", "account", accountId],
|
|
11
|
+
wallets: (accountId, query) => ["venly", "account", accountId, "wallets", query ?? null],
|
|
12
|
+
virtualBankAccounts: (accountId, query) => ["venly", "account", accountId, "virtual-bank-accounts", query ?? null],
|
|
13
|
+
transfers: (accountId, query) => ["venly", "account", accountId, "transfers", query ?? null],
|
|
14
|
+
transfer: (accountId, transferId) => ["venly", "account", accountId, "transfer", transferId],
|
|
15
|
+
rampRequests: (query) => ["venly", "ramp-requests", query ?? null],
|
|
16
|
+
rampRequest: (id) => ["venly", "ramp-request", id],
|
|
17
|
+
referenceData: () => ["venly", "reference-data"],
|
|
18
|
+
companyFees: () => ["venly", "company-fees"],
|
|
19
|
+
feeQuote: (input) => ["venly", "fee-quote", input],
|
|
20
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { VenlyFinanceClient } from "@venlyfinance/sdk";
|
|
2
|
+
type CreateVibaBody = Parameters<VenlyFinanceClient["virtualBankAccounts"]["create"]>[1];
|
|
3
|
+
type CreatePaymentSessionBody = Parameters<VenlyFinanceClient["paymentSessions"]["create"]>[1];
|
|
4
|
+
/** Create a party (individual or organisation), then refresh party lists. */
|
|
5
|
+
export declare function useCreateParty(): import("@tanstack/react-query").UseMutationResult<{
|
|
6
|
+
id?: string;
|
|
7
|
+
externalId?: string;
|
|
8
|
+
partyType?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["PartyType"];
|
|
9
|
+
status?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["PartyStatus"];
|
|
10
|
+
firstName?: string;
|
|
11
|
+
lastName?: string;
|
|
12
|
+
kycStatus?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["KycStatus"];
|
|
13
|
+
name?: string;
|
|
14
|
+
vatNumber?: string;
|
|
15
|
+
kybStatus?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["KybStatus"];
|
|
16
|
+
address?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["Address"];
|
|
17
|
+
createdAt?: string;
|
|
18
|
+
updatedAt?: string;
|
|
19
|
+
version?: number;
|
|
20
|
+
}, Error, {
|
|
21
|
+
partyType: import("@venlyfinance/sdk").FinanceComponents["schemas"]["PartyType"];
|
|
22
|
+
externalId?: string;
|
|
23
|
+
firstName?: string;
|
|
24
|
+
lastName?: string;
|
|
25
|
+
name?: string;
|
|
26
|
+
vatNumber?: string;
|
|
27
|
+
address?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["Address"];
|
|
28
|
+
}, unknown>;
|
|
29
|
+
export declare function useCreateAccount(): import("@tanstack/react-query").UseMutationResult<{
|
|
30
|
+
id?: string;
|
|
31
|
+
externalId?: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
kycStatus?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["KycStatus"];
|
|
34
|
+
status?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["AccountStatus"];
|
|
35
|
+
createdAt?: string;
|
|
36
|
+
version?: number;
|
|
37
|
+
}, Error, {
|
|
38
|
+
externalId: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
chain: import("@venlyfinance/sdk").FinanceComponents["schemas"]["BlockchainNetwork"];
|
|
41
|
+
address?: string;
|
|
42
|
+
partyId?: string;
|
|
43
|
+
party?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["CreatePartyRequest"];
|
|
44
|
+
cardProviderReference?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["CardProviderReference"];
|
|
45
|
+
}, unknown>;
|
|
46
|
+
export declare function useCreateVirtualBankAccount(): import("@tanstack/react-query").UseMutationResult<{
|
|
47
|
+
id?: string;
|
|
48
|
+
accountId?: string;
|
|
49
|
+
bankAccountType?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["BankAccountType"];
|
|
50
|
+
name?: string;
|
|
51
|
+
status?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["VirtualBankAccountStatus"];
|
|
52
|
+
currency?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["FiatCurrency"];
|
|
53
|
+
targetCryptocurrency?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["Cryptocurrency"];
|
|
54
|
+
iban?: string;
|
|
55
|
+
bic?: string;
|
|
56
|
+
bankName?: string;
|
|
57
|
+
beneficiaryName?: string;
|
|
58
|
+
referenceCode?: string;
|
|
59
|
+
createdAt?: string;
|
|
60
|
+
updatedAt?: string;
|
|
61
|
+
}, Error, {
|
|
62
|
+
accountId: string;
|
|
63
|
+
body: CreateVibaBody;
|
|
64
|
+
}, unknown>;
|
|
65
|
+
export declare function useCreatePaymentSession(): import("@tanstack/react-query").UseMutationResult<{
|
|
66
|
+
id?: string;
|
|
67
|
+
accountId?: string;
|
|
68
|
+
status?: import("@venlyfinance/sdk").FinanceComponents["schemas"]["PaymentSessionStatus"];
|
|
69
|
+
inAmount?: number;
|
|
70
|
+
inCurrency?: string;
|
|
71
|
+
outCryptocurrency?: string;
|
|
72
|
+
paymentUrl?: string;
|
|
73
|
+
externalRef?: string;
|
|
74
|
+
walletId?: string;
|
|
75
|
+
blockchainTxId?: string | null;
|
|
76
|
+
cancellable?: boolean;
|
|
77
|
+
expiresAt?: string;
|
|
78
|
+
metadata?: {
|
|
79
|
+
[key: string]: string;
|
|
80
|
+
};
|
|
81
|
+
idempotencyKey?: string;
|
|
82
|
+
createdAt?: string;
|
|
83
|
+
updatedAt?: string;
|
|
84
|
+
}, Error, {
|
|
85
|
+
accountId: string;
|
|
86
|
+
body: CreatePaymentSessionBody;
|
|
87
|
+
}, unknown>;
|
|
88
|
+
export declare function useCreateRampRequest(): import("@tanstack/react-query").UseMutationResult<{
|
|
89
|
+
id?: string;
|
|
90
|
+
companyId?: string;
|
|
91
|
+
companyName?: string;
|
|
92
|
+
rampType?: "ON_RAMP" | "OFF_RAMP";
|
|
93
|
+
status?: "AWAITING_APPROVAL" | "AWAITING_FUNDS" | "PROCESSING" | "SUCCEEDED" | "FAILED" | "BLOCKED" | "DENIED" | "REJECTED" | "CANCELLED";
|
|
94
|
+
amount?: number;
|
|
95
|
+
netAmount?: number;
|
|
96
|
+
fiatAmount?: number;
|
|
97
|
+
fiatNetAmount?: number;
|
|
98
|
+
cryptoAmount?: number;
|
|
99
|
+
fiatFeeAmount?: number;
|
|
100
|
+
exchangeRate?: number;
|
|
101
|
+
feePercentage?: number;
|
|
102
|
+
paymentReference?: string;
|
|
103
|
+
paymentReceived?: boolean;
|
|
104
|
+
blockchainTransactionHash?: string;
|
|
105
|
+
createdAt?: string;
|
|
106
|
+
companyBankAccount?: import("@venlyfinance/sdk").FundflowComponents["schemas"]["EurSepaCompanyBankAccountDto"] | import("@venlyfinance/sdk").FundflowComponents["schemas"]["GbpChapsCompanyBankAccountDto"] | import("@venlyfinance/sdk").FundflowComponents["schemas"]["GbpFpsCompanyBankAccountDto"] | import("@venlyfinance/sdk").FundflowComponents["schemas"]["OtherCurrencySwiftCompanyBankAccountDto"] | import("@venlyfinance/sdk").FundflowComponents["schemas"]["UsAchCompanyBankAccountDto"] | import("@venlyfinance/sdk").FundflowComponents["schemas"]["UsSwiftCompanyBankAccountDto"] | import("@venlyfinance/sdk").FundflowComponents["schemas"]["UsWireCompanyBankAccountDto"];
|
|
107
|
+
companyWallet?: import("@venlyfinance/sdk").FundflowComponents["schemas"]["CompanyWalletDto"];
|
|
108
|
+
depositBankAccount?: import("@venlyfinance/sdk").FundflowComponents["schemas"]["EurSepaDepositBankAccountDto"] | import("@venlyfinance/sdk").FundflowComponents["schemas"]["GbpChapsDepositBankAccountDto"] | import("@venlyfinance/sdk").FundflowComponents["schemas"]["GbpFpsDepositBankAccountDto"] | import("@venlyfinance/sdk").FundflowComponents["schemas"]["OtherCurrencySwiftDepositBankAccountDto"] | import("@venlyfinance/sdk").FundflowComponents["schemas"]["UsAchDepositBankAccountDto"] | import("@venlyfinance/sdk").FundflowComponents["schemas"]["UsSwiftDepositBankAccountDto"] | import("@venlyfinance/sdk").FundflowComponents["schemas"]["UsWireDepositBankAccountDto"];
|
|
109
|
+
depositWallet?: import("@venlyfinance/sdk").FundflowComponents["schemas"]["DepositWalletDto"];
|
|
110
|
+
fiatCurrency?: import("@venlyfinance/sdk").FundflowComponents["schemas"]["FiatCurrencyDto"];
|
|
111
|
+
cryptoCurrency?: import("@venlyfinance/sdk").FundflowComponents["schemas"]["CryptoCurrencyDto"];
|
|
112
|
+
events?: import("@venlyfinance/sdk").FundflowComponents["schemas"]["RampRequestEventDto"][];
|
|
113
|
+
version?: number;
|
|
114
|
+
amountReceived?: number;
|
|
115
|
+
}, Error, {
|
|
116
|
+
rampType: "ON_RAMP" | "OFF_RAMP";
|
|
117
|
+
amount: number;
|
|
118
|
+
companyBankAccountId?: string;
|
|
119
|
+
companyWalletId?: string;
|
|
120
|
+
fiatCurrencyId: string;
|
|
121
|
+
cryptoCurrencyId: string;
|
|
122
|
+
}, unknown>;
|
|
123
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { useVenly } from "./provider.js";
|
|
3
|
+
import { venlyKeys } from "./keys.js";
|
|
4
|
+
/** Create a party (individual or organisation), then refresh party lists. */
|
|
5
|
+
export function useCreateParty() {
|
|
6
|
+
const { finance } = useVenly();
|
|
7
|
+
const queryClient = useQueryClient();
|
|
8
|
+
return useMutation({
|
|
9
|
+
mutationFn: (body) => finance.parties.create(body),
|
|
10
|
+
onSuccess: (party) => {
|
|
11
|
+
if (party.id)
|
|
12
|
+
queryClient.setQueryData(venlyKeys.party(party.id), party);
|
|
13
|
+
void queryClient.invalidateQueries({ queryKey: ["venly", "parties"] });
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function useCreateAccount() {
|
|
18
|
+
const { finance } = useVenly();
|
|
19
|
+
const queryClient = useQueryClient();
|
|
20
|
+
return useMutation({
|
|
21
|
+
mutationFn: (body) => finance.accounts.create(body),
|
|
22
|
+
onSuccess: (account) => {
|
|
23
|
+
if (account.id)
|
|
24
|
+
queryClient.setQueryData(venlyKeys.account(account.id), account);
|
|
25
|
+
void queryClient.invalidateQueries({ queryKey: ["venly", "accounts"] });
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export function useCreateVirtualBankAccount() {
|
|
30
|
+
const { finance } = useVenly();
|
|
31
|
+
const queryClient = useQueryClient();
|
|
32
|
+
return useMutation({
|
|
33
|
+
mutationFn: (input) => finance.virtualBankAccounts.create(input.accountId, input.body),
|
|
34
|
+
onSuccess: (_viba, input) => {
|
|
35
|
+
void queryClient.invalidateQueries({
|
|
36
|
+
queryKey: ["venly", "account", input.accountId, "virtual-bank-accounts"],
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function useCreatePaymentSession() {
|
|
42
|
+
const { finance } = useVenly();
|
|
43
|
+
return useMutation({
|
|
44
|
+
mutationFn: (input) => finance.paymentSessions.create(input.accountId, input.body),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export function useCreateRampRequest() {
|
|
48
|
+
const { fundflow } = useVenly();
|
|
49
|
+
const queryClient = useQueryClient();
|
|
50
|
+
return useMutation({
|
|
51
|
+
mutationFn: (body) => fundflow.rampRequests.create(body),
|
|
52
|
+
onSuccess: (request) => {
|
|
53
|
+
if (request.id)
|
|
54
|
+
queryClient.setQueryData(venlyKeys.rampRequest(request.id), request);
|
|
55
|
+
void queryClient.invalidateQueries({ queryKey: ["venly", "ramp-requests"] });
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
3
|
+
import { FundflowClient, VenlyFinanceClient, type FundflowClientOptions, type VenlyFinanceClientOptions } from "@venlyfinance/sdk";
|
|
4
|
+
/**
|
|
5
|
+
* Environments the provider accepts. "mock" constructs both clients with
|
|
6
|
+
* zero credentials and zero network (the SDK's stateful fixture store);
|
|
7
|
+
* "staging" and "production" require credentials or pre-built clients.
|
|
8
|
+
*/
|
|
9
|
+
export type VenlyReactEnvironment = "mock" | "staging" | "production";
|
|
10
|
+
/** The pair of configured API clients every hook resolves from context. */
|
|
11
|
+
export interface VenlyClients {
|
|
12
|
+
environment: VenlyReactEnvironment;
|
|
13
|
+
finance: VenlyFinanceClient;
|
|
14
|
+
fundflow: FundflowClient;
|
|
15
|
+
}
|
|
16
|
+
export interface VenlyProviderProps {
|
|
17
|
+
/**
|
|
18
|
+
* Default "mock": zero credentials, zero network, seeded fixtures. The
|
|
19
|
+
* same component tree flips to staging/production by changing this prop
|
|
20
|
+
* and supplying credentials server-side or a proxy.
|
|
21
|
+
*/
|
|
22
|
+
environment?: VenlyReactEnvironment;
|
|
23
|
+
/**
|
|
24
|
+
* OAuth2 client-credentials. SERVER-SIDE ONLY (React Server Components,
|
|
25
|
+
* route handlers, tests). The provider throws if a secret reaches a
|
|
26
|
+
* browser bundle outside mock mode: a leaked clientSecret is full API
|
|
27
|
+
* access. For browser apps use `proxyClientOptions()` and keep the
|
|
28
|
+
* credentials behind your own backend.
|
|
29
|
+
*/
|
|
30
|
+
clientId?: string;
|
|
31
|
+
clientSecret?: string;
|
|
32
|
+
/** Pre-built clients (win over environment/credentials/options). */
|
|
33
|
+
finance?: VenlyFinanceClient;
|
|
34
|
+
fundflow?: FundflowClient;
|
|
35
|
+
/** Full per-client options, e.g. from `proxyClientOptions()`. */
|
|
36
|
+
financeOptions?: VenlyFinanceClientOptions;
|
|
37
|
+
fundflowOptions?: FundflowClientOptions;
|
|
38
|
+
/**
|
|
39
|
+
* Reuse the app's QueryClient. When omitted a private one is created with
|
|
40
|
+
* retry disabled: the SDK already retries transient failures (429/5xx
|
|
41
|
+
* with backoff), and stacking a second retry layer multiplies latency.
|
|
42
|
+
*/
|
|
43
|
+
queryClient?: QueryClient;
|
|
44
|
+
children?: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Context provider for every hook in this package.
|
|
48
|
+
*
|
|
49
|
+
* ```tsx
|
|
50
|
+
* <VenlyProvider environment="mock">
|
|
51
|
+
* <App />
|
|
52
|
+
* </VenlyProvider>
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function VenlyProvider(props: VenlyProviderProps): import("react").FunctionComponentElement<import("@tanstack/react-query").QueryClientProviderProps>;
|
|
56
|
+
/** The configured clients. Throws outside a `<VenlyProvider>`. */
|
|
57
|
+
export declare function useVenly(): VenlyClients;
|
|
58
|
+
/**
|
|
59
|
+
* Mock controls (call log, failNext, lifecycle advancement). Defined only
|
|
60
|
+
* when the provider runs with environment "mock"; both fields are undefined
|
|
61
|
+
* otherwise, so demo/test affordances can never fire against live money.
|
|
62
|
+
*/
|
|
63
|
+
export declare function useVenlyMock(): {
|
|
64
|
+
finance: import("@venlyfinance/sdk").VenlyFinanceMock | undefined;
|
|
65
|
+
fundflow: import("@venlyfinance/sdk").VenlyMock | undefined;
|
|
66
|
+
};
|