@recoilpay/intent-core 0.1.0 → 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/README.md +42 -1
- package/dist/index.cjs +601 -15
- package/dist/index.d.cts +454 -29
- package/dist/index.d.ts +454 -29
- package/dist/index.js +580 -13
- package/package.json +6 -1
package/dist/index.d.cts
CHANGED
|
@@ -177,6 +177,106 @@ interface OrderResponse {
|
|
|
177
177
|
fillTransaction?: unknown;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/** The aggregator's fiat API, as it sends it. See design §12.1. */
|
|
181
|
+
type FiatDirection = 'onramp' | 'offramp';
|
|
182
|
+
type FiatTradeState = 'quoted' | 'awaiting_fiat' | 'awaiting_confirmation' | 'disputed' | 'settled_to_fiat_payer' | 'refunded_to_funder' | 'failed';
|
|
183
|
+
/** One fiat trade. Snake case, as `GET /api/v1/fiat/trades/{id}` returns it. */
|
|
184
|
+
interface FiatTrade {
|
|
185
|
+
id: string;
|
|
186
|
+
quote_id: string;
|
|
187
|
+
solver_id: string;
|
|
188
|
+
direction: FiatDirection;
|
|
189
|
+
/** CAIP-2, e.g. `eip155:84532`. */
|
|
190
|
+
chain: string;
|
|
191
|
+
asset: string;
|
|
192
|
+
asset_minor_units: string;
|
|
193
|
+
fiat_currency: string;
|
|
194
|
+
/** What reaches the payee's account, in minor units (kobo for NGN). */
|
|
195
|
+
fiat_minor_units: string;
|
|
196
|
+
rail: string;
|
|
197
|
+
rate: string;
|
|
198
|
+
fee_minor_units: string;
|
|
199
|
+
user_address: string;
|
|
200
|
+
solver_address: string;
|
|
201
|
+
state: FiatTradeState;
|
|
202
|
+
/** When the party the trade is waiting on runs out of time. */
|
|
203
|
+
deadline_at: string | null;
|
|
204
|
+
resolution_note: string | null;
|
|
205
|
+
payee_commitment: string | null;
|
|
206
|
+
payment_reference: string | null;
|
|
207
|
+
proof_kind: 'counterparty' | 'provider' | 'zktls' | null;
|
|
208
|
+
escrow_tx_hash: string | null;
|
|
209
|
+
release_tx_hash: string | null;
|
|
210
|
+
created_at: string;
|
|
211
|
+
updated_at: string;
|
|
212
|
+
}
|
|
213
|
+
/** `POST /api/v1/fiat/quotes`. */
|
|
214
|
+
interface RankFiatQuotesRequest {
|
|
215
|
+
direction: FiatDirection;
|
|
216
|
+
chain: string;
|
|
217
|
+
asset: string;
|
|
218
|
+
fiatCurrency: string;
|
|
219
|
+
rail: string;
|
|
220
|
+
/** Minor units of the asset, as a whole-number string. */
|
|
221
|
+
assetAmount: string;
|
|
222
|
+
}
|
|
223
|
+
/** One priced offer, best first. Amounts are minor-unit strings. */
|
|
224
|
+
interface RankedFiatQuote {
|
|
225
|
+
quoteId: string;
|
|
226
|
+
solverId: string;
|
|
227
|
+
rate: string;
|
|
228
|
+
assetAmount: string;
|
|
229
|
+
fiatCurrency: string;
|
|
230
|
+
rail: string;
|
|
231
|
+
fiatGross: string;
|
|
232
|
+
fiatFee: string;
|
|
233
|
+
/** What reaches the user's account. */
|
|
234
|
+
fiatNet: string;
|
|
235
|
+
paymentWindowSecs: number;
|
|
236
|
+
expiry: number;
|
|
237
|
+
score: number;
|
|
238
|
+
/** Seal the bank details to this before opening a trade. */
|
|
239
|
+
payeeKey: string;
|
|
240
|
+
}
|
|
241
|
+
interface RankFiatQuotesResponse {
|
|
242
|
+
quotes: RankedFiatQuote[];
|
|
243
|
+
totalConsidered: number;
|
|
244
|
+
}
|
|
245
|
+
/** What the user signs (Permit2 `PermitWitnessTransferFrom`) and the relay submits. */
|
|
246
|
+
interface FiatLockInstructions {
|
|
247
|
+
chainId: number;
|
|
248
|
+
escrow: string;
|
|
249
|
+
tradeId: string;
|
|
250
|
+
lockId: string;
|
|
251
|
+
token: string;
|
|
252
|
+
amount: string;
|
|
253
|
+
counterparty: string;
|
|
254
|
+
nonce: string;
|
|
255
|
+
deadline: number;
|
|
256
|
+
typedData: {
|
|
257
|
+
domain: Record<string, unknown>;
|
|
258
|
+
types: Record<string, ReadonlyArray<{
|
|
259
|
+
name: string;
|
|
260
|
+
type: string;
|
|
261
|
+
}>>;
|
|
262
|
+
primaryType: string;
|
|
263
|
+
message: Record<string, unknown>;
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
interface OpenFiatTradeRequest {
|
|
267
|
+
quoteId: string;
|
|
268
|
+
assetAmount: string;
|
|
269
|
+
userAddress: string;
|
|
270
|
+
payee: {
|
|
271
|
+
sealed: unknown;
|
|
272
|
+
commitment: string;
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
interface OpenFiatTradeResponse {
|
|
276
|
+
trade: FiatTrade;
|
|
277
|
+
lock: FiatLockInstructions;
|
|
278
|
+
}
|
|
279
|
+
|
|
180
280
|
/** Production aggregator. Override with `apiUrl` for staging or a local instance. */
|
|
181
281
|
declare const DEFAULT_API_URL = "https://api.recoilpay.com";
|
|
182
282
|
interface ApiOptions {
|
|
@@ -221,10 +321,22 @@ declare function createApiClient(options?: ApiOptions): {
|
|
|
221
321
|
getQuotes: (req: QuoteRequest) => Promise<QuotesResponse>;
|
|
222
322
|
submitOrder: (req: OrderRequest) => Promise<OrderResponse>;
|
|
223
323
|
getOrder: (id: string) => Promise<OrderResponse>;
|
|
324
|
+
rankFiatQuotes: (req: RankFiatQuotesRequest) => Promise<RankFiatQuotesResponse>;
|
|
325
|
+
openFiatTrade: (req: OpenFiatTradeRequest) => Promise<OpenFiatTradeResponse>;
|
|
326
|
+
getFiatTrade: (id: string) => Promise<FiatTrade>;
|
|
327
|
+
/** Hand the signed lock to the solver's relay, which submits it and pays the gas. */
|
|
328
|
+
submitFiatLock: (id: string, signature: string) => Promise<FiatTrade>;
|
|
329
|
+
/** Report a lock the user submitted themselves; the aggregator reads it from the chain. */
|
|
330
|
+
reportFiatFunded: (id: string, txHash?: string) => Promise<FiatTrade>;
|
|
331
|
+
/** `signature` is over `fiatConfirmMessage(id)`. */
|
|
332
|
+
confirmFiatTrade: (id: string, signature: string) => Promise<FiatTrade>;
|
|
333
|
+
/** `signature` is over `fiatDisputeMessage(id, reason)`. */
|
|
334
|
+
disputeFiatTrade: (id: string, reason: string, signature: string) => Promise<FiatTrade>;
|
|
224
335
|
};
|
|
225
336
|
|
|
226
337
|
/** Intent parser types. See docs/intent-parser-design.md. */
|
|
227
|
-
|
|
338
|
+
/** `offramp` cashes crypto out to a bank account (fiat ramps, design §5.1). */
|
|
339
|
+
type IntentAction = 'swap' | 'send' | 'offramp';
|
|
228
340
|
type AmountKind = 'token' | 'usd';
|
|
229
341
|
/** Output of the grammar parser, before resolution. Raw alias strings as typed. */
|
|
230
342
|
interface RawIntent {
|
|
@@ -236,6 +348,10 @@ interface RawIntent {
|
|
|
236
348
|
tokenOut: string | null;
|
|
237
349
|
chainOut: string | null;
|
|
238
350
|
recipient: string | null;
|
|
351
|
+
/** offramp only: the currency to receive, as typed ("naira", "NGN"). Bank
|
|
352
|
+
* details are never parsed: they are collected in a form, so they never
|
|
353
|
+
* leave the browser unsealed. */
|
|
354
|
+
fiatCurrency?: string | null;
|
|
239
355
|
}
|
|
240
356
|
type ParseResult = {
|
|
241
357
|
ok: true;
|
|
@@ -245,7 +361,7 @@ type ParseResult = {
|
|
|
245
361
|
offTemplate: true;
|
|
246
362
|
message: string;
|
|
247
363
|
};
|
|
248
|
-
type IssueField = 'amount' | 'tokenIn' | 'chainIn' | 'tokenOut' | 'chainOut' | 'recipient';
|
|
364
|
+
type IssueField = 'amount' | 'tokenIn' | 'chainIn' | 'tokenOut' | 'chainOut' | 'recipient' | 'fiatCurrency';
|
|
249
365
|
type IssueKind = 'missing' | 'unknown' | 'unsupported';
|
|
250
366
|
interface ValidationIssue {
|
|
251
367
|
field: IssueField;
|
|
@@ -265,7 +381,16 @@ interface ResolvedIntent {
|
|
|
265
381
|
recipient: string;
|
|
266
382
|
}
|
|
267
383
|
|
|
268
|
-
declare const TEMPLATE_HINT = "Try: swap <amount> <token> on <chain> for <token> on <chain> [to <address>] (e.g. swap 10 USDC on Base for ETH on Arbitrum) OR send <amount> <token> on <chain> to <address>";
|
|
384
|
+
declare const TEMPLATE_HINT = "Try: swap <amount> <token> on <chain> for <token> on <chain> [to <address>] (e.g. swap 10 USDC on Base for ETH on Arbitrum) OR send <amount> <token> on <chain> to <address> OR cash out <amount> <token> on <chain> to <currency> (e.g. cash out 50 USDC on Base to NGN)";
|
|
385
|
+
/** Shown instead of parsing when the text looks like it holds bank details. */
|
|
386
|
+
declare const BANK_DETAILS_HINT = "Leave bank details out of the sentence \u2014 you'll enter them privately on the next step. Try: cash out 50 USDC on Base to NGN";
|
|
387
|
+
/**
|
|
388
|
+
* Parsing sends the text to a third party (Hugging Face), so bank details
|
|
389
|
+
* must never be in it. An account number is a long run of digits: a NUBAN
|
|
390
|
+
* is ten, an IBAN's tail more. Hex addresses are skipped, and amounts this
|
|
391
|
+
* long do not occur in practice.
|
|
392
|
+
*/
|
|
393
|
+
declare function looksLikeBankDetails(text: string): boolean;
|
|
269
394
|
/** Hugging Face's OpenAI-compatible router: the endpoint @huggingface/inference's chatCompletion uses. */
|
|
270
395
|
declare const HF_ROUTER_URL = "https://router.huggingface.co/v1/chat/completions";
|
|
271
396
|
declare const DEFAULT_INTENT_MODEL = "Qwen/Qwen2.5-Coder-32B-Instruct";
|
|
@@ -370,6 +495,11 @@ interface IntentWallet {
|
|
|
370
495
|
to: Address;
|
|
371
496
|
value: bigint;
|
|
372
497
|
}): Promise<Hex>;
|
|
498
|
+
/**
|
|
499
|
+
* EIP-191 `personal_sign` of a plain message. Only fiat trades need it (to
|
|
500
|
+
* confirm or dispute), so a wallet used for swaps alone may omit it.
|
|
501
|
+
*/
|
|
502
|
+
signMessage?(message: string): Promise<Hex>;
|
|
373
503
|
}
|
|
374
504
|
/** Adapt a viem `WalletClient` (with an account attached) to `IntentWallet`. */
|
|
375
505
|
declare function viemWallet(client: WalletClient): IntentWallet;
|
|
@@ -382,6 +512,89 @@ declare function stripDomainType(types: Record<string, ReadonlyArray<{
|
|
|
382
512
|
type: string;
|
|
383
513
|
}>>;
|
|
384
514
|
|
|
515
|
+
/**
|
|
516
|
+
* The registry has two layers (see docs/intent-parser-design.md §2):
|
|
517
|
+
* - alias layer (static): maps loose user words → canonical token symbol / chain.
|
|
518
|
+
* - support layer (dynamic): what solvers can actually fill, from the live aggregator.
|
|
519
|
+
*
|
|
520
|
+
* A word can be a token OR a chain depending on slot (e.g. "eth", "sol"); the grammar
|
|
521
|
+
* decides which table to consult by position, so the same word appears in both.
|
|
522
|
+
*/
|
|
523
|
+
interface CanonicalChain {
|
|
524
|
+
id: number;
|
|
525
|
+
name: string;
|
|
526
|
+
}
|
|
527
|
+
declare const TOKEN_ALIASES: Record<string, string>;
|
|
528
|
+
declare const CHAIN_ALIASES: Record<string, CanonicalChain>;
|
|
529
|
+
declare function normalizeToken(raw: string | null): string | null;
|
|
530
|
+
declare function normalizeChain(raw: string | null): CanonicalChain | null;
|
|
531
|
+
interface SupportedSet {
|
|
532
|
+
chains: Set<number>;
|
|
533
|
+
assets: Map<string, SolverAsset>;
|
|
534
|
+
}
|
|
535
|
+
declare const EMPTY_SUPPORTED: SupportedSet;
|
|
536
|
+
declare function buildSupportedSet(assets: SolverAsset[]): SupportedSet;
|
|
537
|
+
declare function findAsset(set: SupportedSet, chainId: number, symbol: string): SolverAsset | undefined;
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* The fiat corridors this kit knows how to collect payee details for.
|
|
541
|
+
*
|
|
542
|
+
* A corridor is a currency, the rail payouts go over, and how its banks are
|
|
543
|
+
* named. Bank codes matter beyond display: the payee commitment covers the
|
|
544
|
+
* bank code, and a Tier 1 proof recomputes it from the code the payment
|
|
545
|
+
* provider reports. So the codes here must be the ones the solvers' provider
|
|
546
|
+
* uses — Paystack's, for the Phase 1 pilot.
|
|
547
|
+
*/
|
|
548
|
+
interface FiatBank {
|
|
549
|
+
code: string;
|
|
550
|
+
name: string;
|
|
551
|
+
}
|
|
552
|
+
interface FiatCorridor {
|
|
553
|
+
currency: string;
|
|
554
|
+
rail: string;
|
|
555
|
+
/** Minor units per major unit, as a power of ten (kobo: 2). */
|
|
556
|
+
decimals: number;
|
|
557
|
+
symbol: string;
|
|
558
|
+
/** What the account number is called, for a form label. */
|
|
559
|
+
accountLabel: string;
|
|
560
|
+
/** Checks shape only; the solver checks the account exists. */
|
|
561
|
+
accountPattern: RegExp;
|
|
562
|
+
banks: FiatBank[];
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Nigerian banks with Paystack's codes (CBN codes for commercial banks,
|
|
566
|
+
* Paystack's own for fintechs). Not exhaustive. Checked against Paystack's
|
|
567
|
+
* `GET /bank?country=nigeria` on 2026-10-05; re-check when adding banks or a
|
|
568
|
+
* provider: a wrong code here makes a correct payout fail its commitment
|
|
569
|
+
* check.
|
|
570
|
+
*/
|
|
571
|
+
declare const NG_BANKS: FiatBank[];
|
|
572
|
+
declare const FIAT_CORRIDORS: Record<string, FiatCorridor>;
|
|
573
|
+
declare const FIAT_CURRENCY_ALIASES: Record<string, string>;
|
|
574
|
+
declare function normalizeFiatCurrency(raw: string | null | undefined): string | null;
|
|
575
|
+
/** Minor units → "151,543.85". */
|
|
576
|
+
declare function formatFiat(minor: string | bigint, corridor: Pick<FiatCorridor, 'decimals'>): string;
|
|
577
|
+
|
|
578
|
+
/** An off-ramp, resolved: everything needed to ask for fiat quotes. */
|
|
579
|
+
interface OfframpRequest {
|
|
580
|
+
chainId: number;
|
|
581
|
+
chainName: string;
|
|
582
|
+
/** Symbol, as fiat quotes name assets (`USDC`). */
|
|
583
|
+
asset: string;
|
|
584
|
+
token: string;
|
|
585
|
+
decimals: number;
|
|
586
|
+
/** Base units of `asset`. */
|
|
587
|
+
amount: bigint;
|
|
588
|
+
corridor: FiatCorridor;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Validate a parsed `offramp` intent. Like `validateIntent`, it collects
|
|
592
|
+
* every problem at once.
|
|
593
|
+
*/
|
|
594
|
+
declare function validateOfframp(raw: RawIntent, supported: SupportedSet): ValidationIssue[];
|
|
595
|
+
/** PRECONDITION: `validateOfframp(raw, supported)` returned no issues. */
|
|
596
|
+
declare function resolveOfframp(raw: RawIntent, supported: SupportedSet): OfframpRequest;
|
|
597
|
+
|
|
385
598
|
/**
|
|
386
599
|
* Where the flow is.
|
|
387
600
|
*
|
|
@@ -395,9 +608,12 @@ declare function stripDomainType(types: Record<string, ReadonlyArray<{
|
|
|
395
608
|
* switchingChain / approving / signing / submitting confirm() progress
|
|
396
609
|
* tracking order submitted, polling its status
|
|
397
610
|
* done every intent reached a terminal status
|
|
611
|
+
* offramp a cash-out to a bank: `offramp` is resolved; hand it to
|
|
612
|
+
* an off-ramp session (`createOfframpSession`), which
|
|
613
|
+
* collects the bank details and runs the trade
|
|
398
614
|
* error something threw; see `error`
|
|
399
615
|
*/
|
|
400
|
-
type IntentPhase = 'idle' | 'parsing' | 'offTemplate' | 'invalid' | 'needsWallet' | 'quoting' | 'quoted' | 'switchingChain' | 'approving' | 'signing' | 'submitting' | 'tracking' | 'done' | 'error';
|
|
616
|
+
type IntentPhase = 'idle' | 'parsing' | 'offTemplate' | 'invalid' | 'needsWallet' | 'quoting' | 'quoted' | 'switchingChain' | 'approving' | 'signing' | 'submitting' | 'tracking' | 'done' | 'offramp' | 'error';
|
|
401
617
|
/** What a confirm card shows: human amounts and names, no addresses or base units. */
|
|
402
618
|
interface IntentPreview {
|
|
403
619
|
action: 'swap' | 'send';
|
|
@@ -442,6 +658,8 @@ interface IntentState {
|
|
|
442
658
|
/** Multi-intent sentences run one after another. */
|
|
443
659
|
queueIndex: number;
|
|
444
660
|
queueTotal: number;
|
|
661
|
+
/** Phase `offramp` only. */
|
|
662
|
+
offramp: OfframpRequest | null;
|
|
445
663
|
}
|
|
446
664
|
interface IntentSession {
|
|
447
665
|
getState(): IntentState;
|
|
@@ -484,30 +702,6 @@ declare function isTerminalStatus(status: OrderStatus): boolean;
|
|
|
484
702
|
*/
|
|
485
703
|
declare function createIntentSession(options?: SessionOptions): IntentSession;
|
|
486
704
|
|
|
487
|
-
/**
|
|
488
|
-
* The registry has two layers (see docs/intent-parser-design.md §2):
|
|
489
|
-
* - alias layer (static): maps loose user words → canonical token symbol / chain.
|
|
490
|
-
* - support layer (dynamic): what solvers can actually fill, from the live aggregator.
|
|
491
|
-
*
|
|
492
|
-
* A word can be a token OR a chain depending on slot (e.g. "eth", "sol"); the grammar
|
|
493
|
-
* decides which table to consult by position, so the same word appears in both.
|
|
494
|
-
*/
|
|
495
|
-
interface CanonicalChain {
|
|
496
|
-
id: number;
|
|
497
|
-
name: string;
|
|
498
|
-
}
|
|
499
|
-
declare const TOKEN_ALIASES: Record<string, string>;
|
|
500
|
-
declare const CHAIN_ALIASES: Record<string, CanonicalChain>;
|
|
501
|
-
declare function normalizeToken(raw: string | null): string | null;
|
|
502
|
-
declare function normalizeChain(raw: string | null): CanonicalChain | null;
|
|
503
|
-
interface SupportedSet {
|
|
504
|
-
chains: Set<number>;
|
|
505
|
-
assets: Map<string, SolverAsset>;
|
|
506
|
-
}
|
|
507
|
-
declare const EMPTY_SUPPORTED: SupportedSet;
|
|
508
|
-
declare function buildSupportedSet(assets: SolverAsset[]): SupportedSet;
|
|
509
|
-
declare function findAsset(set: SupportedSet, chainId: number, symbol: string): SolverAsset | undefined;
|
|
510
|
-
|
|
511
705
|
/**
|
|
512
706
|
* Validate a parsed intent against the live support set. Collects EVERY problem
|
|
513
707
|
* (missing / unknown / unsupported) so the user can fix them in one edit.
|
|
@@ -620,4 +814,235 @@ declare function buildPermit2ApproveRequest(token: Address, amount?: bigint): {
|
|
|
620
814
|
args: readonly [`0x${string}`, bigint];
|
|
621
815
|
};
|
|
622
816
|
|
|
623
|
-
|
|
817
|
+
/**
|
|
818
|
+
* Payee bank details for a fiat off-ramp: canonical form, commitment, and
|
|
819
|
+
* sealing to the matched solver.
|
|
820
|
+
*
|
|
821
|
+
* Bank details are personal data. They should reach exactly one party — the
|
|
822
|
+
* solver who pays out — and never a chain, a log, or the aggregator's
|
|
823
|
+
* database in readable form. So the client:
|
|
824
|
+
*
|
|
825
|
+
* 1. normalises the account identity (`rail`, `bank_code`, `account`) into
|
|
826
|
+
* one canonical byte string;
|
|
827
|
+
* 2. commits to it as `keccak256(canonical ‖ salt)` with a fresh 32-byte
|
|
828
|
+
* salt — the aggregator stores only this;
|
|
829
|
+
* 3. seals the identity, the account holder's name and the salt to the
|
|
830
|
+
* solver's key, in the same envelope gift cards use.
|
|
831
|
+
*
|
|
832
|
+
* # Why the name is not in the commitment
|
|
833
|
+
*
|
|
834
|
+
* A Tier 1 proof is a provider's payout event, and the aggregator checks it
|
|
835
|
+
* by recomputing the commitment from the account the event names. Providers
|
|
836
|
+
* report account numbers and bank codes reliably but names in their own
|
|
837
|
+
* format ("OSAHON GINO" for "Gino Osahon"), so a commitment over the name
|
|
838
|
+
* would reject honest payouts. The name is sealed for the solver's name
|
|
839
|
+
* check, which is where it is actually used.
|
|
840
|
+
*
|
|
841
|
+
* # Why the commitment is salted
|
|
842
|
+
*
|
|
843
|
+
* An account number is short: a Nigerian NUBAN is ten digits with a check
|
|
844
|
+
* digit, about a billion per bank. Unsalted, anyone holding the database
|
|
845
|
+
* could hash every one in minutes and read every payee's account back out of
|
|
846
|
+
* its commitment. The salt travels inside the envelope, so the solver can
|
|
847
|
+
* attach it to a proof and the user can reveal it in a dispute.
|
|
848
|
+
*
|
|
849
|
+
* # Canonical form
|
|
850
|
+
*
|
|
851
|
+
* Each identity field is normalised and then restricted to a plain character
|
|
852
|
+
* set, so the canonical JSON needs no escaping and is byte-identical in every
|
|
853
|
+
* language. The Rust side (`oif_types::fiat_trades::payee`) implements the
|
|
854
|
+
* same rules, and both are tested against `test-vectors/fiat-payee.json` at
|
|
855
|
+
* the repository root.
|
|
856
|
+
*/
|
|
857
|
+
|
|
858
|
+
/** Payee details as a user enters them. */
|
|
859
|
+
interface FiatPayee {
|
|
860
|
+
/** Stable `country.rail` id, e.g. `ng.nip`, `eu.sepa_instant`, `ke.mpesa`. */
|
|
861
|
+
rail: string;
|
|
862
|
+
/** Bank or institution code, e.g. a NIP bank code or a BIC. Empty for rails
|
|
863
|
+
* that address an account directly, such as mobile money. */
|
|
864
|
+
bankCode: string;
|
|
865
|
+
/** Account number, IBAN, or mobile money number. */
|
|
866
|
+
account: string;
|
|
867
|
+
/** The account holder's name, for the solver's name check. */
|
|
868
|
+
name: string;
|
|
869
|
+
}
|
|
870
|
+
/** The fields that identify where the money goes, normalised. */
|
|
871
|
+
interface PayeeIdentity {
|
|
872
|
+
rail: string;
|
|
873
|
+
bank_code: string;
|
|
874
|
+
account: string;
|
|
875
|
+
}
|
|
876
|
+
/** The sealed envelope. Same shape as the aggregator's `SealedCode`. */
|
|
877
|
+
interface SealedPayee {
|
|
878
|
+
v: 1;
|
|
879
|
+
alg: 'ECIES-secp256k1-AES256GCM';
|
|
880
|
+
epk: Hex;
|
|
881
|
+
iv: Hex;
|
|
882
|
+
ct: Hex;
|
|
883
|
+
tag: Hex;
|
|
884
|
+
}
|
|
885
|
+
/** What the solver recovers on opening an envelope. */
|
|
886
|
+
interface OpenedPayee {
|
|
887
|
+
identity: PayeeIdentity;
|
|
888
|
+
name: string;
|
|
889
|
+
salt: Hex;
|
|
890
|
+
}
|
|
891
|
+
/** Normalise and validate the account identity. Throws on anything that
|
|
892
|
+
* would not be safe to commit to.
|
|
893
|
+
*
|
|
894
|
+
* - ASCII whitespace (space, tab, CR, LF) is removed from every field, and
|
|
895
|
+
* `-` from the account — so an IBAN written in groups of four and the
|
|
896
|
+
* same IBAN written solid are one account.
|
|
897
|
+
* - The character set is checked *before* case is changed, and only ASCII
|
|
898
|
+
* is accepted. Unicode case mapping would otherwise turn `ß` into `SS`
|
|
899
|
+
* or the Kelvin sign into `k`, quietly making a different account; and
|
|
900
|
+
* "whitespace" means different characters in different languages, so
|
|
901
|
+
* only the ASCII set is stripped and anything else is rejected. */
|
|
902
|
+
declare function payeeIdentity(payee: Pick<FiatPayee, 'rail' | 'bankCode' | 'account'>): PayeeIdentity;
|
|
903
|
+
/** The canonical bytes, as a string: compact JSON with keys in sorted
|
|
904
|
+
* order. The character sets enforced by [`payeeIdentity`] mean no value
|
|
905
|
+
* ever needs escaping. */
|
|
906
|
+
declare function canonicalPayeeIdentity(identity: PayeeIdentity): string;
|
|
907
|
+
/** `keccak256(canonical ‖ salt)`, `0x`-hex — what the aggregator stores. */
|
|
908
|
+
declare function payeeCommitment(identity: PayeeIdentity, salt: Hex): Hex;
|
|
909
|
+
/** Seal `payee` to the matched solver's encryption key.
|
|
910
|
+
*
|
|
911
|
+
* Returns the envelope and commitment to send to the aggregator, and the
|
|
912
|
+
* salt for the user to keep: in a dispute, the details plus the salt are
|
|
913
|
+
* what prove which account the solver was told to pay.
|
|
914
|
+
*
|
|
915
|
+
* @param solverPubkey The solver's 65-byte uncompressed secp256k1 key.
|
|
916
|
+
* @param salt Only for test vectors. Omit it: a fresh random salt is what
|
|
917
|
+
* makes the commitment unguessable. */
|
|
918
|
+
declare function sealPayee(payee: FiatPayee, solverPubkey: Hex, salt?: Hex): {
|
|
919
|
+
sealed: SealedPayee;
|
|
920
|
+
commitment: Hex;
|
|
921
|
+
salt: Hex;
|
|
922
|
+
};
|
|
923
|
+
/** Open an envelope as the solver, and check it against the trade's
|
|
924
|
+
* commitment.
|
|
925
|
+
*
|
|
926
|
+
* `expectedCommitment` is required, not optional: a solver must never pay
|
|
927
|
+
* out to details that do not match what the aggregator recorded. Otherwise
|
|
928
|
+
* a user could seal one account, commit to another, get paid, and then
|
|
929
|
+
* dispute the payout as going to "the wrong account".
|
|
930
|
+
*
|
|
931
|
+
* Throws if the envelope was tampered with, was sealed to another key, or
|
|
932
|
+
* does not match the commitment. */
|
|
933
|
+
declare function openPayee(sealed: SealedPayee, solverPrivkey: Hex, expectedCommitment: Hex): OpenedPayee;
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Where an off-ramp is.
|
|
937
|
+
*
|
|
938
|
+
* idle nothing asked yet
|
|
939
|
+
* needsWallet ready to quote once a wallet is set
|
|
940
|
+
* quoting / quoted ranking solvers' offers; `preview` is ready
|
|
941
|
+
* switchingChain / approving / opening / signing / locking confirm() progress
|
|
942
|
+
* awaitingFiat the USDC is locked; the solver is paying out
|
|
943
|
+
* awaitingConfirmation the solver says it paid: the user checks their bank
|
|
944
|
+
* and confirms or disputes. Nothing releases the
|
|
945
|
+
* escrow until they do (design §6.1)
|
|
946
|
+
* confirming / disputing signing and sending that answer
|
|
947
|
+
* disputed an admin decides; nobody's clock runs
|
|
948
|
+
* settled the solver was paid out of escrow (the user has the fiat)
|
|
949
|
+
* refunded the USDC went back to the user
|
|
950
|
+
* failed the lock never landed; nothing moved
|
|
951
|
+
* error something threw before a trade existed; see `error`
|
|
952
|
+
*/
|
|
953
|
+
type OfframpPhase = 'idle' | 'needsWallet' | 'quoting' | 'quoted' | 'switchingChain' | 'approving' | 'opening' | 'signing' | 'locking' | 'awaitingFiat' | 'awaitingConfirmation' | 'confirming' | 'disputing' | 'disputed' | 'settled' | 'refunded' | 'failed' | 'error';
|
|
954
|
+
/** A confirm card's worth: human amounts, no base units. */
|
|
955
|
+
interface OfframpPreview {
|
|
956
|
+
payAmount: string;
|
|
957
|
+
paySymbol: string;
|
|
958
|
+
chainName: string;
|
|
959
|
+
/** What reaches the bank account. */
|
|
960
|
+
receiveAmount: string;
|
|
961
|
+
currency: string;
|
|
962
|
+
currencySymbol: string;
|
|
963
|
+
fee: string;
|
|
964
|
+
/** Fiat per one unit of the asset. */
|
|
965
|
+
rate: string;
|
|
966
|
+
/** How long the solver has to pay once the USDC is locked. */
|
|
967
|
+
paymentWindowSecs: number;
|
|
968
|
+
quotesConsidered: number;
|
|
969
|
+
}
|
|
970
|
+
interface OfframpState {
|
|
971
|
+
phase: OfframpPhase;
|
|
972
|
+
request: OfframpRequest | null;
|
|
973
|
+
quote: RankedFiatQuote | null;
|
|
974
|
+
preview: OfframpPreview | null;
|
|
975
|
+
/** A Permit2 approval of the asset comes first. */
|
|
976
|
+
needsApproval: boolean;
|
|
977
|
+
trade: FiatTrade | null;
|
|
978
|
+
/**
|
|
979
|
+
* The salt the bank details were committed with. With the details, it
|
|
980
|
+
* proves in a dispute which account the solver was told to pay, so offer
|
|
981
|
+
* it to the user to keep. Only known in the session that opened the trade.
|
|
982
|
+
*/
|
|
983
|
+
payeeSalt: Hex | null;
|
|
984
|
+
/** The last failure. During a trade, the phase still follows the trade. */
|
|
985
|
+
error: string | null;
|
|
986
|
+
}
|
|
987
|
+
interface OfframpSession {
|
|
988
|
+
getState(): OfframpState;
|
|
989
|
+
subscribe(listener: (state: OfframpState) => void): () => void;
|
|
990
|
+
/** Rank solvers' offers for `request`. */
|
|
991
|
+
quote(request: OfframpRequest): Promise<void>;
|
|
992
|
+
/** Seal `payee` to the chosen solver, open the trade, and lock the asset. */
|
|
993
|
+
confirm(payee: FiatPayee): Promise<void>;
|
|
994
|
+
/** The fiat arrived: release the escrow to the solver. */
|
|
995
|
+
confirmReceived(): Promise<void>;
|
|
996
|
+
/** The fiat did not arrive (or not in full): an admin decides. */
|
|
997
|
+
dispute(reason: string): Promise<void>;
|
|
998
|
+
/** Pick a trade back up by id, e.g. after a reload, and track it. */
|
|
999
|
+
resume(tradeId: string): Promise<void>;
|
|
1000
|
+
reset(): void;
|
|
1001
|
+
setWallet(wallet: IntentWallet | null): void;
|
|
1002
|
+
destroy(): void;
|
|
1003
|
+
}
|
|
1004
|
+
interface OfframpOptions extends ApiOptions {
|
|
1005
|
+
api?: ApiClient;
|
|
1006
|
+
wallet?: IntentWallet | null;
|
|
1007
|
+
/** Trade-status polling interval. Default 3000ms. */
|
|
1008
|
+
pollIntervalMs?: number;
|
|
1009
|
+
/** Chain reads (Permit2 allowance, receipts). Defaults to the aggregator's RPCs. */
|
|
1010
|
+
chainReader?: (chainId: number) => Promise<ChainReader | null>;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Before the user signs, check that what the aggregator asks them to sign
|
|
1014
|
+
* is the trade they chose: this asset and amount, from this chain, to the
|
|
1015
|
+
* escrow, with the quoted solver as counterparty, for the quoted payout. A
|
|
1016
|
+
* signature is a spend authorisation, so it is never given on trust.
|
|
1017
|
+
*/
|
|
1018
|
+
declare function checkLock(lock: FiatLockInstructions, trade: FiatTrade, request: OfframpRequest, quote: RankedFiatQuote, user: string): void;
|
|
1019
|
+
/**
|
|
1020
|
+
* Cash crypto out to a bank account, as a framework-free store (design §5.1):
|
|
1021
|
+
* quote → (approve) → open → sign the lock → the solver's relay locks it →
|
|
1022
|
+
* the solver pays → the user confirms or disputes.
|
|
1023
|
+
*
|
|
1024
|
+
* The bank details are sealed in the browser to the chosen solver's key;
|
|
1025
|
+
* the aggregator sees only the envelope and a salted commitment.
|
|
1026
|
+
*/
|
|
1027
|
+
declare function createOfframpSession(options?: OfframpOptions): OfframpSession;
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* What a user signs (EIP-191 `personal_sign`) to act on a fiat trade.
|
|
1031
|
+
*
|
|
1032
|
+
* One message per action, each naming the trade, so a signature is good for
|
|
1033
|
+
* exactly one action on one trade: a signature given to dispute can never be
|
|
1034
|
+
* replayed to confirm, which would release the escrow. These must match
|
|
1035
|
+
* `oif_types::fiat_trades::actions` byte for byte; both are tested against
|
|
1036
|
+
* `test-vectors/fiat-actions.json` at the repository root.
|
|
1037
|
+
*
|
|
1038
|
+
* The aggregator accepts only printable ASCII for reasons and references,
|
|
1039
|
+
* so trimming here and there cannot disagree.
|
|
1040
|
+
*/
|
|
1041
|
+
/** The fiat receiver confirms the payment arrived. Releases the escrow to the payer. */
|
|
1042
|
+
declare function fiatConfirmMessage(tradeId: string): string;
|
|
1043
|
+
/** The fiat receiver contests the payer's claim. The reason is signed too. */
|
|
1044
|
+
declare function fiatDisputeMessage(tradeId: string, reason: string): string;
|
|
1045
|
+
/** A user who pays fiat (on-ramp) says it is sent, with the transfer's reference. */
|
|
1046
|
+
declare function fiatPaymentSentMessage(tradeId: string, reference: string): string;
|
|
1047
|
+
|
|
1048
|
+
export { type AggregationMetadata, type AmountKind, type ApiClient, ApiError, type ApiOptions, type AssetAmount, type AssetLockReference, BANK_DETAILS_HINT, CHAIN_ALIASES, type CanonicalChain, type ChainInfo, type ChainReader, DEFAULT_API_URL, DEFAULT_INTENT_MODEL, EMPTY_SUPPORTED, FIAT_CORRIDORS, FIAT_CURRENCY_ALIASES, type FiatBank, type FiatCorridor, type FiatDirection, type FiatLockInstructions, type FiatPayee, type FiatTrade, type FiatTradeState, HF_ROUTER_URL, type Input, type IntentAction, type IntentPhase, type IntentPreview, type IntentRequest, type IntentSession, type IntentState, type IntentWallet, type InteropAddress, type IssueField, type IssueKind, MAX_UINT256, NG_BANKS, type OfframpOptions, type OfframpPhase, type OfframpPreview, type OfframpRequest, type OfframpSession, type OfframpState, type OpenFiatTradeRequest, type OpenFiatTradeResponse, type OpenedPayee, type Order, type OrderPayload, type OrderRequest, type OrderResponse, type OrderStatus, type OriginSubmission, type Output, PERMIT2_ADDRESS, type ParseResult, type ParserOptions, type PayeeIdentity, type Quote, type QuotePreview, type QuoteRequest, type QuotesResponse, type RankFiatQuotesRequest, type RankFiatQuotesResponse, type RankedFiatQuote, type RawIntent, type ResolvedIntent, SOLANA_CHAIN_IDS, type SealedPayee, type SessionOptions, type Settlement, type Solver, type SolverAsset, type SolverOptions, type SolverSupportedAssets, type SolversResponse, type SupportedSet, TEMPLATE_HINT, TOKEN_ALIASES, type TypedDataSigner, type ValidationIssue, buildOrderRequest, buildPermit2ApproveRequest, buildQuoteRequest, buildSupportedSet, canonicalPayeeIdentity, chainName, checkLock, createApiClient, createIntentSession, createOfframpSession, explorerUrl, extractIntents, fiatConfirmMessage, fiatDisputeMessage, fiatPaymentSentMessage, findAsset, formatFiat, getPermit2Allowance, hasPermit2Allowance, interopAddress, isTerminalStatus, looksLikeBankDetails, normalizeChain, normalizeFiatCurrency, normalizeToken, openPayee, parseIntent, payeeCommitment, payeeIdentity, resolveIntent, resolveOfframp, rpcReaders, sealPayee, signQuote, stripDomainType, validateIntent, validateOfframp, viemWallet };
|