@mandateops/audit 0.0.0-stage → 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.
@@ -0,0 +1,188 @@
1
+ import { undocumented } from "./common.js";
2
+ /** Rails on which a destination is an EVM address. */
3
+ const BRIDGE_EVM_RAILS = [
4
+ "arbitrum",
5
+ "avalanche_c_chain",
6
+ "base",
7
+ "celo",
8
+ "ethereum",
9
+ "linea",
10
+ "optimism",
11
+ "polygon",
12
+ "tempo",
13
+ "xdc",
14
+ ];
15
+ /** Bridge's crypto rails; which of them carry USDC is unverified. */
16
+ const BRIDGE_RAILS = [...BRIDGE_EVM_RAILS, "solana", "stellar", "tron"];
17
+ /**
18
+ * Circle's native USDC contract on each EVM rail, from
19
+ * https://developers.circle.com/stablecoins/usdc-contract-addresses.
20
+ */
21
+ const USDC_CONTRACTS = {
22
+ "usdc@arbitrum": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
23
+ "usdc@avalanche_c_chain": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
24
+ "usdc@base": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
25
+ "usdc@celo": "0xcebA9300f2b948710d2653dD7B07f33A8B32118C",
26
+ "usdc@ethereum": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
27
+ "usdc@linea": "0x176211869cA2b568f2A7D4EE941E073a821EE1ff",
28
+ "usdc@optimism": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
29
+ "usdc@polygon": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
30
+ };
31
+ /**
32
+ * `POST /v0/transfers`, USDC out on the rail the request names. `amount` is in
33
+ * the source currency if fiat, else the fiat it pegs, so a USDC source reads at
34
+ * USD scale while staying its own term; `destination.amount` is in the destination currency, with the term
35
+ * written `usdc-from-eur` so what funds it stays bound, and giving both is
36
+ * refused. The source currencies listed are unverified. A new transfer
37
+ * starts `awaiting_funds`; later states are not imitated. Bridge replays the
38
+ * original for a key for 24 hours and errors on a changed body; that error's
39
+ * status and body are unverified, and 400 is the listed status that fits.
40
+ * `client_reference_id` is optional, so the payment then takes the key.
41
+ */
42
+ export const bridge = (terms) => ({
43
+ path: "/v0/transfers",
44
+ credential: { header: "api-key", prefix: "" },
45
+ key: { header: "idempotency-key" },
46
+ fields: {
47
+ payment: "client_reference_id",
48
+ account: "on_behalf_of",
49
+ amount: "amount",
50
+ currency: "source.currency",
51
+ asset: "destination.currency",
52
+ network: "destination.payment_rail",
53
+ destination: "destination.to_address",
54
+ },
55
+ alternateAmount: {
56
+ path: "destination.amount",
57
+ currency: "destination.currency",
58
+ },
59
+ pegged: { currency: { usdc: "usd" } },
60
+ fixed: terms,
61
+ amount: {
62
+ scaleTerm: "currency",
63
+ scales: { usd: 2, eur: 2, gbp: 2, mxn: 2, usdc: 6 },
64
+ networks: { usdc: BRIDGE_RAILS },
65
+ contracts: USDC_CONTRACTS,
66
+ evmNetworks: BRIDGE_EVM_RAILS,
67
+ },
68
+ replies: {
69
+ paid: {
70
+ status: 201,
71
+ body: {
72
+ id: "{{effectId}}",
73
+ client_reference_id: "{{payment}}",
74
+ amount: "{{amountMajor}}",
75
+ state: "awaiting_funds",
76
+ destination: {
77
+ currency: "{{asset}}",
78
+ payment_rail: "{{network}}",
79
+ to_address: "{{destination}}",
80
+ },
81
+ },
82
+ },
83
+ declined: undocumented(400),
84
+ conflict: undocumented(400),
85
+ },
86
+ });
87
+ /**
88
+ * `POST /v1/businessAccount/transfers`, USDC only, written as currency `USD`.
89
+ * The destination is a registered address id, whose chain Circle looks up, so
90
+ * asset and network are declared. `source.id` names the paying wallet;
91
+ * `account` is what its absence means. The reply's `{ data }` wrapper is
92
+ * documented, and a failure is a 201 with `status: failed`. The key must be a
93
+ * UUID v4. The repeat-key reply, behaviour on a changed body and the scale of
94
+ * six are unverified.
95
+ */
96
+ export const circleMint = ({ account, ...terms }) => ({
97
+ path: "/v1/businessAccount/transfers",
98
+ key: { field: "idempotencyKey", shape: "uuid_v4" },
99
+ fields: {
100
+ account: "source.id",
101
+ amount: "amount.amount",
102
+ currency: "amount.currency",
103
+ destination: "destination.addressId",
104
+ },
105
+ defaults: { account },
106
+ fixed: terms,
107
+ amount: { scaleTerm: "currency", scales: { USD: 6 } },
108
+ replies: {
109
+ paid: {
110
+ status: 201,
111
+ body: {
112
+ data: {
113
+ id: "{{effectId}}",
114
+ status: "pending",
115
+ amount: { amount: "{{amountMajor}}", currency: "{{currency}}" },
116
+ },
117
+ },
118
+ },
119
+ declined: {
120
+ status: 201,
121
+ body: {
122
+ data: {
123
+ id: "{{payment}}",
124
+ status: "failed",
125
+ errorCode: "insufficient_funds",
126
+ amount: { amount: "{{amountMajor}}", currency: "{{currency}}" },
127
+ },
128
+ },
129
+ },
130
+ conflict: undocumented(409),
131
+ },
132
+ });
133
+ /** The lab's own code for a reused id; Fireblocks' is unverified. */
134
+ const DUPLICATE_ID_CODE = 1438;
135
+ /** Lab ids for the two assets, each on the network it implies. */
136
+ const FIREBLOCKS_NETWORKS = { USDC: ["ethereum"], ETH: ["ethereum"] };
137
+ /**
138
+ * `POST /v1/transactions`, USDC and ETH to a one-time address from a vault,
139
+ * signed per request. Fireblocks keys transactions on `externalTxId` and
140
+ * refuses a reused one with 400, so a retrying client must read that as already
141
+ * sent. A refused id stays refused. The network is declared and must be the one
142
+ * the asset implies; only the assets listed are supported. A
143
+ * reply of `SUBMITTED` is not completion. A create answers 200. Failure bodies
144
+ * follow the documented `{ message, code }`; their messages are unverified.
145
+ * Sources disagree on whether `externalTxId` is mandatory: the official SDK
146
+ * marks it optional, so a call without one has no idempotency and is its own
147
+ * payment. It is at most 255 characters.
148
+ */
149
+ export const fireblocks = (terms) => {
150
+ const duplicate = {
151
+ status: 400,
152
+ body: { message: "External tx id already used", code: DUPLICATE_ID_CODE },
153
+ };
154
+ return {
155
+ path: "/v1/transactions",
156
+ credential: { signedJwt: true },
157
+ key: { field: "externalTxId", optional: true, maxLength: 255 },
158
+ fields: {
159
+ account: "source.id",
160
+ amount: "amount",
161
+ currency: "assetId",
162
+ asset: "assetId",
163
+ destination: "destination.oneTimeAddress.address",
164
+ },
165
+ fixed: terms,
166
+ amount: {
167
+ scaleTerm: "asset",
168
+ scales: { USDC: 6, ETH: 18 },
169
+ networks: FIREBLOCKS_NETWORKS,
170
+ contracts: { "USDC@ethereum": USDC_CONTRACTS["usdc@ethereum"] },
171
+ evmNetworks: ["ethereum"],
172
+ },
173
+ replay: "duplicate_reply",
174
+ refusals: "final",
175
+ replies: {
176
+ paid: {
177
+ status: 200,
178
+ body: { id: "{{effectId}}", status: "SUBMITTED", systemMessages: [] },
179
+ },
180
+ declined: {
181
+ status: 400,
182
+ body: { message: "Transaction refused", code: 0 },
183
+ },
184
+ conflict: duplicate,
185
+ duplicate,
186
+ },
187
+ };
188
+ };
@@ -0,0 +1,106 @@
1
+ import type { CheckoutTerms, LedgerTerms, MollieTerms, ProviderWire, SourcedTerms, StagedWire, TermDefaults, WiseRoutes, WiseTerms } from "./types.js";
2
+ /**
3
+ * `POST /transfer`, from Paystack's published OpenAPI description. The amount
4
+ * is in kobo or pesewas, and a reference is lowercase alphanumeric with `-` and
5
+ * `_`. Reuse of a reference is unverified: its docs both reject one and advise
6
+ * retrying with it. `currency` is optional there, defaulting to NGN.
7
+ */
8
+ export declare const paystack: (terms: LedgerTerms) => ProviderWire;
9
+ /** `POST /v1/payouts`. A request without `destination` is refused here. */
10
+ export declare const stripePayouts: (terms: LedgerTerms) => ProviderWire;
11
+ /** `POST /v1/transfers`, to a connected account. */
12
+ export declare const stripeTransfers: (terms: LedgerTerms) => ProviderWire;
13
+ /**
14
+ * `POST /v3/transfers`. Keyed on the unique `reference`, as v3 coverage of
15
+ * `X-Idempotency-Key` and v3 reference reuse are unverified. The amount is a
16
+ * required integer whose unit is not stated, though samples look major; it is
17
+ * read as given. A synchronous refusal is unverified. The destination is
18
+ * `bank_account`, so a run authorises the two joined.
19
+ */
20
+ export declare const flutterwave: (terms: LedgerTerms) => ProviderWire;
21
+ /**
22
+ * `POST /transfers` of the Transfers API, to a transfer instrument. A refusal
23
+ * is a 202, as the published specification shows, and 422 is its validation
24
+ * status. A processed retry gets the first response; a reused key with a
25
+ * different body is unverified.
26
+ */
27
+ export declare const adyen: (terms: SourcedTerms) => ProviderWire;
28
+ /**
29
+ * `POST /transfers` between entities; the sample names no currency. Idempotency
30
+ * is documented for this route: only 2xx replies are cached, and a concurrent
31
+ * repeat gets 409. The field names come from non-canonical sources, and the
32
+ * amount's unit, the credential scheme and all failures are unverified.
33
+ */
34
+ export declare const checkout: (terms: CheckoutTerms) => ProviderWire;
35
+ /**
36
+ * `POST /v1/payments/payouts`, one item per batch. An email receiver is not an
37
+ * identifier the lab can hold. A repeated batch id is rejected with a link to
38
+ * the original batch, within 30 days; that status is unverified. Insufficient
39
+ * funds is documented per item, so a synchronous refusal is unverified.
40
+ */
41
+ export declare const paypal: (terms: LedgerTerms) => ProviderWire;
42
+ /**
43
+ * `POST /v1/payouts`. A repeated key returns the first response; a changed body
44
+ * is unverified. Low balance is a payout state, so a refusal is a reply whose
45
+ * status says so, and every HTTP status here is unverified.
46
+ */
47
+ export declare const razorpayx: (terms: SourcedTerms) => ProviderWire;
48
+ /**
49
+ * `POST /v2/payouts`, to the balance's configured bank account. `balanceId` is
50
+ * optional; `account` is what its absence means. Omitting `amount` pays out the
51
+ * whole balance, which the suite states as `wholeBalance`. The reply's
52
+ * `statusReason` is left out, as its codes are unverified.
53
+ */
54
+ export declare const molliePayouts: ({ account, wholeBalance, ...terms }: MollieTerms) => ProviderWire;
55
+ /**
56
+ * `POST /api/merchants/{merchantId}/payouts`, one payout per request. Failures
57
+ * are documented as `{ title, message, code }`; which code fits each refusal is
58
+ * unverified, so their bodies stay empty. The merchant is the account, and the
59
+ * destination is `branchCode--accountNumber`. A payout without `payoutId` has no
60
+ * idempotency, so each such request is its own payment.
61
+ */
62
+ export declare const peach: (terms: SourcedTerms) => ProviderWire;
63
+ /**
64
+ * `POST /v2/disbursements`, ZAR only. The amount is read as decimal, and a
65
+ * repeated nonce gets 409. Insufficient funds is documented as a later `error`
66
+ * status, so a refusal is a reply in that status. The destination is
67
+ * `bankId--accountNumber`.
68
+ */
69
+ export declare const stitch: (terms: LedgerTerms) => ProviderWire;
70
+ /**
71
+ * `POST /pay`, from `account_id` to a counterparty's account, per Revolut's
72
+ * OpenAPI description. A reused `request_id` makes no second transaction and
73
+ * answers the documented 400 for two weeks. The id is at most 40 characters.
74
+ */
75
+ export declare const revolut: (terms: SourcedTerms) => ProviderWire;
76
+ /**
77
+ * `POST /api/payment_orders`. A key is at most 180 characters, and only
78
+ * successes are cached, with a concurrent repeat getting 409. The reply holds a
79
+ * few of the object's fields; its status, a changed body and the failures are
80
+ * unverified. `currency` is optional
81
+ * there, taken from the originating account, so pass it in `defaults`.
82
+ */
83
+ export declare const modernTreasury: (terms: SourcedTerms, defaults?: TermDefaults) => ProviderWire;
84
+ /**
85
+ * `POST /api/v1/transfers/create`. The amount is a string in the schema and a
86
+ * number in the example; either is read, as decimal, which is unverified, as
87
+ * is the `transfer_amount` name. Both failure codes are from Airwallex's
88
+ * transfer error list.
89
+ */
90
+ export declare const airwallex: (terms: LedgerTerms) => ProviderWire;
91
+ /**
92
+ * `POST /transfers`, answered by a `Location`, relative so a client following
93
+ * it stays in the lab. A repeated key gets 201 with the same `Location`; a 409
94
+ * there means the original is still processing, so it is not used for a changed
95
+ * body, whose status is unverified, as are failure bodies.
96
+ */
97
+ export declare const dwolla: (terms: SourcedTerms) => ProviderWire;
98
+ /**
99
+ * Quote, transfer, then fund, from Wise's API reference. Only the fund call
100
+ * moves money. The account is the quote's profile, the currency its source and
101
+ * the asset its target; `customerTransactionId` is the key, and a repeat makes
102
+ * no duplicate, though its reply body and quote reuse are unverified. Wise also
103
+ * serves dated bases such as `/2026Q4`, so a client may override the routes.
104
+ * Recipient creation is not imitated.
105
+ */
106
+ export declare const wise: (terms: WiseTerms, routes?: WiseRoutes) => StagedWire;