@loopprotocol/sdk-byoaa 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/index.d.mts +1046 -0
- package/dist/index.d.ts +1046 -0
- package/dist/index.js +1127 -0
- package/dist/index.mjs +1068 -0
- package/package.json +65 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
import { PublicKey, Commitment, ConnectionConfig, Connection, TransactionSignature, AccountInfo } from '@solana/web3.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Core BYOAA types + helpers.
|
|
5
|
+
*
|
|
6
|
+
* Spec reference: docs/roadmap/08-byoaa-sdk.md § "SDK surface"
|
|
7
|
+
*
|
|
8
|
+
* `BankReceipt` is the in-memory shape an attested agent constructs from a
|
|
9
|
+
* single bank transaction. Helpers normalize the fields so the same
|
|
10
|
+
* underlying transaction collides on the same on-chain PDA across re-fetches.
|
|
11
|
+
*/
|
|
12
|
+
/** Receipt payload carried by an attested receipt proof. */
|
|
13
|
+
type ReceiptPayload = BankReceipt;
|
|
14
|
+
/** Stable proof discriminants for the BYOAA receipt wire contract. */
|
|
15
|
+
type ReceiptProofType = "cose_sign1_x509" | "risc0_v1" | "groth16_alt_bn128";
|
|
16
|
+
/** Today's implemented proof arm: COSE_Sign1 signature plus X.509 chain. */
|
|
17
|
+
interface CoseSign1X509Proof {
|
|
18
|
+
proof_type: "cose_sign1_x509";
|
|
19
|
+
signature: Uint8Array;
|
|
20
|
+
cert_chain: Uint8Array[];
|
|
21
|
+
}
|
|
22
|
+
/** Reserved v1 ZK arm. The verifier dispatch is present; implementation throws today. */
|
|
23
|
+
interface Risc0V1Proof {
|
|
24
|
+
proof_type: "risc0_v1";
|
|
25
|
+
receipt: Uint8Array;
|
|
26
|
+
image_id: Uint8Array;
|
|
27
|
+
}
|
|
28
|
+
/** Reserved compact on-chain proof arm. The verifier dispatch is present; implementation throws today. */
|
|
29
|
+
interface Groth16AltBn128Proof {
|
|
30
|
+
proof_type: "groth16_alt_bn128";
|
|
31
|
+
proof: Uint8Array;
|
|
32
|
+
public_inputs: Uint8Array;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Wire shape for receipts whose payload proof may evolve without retargeting
|
|
36
|
+
* the published SDK contract. New proof systems append new proof_type arms;
|
|
37
|
+
* existing arms must not change meaning.
|
|
38
|
+
*/
|
|
39
|
+
interface AttestedReceipt {
|
|
40
|
+
payload: ReceiptPayload;
|
|
41
|
+
proof: CoseSign1X509Proof | Risc0V1Proof | Groth16AltBn128Proof;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* One bank transaction, as constructed inside an attested agent and submitted
|
|
45
|
+
* to `loop-shopping::submit_attested_receipt`.
|
|
46
|
+
*
|
|
47
|
+
* All fields except `mcc` and `accountLast4` are required. The on-chain
|
|
48
|
+
* handler will reject submissions where `amountCents == 0`, where
|
|
49
|
+
* `merchantNameRaw` is non-ASCII or > 64 bytes, or where `postedAt` is
|
|
50
|
+
* outside the [now - 365d, now] window.
|
|
51
|
+
*/
|
|
52
|
+
interface BankReceipt {
|
|
53
|
+
/**
|
|
54
|
+
* 32-byte stable hash of the transaction. Derived from the deterministic
|
|
55
|
+
* normalize-then-hash recipe in `deriveBankTxnId()`. The PDA seeds
|
|
56
|
+
* `[b"attested_receipt", vault, bank_txn_id]` mean two submissions of the
|
|
57
|
+
* same underlying transaction collide on the same account, so the second
|
|
58
|
+
* `init` call fails with `AccountAlreadyInUse` — natural double-submit
|
|
59
|
+
* protection without per-vault dedupe state.
|
|
60
|
+
*/
|
|
61
|
+
bankTxnId: Uint8Array;
|
|
62
|
+
/**
|
|
63
|
+
* Merchant name as it appeared on the bank statement, normalized via
|
|
64
|
+
* `normalizeMerchantName()` (uppercase, whitespace-collapsed). Must be
|
|
65
|
+
* ASCII and ≤ 64 bytes. The on-chain account stores this raw; off-chain
|
|
66
|
+
* matchers normalize further when joining to merchant claim names.
|
|
67
|
+
*/
|
|
68
|
+
merchantNameRaw: string;
|
|
69
|
+
/** MCC (merchant category code) if the bank exposed it. 0 = unknown. */
|
|
70
|
+
mcc: number;
|
|
71
|
+
/** Transaction amount in cents. Must be > 0; capped server-side. */
|
|
72
|
+
amountCents: bigint;
|
|
73
|
+
/**
|
|
74
|
+
* ISO-8601 posted date as Unix seconds at midnight UTC. The on-chain
|
|
75
|
+
* handler enforces a [now - 365d, now] window.
|
|
76
|
+
*/
|
|
77
|
+
postedAt: bigint;
|
|
78
|
+
/**
|
|
79
|
+
* Last 4 of the funding card/account, if the bank exposed it. 0 = unknown.
|
|
80
|
+
* Used by the off-chain matcher to disambiguate a user's connected
|
|
81
|
+
* accounts when there are multiple.
|
|
82
|
+
*/
|
|
83
|
+
accountLast4: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Maximum receipt amount in cents enforced by the on-chain handler. Mirrors
|
|
87
|
+
* `MAX_RECEIPT_CENTS` in `programs/loop-shopping/src/lib.rs`. Clients may
|
|
88
|
+
* front-run this check to surface a clearer error.
|
|
89
|
+
*/
|
|
90
|
+
declare const MAX_RECEIPT_CENTS: bigint;
|
|
91
|
+
/**
|
|
92
|
+
* Maximum merchant_name_raw length in bytes enforced on-chain. Mirrors
|
|
93
|
+
* `MAX_MERCHANT_NAME_RAW_LEN`.
|
|
94
|
+
*/
|
|
95
|
+
declare const MAX_MERCHANT_NAME_RAW_LEN = 64;
|
|
96
|
+
/**
|
|
97
|
+
* Maximum age of a receipt's `postedAt` enforced on-chain (365 days).
|
|
98
|
+
*/
|
|
99
|
+
declare const MAX_RECEIPT_AGE_SECONDS: bigint;
|
|
100
|
+
/**
|
|
101
|
+
* Normalize a bank-statement merchant name to the on-chain canonical form:
|
|
102
|
+
*
|
|
103
|
+
* 1. Trim leading/trailing whitespace.
|
|
104
|
+
* 2. Collapse internal whitespace runs to a single space.
|
|
105
|
+
* 3. Uppercase.
|
|
106
|
+
* 4. Strip any non-ASCII characters (accents, currency symbols, etc.).
|
|
107
|
+
*
|
|
108
|
+
* The result is what the on-chain handler stores and what off-chain
|
|
109
|
+
* matchers compare against `MerchantClaimableNames` entries.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* normalizeMerchantName(" starbucks #12345 SEATTLE WA ")
|
|
113
|
+
* // → "STARBUCKS #12345 SEATTLE WA"
|
|
114
|
+
*
|
|
115
|
+
* normalizeMerchantName("Café Du Monde — New Orleans")
|
|
116
|
+
* // → "CAF DU MONDE NEW ORLEANS" (accents + em-dash stripped; the
|
|
117
|
+
* // whitespace they left collapses)
|
|
118
|
+
*/
|
|
119
|
+
declare function normalizeMerchantName(raw: string): string;
|
|
120
|
+
/**
|
|
121
|
+
* Derive a stable 32-byte transaction id from the normalized fields of a
|
|
122
|
+
* bank transaction. The same underlying transaction across re-fetches MUST
|
|
123
|
+
* produce the same id, so:
|
|
124
|
+
*
|
|
125
|
+
* - merchant name is run through `normalizeMerchantName` first
|
|
126
|
+
* - amount is taken in cents (no float rounding)
|
|
127
|
+
* - posted date is taken at midnight UTC seconds (caller is responsible
|
|
128
|
+
* for snapping; we don't truncate here)
|
|
129
|
+
* - account_last4 is included so two different cards at the same merchant
|
|
130
|
+
* for the same amount on the same day produce different ids
|
|
131
|
+
*
|
|
132
|
+
* Hashing is sha256. Output is exactly 32 bytes.
|
|
133
|
+
*/
|
|
134
|
+
declare function deriveBankTxnId(input: {
|
|
135
|
+
merchantNameRaw: string;
|
|
136
|
+
amountCents: bigint;
|
|
137
|
+
postedAt: bigint;
|
|
138
|
+
accountLast4: number;
|
|
139
|
+
}): Uint8Array;
|
|
140
|
+
/**
|
|
141
|
+
* Validation result returned from `validateReceipt()`. `ok: true` means the
|
|
142
|
+
* receipt should pass the on-chain handler's pre-flight checks; `ok: false`
|
|
143
|
+
* surfaces the specific error so callers can present a clear message before
|
|
144
|
+
* paying RPC + rent.
|
|
145
|
+
*/
|
|
146
|
+
type ReceiptValidationResult = {
|
|
147
|
+
ok: true;
|
|
148
|
+
} | {
|
|
149
|
+
ok: false;
|
|
150
|
+
error: ReceiptValidationError;
|
|
151
|
+
};
|
|
152
|
+
type ReceiptValidationError = "merchant_name_empty" | "merchant_name_too_long" | "merchant_name_not_ascii" | "amount_zero" | "amount_too_large" | "posted_at_in_future" | "posted_at_too_old" | "bank_txn_id_wrong_length";
|
|
153
|
+
/**
|
|
154
|
+
* Validate a receipt against the same bounds the on-chain handler enforces.
|
|
155
|
+
* Cheap, no I/O. Run this before `AttestedReceiptSubmitter.submit()` to
|
|
156
|
+
* fail fast without paying network fees or enclave round-trips.
|
|
157
|
+
*
|
|
158
|
+
* `nowSeconds` defaults to `Math.floor(Date.now() / 1000)`. Callers
|
|
159
|
+
* targeting a specific clock (e.g. cluster slot time) can override.
|
|
160
|
+
*/
|
|
161
|
+
declare function validateReceipt(receipt: BankReceipt, nowSeconds?: bigint): ReceiptValidationResult;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Network-switchable configuration for the BYOAA SDK.
|
|
165
|
+
*
|
|
166
|
+
* Mirrors the pattern in `@loopprotocol/sdk` `src/network.ts` so that
|
|
167
|
+
* flipping a deployment between devnet and mainnet is a single config
|
|
168
|
+
* change, never a code change. Same hot-swap discipline as the on-chain
|
|
169
|
+
* programs (per-network `declare_id`, per-network
|
|
170
|
+
* `LOOP_VAULT_PROGRAM_ID_BYTES`).
|
|
171
|
+
*
|
|
172
|
+
* The BYOAA SDK only needs three things to talk to the chain:
|
|
173
|
+
* 1. The Solana RPC connection
|
|
174
|
+
* 2. The loop-shopping program id (carries `submit_attested_receipt`)
|
|
175
|
+
* 3. The loop-vault program id (used for the `session.owner` cross-program
|
|
176
|
+
* check; the shopping handler does the same check in Rust)
|
|
177
|
+
*
|
|
178
|
+
* Everything else is derived from these. Constructors accept either a
|
|
179
|
+
* `Network` literal (the convenience path) or a fully-resolved
|
|
180
|
+
* `LoopByoaaConfig` (the override path — RPC URL, custom program ids for
|
|
181
|
+
* a sandbox or a forked cluster).
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
type Network = "devnet" | "mainnet-beta" | "localnet";
|
|
185
|
+
/**
|
|
186
|
+
* Devnet program ids. Authoritative for BYOAA work today — all 6 anchor
|
|
187
|
+
* programs are deployed and the shopping binary on devnet contains
|
|
188
|
+
* `submit_attested_receipt` as of `81aa56e` (truthful 4/4 NO-SKIPS rehearsal).
|
|
189
|
+
*
|
|
190
|
+
* Pulled into this module rather than imported from the main SDK so that
|
|
191
|
+
* the SDK can ship without forcing a parent-SDK version match on every
|
|
192
|
+
* BYOAA point release. When the parent SDK rotates ids, bump here to match.
|
|
193
|
+
*/
|
|
194
|
+
declare const DEVNET_PROGRAM_IDS: {
|
|
195
|
+
readonly SHOPPING: PublicKey;
|
|
196
|
+
readonly VAULT: PublicKey;
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Mainnet program ids. Placeholder until Bundle 2 deploys (loop-shopping
|
|
200
|
+
* program upgrade carrying spec 08 + 08a + 08b + 09). At that point this
|
|
201
|
+
* map flips to the live mainnet ids and consumers re-deploy with
|
|
202
|
+
* `network: "mainnet-beta"`.
|
|
203
|
+
*
|
|
204
|
+
* The on-chain BYOAA surface is **not on mainnet today** — the deployed
|
|
205
|
+
* binary at `b407bae` covers specs 01-05 only. Don't switch consumers to
|
|
206
|
+
* `mainnet-beta` until the upgrade lands AND the mainnet ShoppingState
|
|
207
|
+
* bump=0 corruption is repaired (Burt P0 brief 2026-05-03).
|
|
208
|
+
*/
|
|
209
|
+
declare const MAINNET_PROGRAM_IDS: {
|
|
210
|
+
readonly SHOPPING: PublicKey;
|
|
211
|
+
readonly VAULT: PublicKey;
|
|
212
|
+
};
|
|
213
|
+
interface LoopByoaaConfig {
|
|
214
|
+
/** Which Solana cluster the BYOAA SDK targets. */
|
|
215
|
+
network: Network;
|
|
216
|
+
/** RPC endpoint. Defaults to the public cluster RPC for `network`. */
|
|
217
|
+
rpcUrl?: string;
|
|
218
|
+
/**
|
|
219
|
+
* Override the loop-shopping program id. Useful for sandbox forks where
|
|
220
|
+
* you've redeployed the program at a custom address (see
|
|
221
|
+
* `scripts/sandbox/`).
|
|
222
|
+
*/
|
|
223
|
+
shoppingProgramId?: PublicKey;
|
|
224
|
+
/** Override the loop-vault program id. Same use-case as above. */
|
|
225
|
+
vaultProgramId?: PublicKey;
|
|
226
|
+
/**
|
|
227
|
+
* Commitment level for confirmations + reads. Defaults to "confirmed".
|
|
228
|
+
* BYOAA receipts are fire-and-forget once confirmed; "finalized" only
|
|
229
|
+
* matters if you need rollback resistance against deep reorgs.
|
|
230
|
+
*/
|
|
231
|
+
commitment?: Commitment;
|
|
232
|
+
/** Extra connection config (forwarded to web3.js Connection). */
|
|
233
|
+
connectionConfig?: ConnectionConfig;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Resolved internal shape — everything is a concrete value, never undefined.
|
|
237
|
+
* Internal modules use this; public APIs accept the looser
|
|
238
|
+
* `LoopByoaaConfig` and resolve via `resolveConfig`.
|
|
239
|
+
*/
|
|
240
|
+
interface ResolvedConfig {
|
|
241
|
+
network: Network;
|
|
242
|
+
connection: Connection;
|
|
243
|
+
shoppingProgramId: PublicKey;
|
|
244
|
+
vaultProgramId: PublicKey;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Resolve a public `LoopByoaaConfig` (or just a network literal) into a
|
|
248
|
+
* fully-populated internal config. Pure: no I/O, no side effects.
|
|
249
|
+
*/
|
|
250
|
+
declare function resolveConfig(input: LoopByoaaConfig | Network): ResolvedConfig;
|
|
251
|
+
/**
|
|
252
|
+
* Look up the canonical program ids for a given network. Localnet reuses
|
|
253
|
+
* the devnet ids by convention — sandbox forks should override via
|
|
254
|
+
* `LoopByoaaConfig.shoppingProgramId` / `vaultProgramId` instead of
|
|
255
|
+
* adding a new network case.
|
|
256
|
+
*/
|
|
257
|
+
declare function getProgramIds(network: Network): {
|
|
258
|
+
readonly SHOPPING: PublicKey;
|
|
259
|
+
readonly VAULT: PublicKey;
|
|
260
|
+
};
|
|
261
|
+
/** Default public RPC for each cluster. */
|
|
262
|
+
declare function defaultRpcUrl(network: Network): string;
|
|
263
|
+
/**
|
|
264
|
+
* True iff this network is mainnet. Used by callers to gate "real-money"
|
|
265
|
+
* UX (extra confirmation modals, larger fonts on amounts, etc.).
|
|
266
|
+
*/
|
|
267
|
+
declare function isMainnet(network: Network): boolean;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* PDA derivations for BYOAA accounts.
|
|
271
|
+
*
|
|
272
|
+
* Authoritative seeds live in `programs/loop-shopping/src/lib.rs` next to
|
|
273
|
+
* the `BankAttestedReceipt` struct. Mirror them here exactly — drift would
|
|
274
|
+
* silently corrupt receipt addresses.
|
|
275
|
+
*/
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* `BankAttestedReceipt` PDA.
|
|
279
|
+
*
|
|
280
|
+
* Seeds: `[b"attested_receipt", vault.key().as_ref(), bank_txn_id.as_ref()]`
|
|
281
|
+
*
|
|
282
|
+
* Per-vault-per-txn uniqueness — same bank transaction can't be submitted
|
|
283
|
+
* twice for the same user (the second `init` collides with the first
|
|
284
|
+
* account and fails).
|
|
285
|
+
*
|
|
286
|
+
* @returns the receipt PDA + bump seed
|
|
287
|
+
*/
|
|
288
|
+
declare function deriveBankAttestedReceiptPda(args: {
|
|
289
|
+
shoppingProgramId: PublicKey;
|
|
290
|
+
vault: PublicKey;
|
|
291
|
+
bankTxnId: Uint8Array;
|
|
292
|
+
}): {
|
|
293
|
+
pda: PublicKey;
|
|
294
|
+
bump: number;
|
|
295
|
+
};
|
|
296
|
+
/**
|
|
297
|
+
* `ShoppingState` PDA (loop-shopping).
|
|
298
|
+
*
|
|
299
|
+
* Seeds: `[b"state"]`
|
|
300
|
+
*
|
|
301
|
+
* Global state for the loop-shopping program. `submit_attested_receipt`
|
|
302
|
+
* takes this as a read-only input to gate on `is_paused`.
|
|
303
|
+
*/
|
|
304
|
+
declare function deriveShoppingStatePda(args: {
|
|
305
|
+
shoppingProgramId: PublicKey;
|
|
306
|
+
}): {
|
|
307
|
+
pda: PublicKey;
|
|
308
|
+
bump: number;
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* `AgentSessionRegistration` PDA (loop-vault).
|
|
312
|
+
*
|
|
313
|
+
* Seeds: `[b"agent_session", vault.key().as_ref(), session_pubkey.as_ref()]`
|
|
314
|
+
*
|
|
315
|
+
* Mirrored from spec 07a so SDK consumers can resolve the session account
|
|
316
|
+
* without a separate import from `@loopprotocol/sdk`.
|
|
317
|
+
*/
|
|
318
|
+
declare function deriveAgentSessionPda(args: {
|
|
319
|
+
vaultProgramId: PublicKey;
|
|
320
|
+
vault: PublicKey;
|
|
321
|
+
sessionPubkey: PublicKey;
|
|
322
|
+
}): {
|
|
323
|
+
pda: PublicKey;
|
|
324
|
+
bump: number;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Hand-rolled Anchor wire encoding for the BYOAA instructions.
|
|
329
|
+
*
|
|
330
|
+
* We intentionally do NOT depend on the loop-shopping IDL artifact:
|
|
331
|
+
*
|
|
332
|
+
* 1. The IDL goes stale every time the on-chain program upgrades, and
|
|
333
|
+
* sdk-byoaa needs to ship + npm-publish independently of the
|
|
334
|
+
* anchor-build cycle.
|
|
335
|
+
* 2. Anchor's BorshCoder pulls in @coral-xyz/anchor as a hard dep with a
|
|
336
|
+
* large transitive footprint. sdk-byoaa stays lean.
|
|
337
|
+
* 3. The SDK only constructs ONE instruction (`submit_attested_receipt`).
|
|
338
|
+
* Hand-rolling that one is ~30 lines of Borsh; cheaper than the IDL
|
|
339
|
+
* machinery.
|
|
340
|
+
*
|
|
341
|
+
* This module covers:
|
|
342
|
+
* - Anchor's instruction discriminator (`sha256("global:<name>")[0..8]`)
|
|
343
|
+
* - Borsh primitive writers (u16, u64, i64, [u8; N], String)
|
|
344
|
+
* - The `AttestedReceiptArgs` struct encoder
|
|
345
|
+
*
|
|
346
|
+
* Reference for the on-chain shape:
|
|
347
|
+
* programs/loop-shopping/src/lib.rs
|
|
348
|
+
* - `pub fn submit_attested_receipt(ctx, args: AttestedReceiptArgs)`
|
|
349
|
+
* - `pub struct AttestedReceiptArgs { bank_txn_id, merchant_name_raw,
|
|
350
|
+
* mcc, amount_cents, posted_at, account_last4 }`
|
|
351
|
+
*/
|
|
352
|
+
/**
|
|
353
|
+
* Compute the 8-byte Anchor instruction discriminator.
|
|
354
|
+
* Convention: `sha256("global:" + snake_case_name)[0..8]`.
|
|
355
|
+
*/
|
|
356
|
+
declare function instructionDiscriminator(snakeName: string): Uint8Array;
|
|
357
|
+
/**
|
|
358
|
+
* Borsh-encode the `AttestedReceiptArgs` struct exactly as the on-chain
|
|
359
|
+
* handler will deserialize it. Field order is load-bearing.
|
|
360
|
+
*
|
|
361
|
+
* pub struct AttestedReceiptArgs {
|
|
362
|
+
* pub bank_txn_id: [u8; 32], // raw 32 bytes
|
|
363
|
+
* pub merchant_name_raw: String, // u32 LE length + UTF-8 bytes
|
|
364
|
+
* pub mcc: u16, // 2 bytes LE
|
|
365
|
+
* pub amount_cents: u64, // 8 bytes LE
|
|
366
|
+
* pub posted_at: i64, // 8 bytes LE (two's complement)
|
|
367
|
+
* pub account_last4: u16, // 2 bytes LE
|
|
368
|
+
* }
|
|
369
|
+
*/
|
|
370
|
+
declare function encodeAttestedReceiptArgs(args: {
|
|
371
|
+
bankTxnId: Uint8Array;
|
|
372
|
+
merchantNameRaw: string;
|
|
373
|
+
mcc: number;
|
|
374
|
+
amountCents: bigint;
|
|
375
|
+
postedAt: bigint;
|
|
376
|
+
accountLast4: number;
|
|
377
|
+
}): Uint8Array;
|
|
378
|
+
/**
|
|
379
|
+
* Build the full instruction payload (discriminator || args) for
|
|
380
|
+
* `submit_attested_receipt`. This is what goes into
|
|
381
|
+
* `TransactionInstruction.data`.
|
|
382
|
+
*/
|
|
383
|
+
declare function encodeSubmitAttestedReceiptIxData(args: {
|
|
384
|
+
bankTxnId: Uint8Array;
|
|
385
|
+
merchantNameRaw: string;
|
|
386
|
+
mcc: number;
|
|
387
|
+
amountCents: bigint;
|
|
388
|
+
postedAt: bigint;
|
|
389
|
+
accountLast4: number;
|
|
390
|
+
}): Uint8Array;
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* AttestedReceiptSubmitter — sign + submit `submit_attested_receipt` txs.
|
|
394
|
+
*
|
|
395
|
+
* Spec 08 § "Enclave-side API". This class is what an attested agent
|
|
396
|
+
* (running inside an AWS Nitro / GCP Confidential Space / Anthropic
|
|
397
|
+
* Claude Computer Use enclave) uses to publish bank receipts on-chain.
|
|
398
|
+
*
|
|
399
|
+
* Trust boundary:
|
|
400
|
+
* - The session **secret** lives only in the enclave; this class never
|
|
401
|
+
* holds it. Signing is delegated to an `EnclaveSigner` (typically
|
|
402
|
+
* the `EnclaveClient` from `@loopprotocol/sdk`, which talks to the
|
|
403
|
+
* enclave via vsock or an HTTPS broker).
|
|
404
|
+
* - This class assembles transactions, derives PDAs, and shepherds
|
|
405
|
+
* network I/O. It can run anywhere — host, browser, an unattested
|
|
406
|
+
* proxy — without weakening the security model.
|
|
407
|
+
*/
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Bit position of `SUBMIT_ATTESTED_RECEIPT` in the
|
|
411
|
+
* `AgentSessionRegistration::allowed_instructions` bitfield.
|
|
412
|
+
*
|
|
413
|
+
* Mirrors `loop-shopping::SESSION_BIT_SUBMIT_ATTESTED_RECEIPT = 1 << 5`.
|
|
414
|
+
* `loop-vault` writes the numeric bitmap during session registration; bit 5
|
|
415
|
+
* is reserved for shopping attested receipts and must not be retargeted.
|
|
416
|
+
* Drift here = enclave's policy filter rejects valid signing requests,
|
|
417
|
+
* or worse, accepts wrong ones. Keep in lockstep with the on-chain const.
|
|
418
|
+
*/
|
|
419
|
+
declare const SESSION_INSTRUCTION_INDEX_SUBMIT_ATTESTED_RECEIPT = 5;
|
|
420
|
+
/**
|
|
421
|
+
* Signing requests passed to the enclave. The enclave's `policy.rs`
|
|
422
|
+
* uses the policy fields to verify the signature request matches a
|
|
423
|
+
* receipt the enclave actually scraped (defense in depth against a
|
|
424
|
+
* compromised host trying to inject forged receipts through the
|
|
425
|
+
* legitimate signing channel).
|
|
426
|
+
*/
|
|
427
|
+
interface PolicyInputs {
|
|
428
|
+
/** Amount in cents, mirrored from the receipt being signed. */
|
|
429
|
+
amountCents: bigint;
|
|
430
|
+
/** Normalized merchant name, mirrored from the receipt. */
|
|
431
|
+
merchantNameRaw: string;
|
|
432
|
+
/** Bank-side posted-at timestamp, mirrored from the receipt. */
|
|
433
|
+
postedAt: bigint;
|
|
434
|
+
/** 32-byte receipt id, mirrored from the receipt. */
|
|
435
|
+
bankTxnId: Uint8Array;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* The minimal contract sdk-byoaa needs to sign a transaction message
|
|
439
|
+
* inside an attested enclave. Decoupled from any specific transport so
|
|
440
|
+
* the SDK works against:
|
|
441
|
+
* - The Loop-operated HTTPS broker (production)
|
|
442
|
+
* - A local vsock listener (developer running the EIF on-host)
|
|
443
|
+
* - A test double (unit tests; see tests/enclave.test.ts)
|
|
444
|
+
*
|
|
445
|
+
* `signInstruction` MUST return a 64-byte ed25519 signature over `message`
|
|
446
|
+
* produced by the session keypair bound to `getSessionPubkey()`.
|
|
447
|
+
*/
|
|
448
|
+
interface EnclaveSigner {
|
|
449
|
+
/** The enclave's bound session public key (same one registered on-chain). */
|
|
450
|
+
getSessionPubkey(): Promise<PublicKey>;
|
|
451
|
+
/**
|
|
452
|
+
* Request the enclave to sign a transaction message. Returns the raw
|
|
453
|
+
* 64-byte ed25519 signature. The enclave is expected to enforce its
|
|
454
|
+
* own policy filter — instruction index allowlist, well-formedness,
|
|
455
|
+
* rate limits — before signing.
|
|
456
|
+
*/
|
|
457
|
+
signInstruction(args: {
|
|
458
|
+
instructionIndex: number;
|
|
459
|
+
message: Uint8Array;
|
|
460
|
+
policyInputs: PolicyInputs;
|
|
461
|
+
}): Promise<Uint8Array>;
|
|
462
|
+
}
|
|
463
|
+
interface AttestedReceiptSubmitterOptions {
|
|
464
|
+
/** Network selector or fully-resolved config. Defaults to "devnet". */
|
|
465
|
+
network?: Network | LoopByoaaConfig;
|
|
466
|
+
/** Enclave signer abstraction (see `EnclaveSigner`). */
|
|
467
|
+
enclaveSigner: EnclaveSigner;
|
|
468
|
+
/** User vault that the receipts are for (must match the session's vault). */
|
|
469
|
+
vault: PublicKey;
|
|
470
|
+
/**
|
|
471
|
+
* The session ed25519 pubkey that was registered on-chain via
|
|
472
|
+
* `register_agent_session` — i.e. the public side of the keypair the
|
|
473
|
+
* enclave signs with. The submitter derives the on-chain
|
|
474
|
+
* `AgentSessionRegistration` PDA from `[b"agent_session", vault,
|
|
475
|
+
* sessionPubkey]` against the configured vault program; consumers
|
|
476
|
+
* never have to compute the PDA themselves.
|
|
477
|
+
*/
|
|
478
|
+
sessionPubkey: PublicKey;
|
|
479
|
+
/** Confirmation level for `submit()`. Defaults to "confirmed". */
|
|
480
|
+
commitment?: Commitment;
|
|
481
|
+
}
|
|
482
|
+
/** Outcome of a single `submit()` call. */
|
|
483
|
+
interface SubmitOk {
|
|
484
|
+
ok: true;
|
|
485
|
+
signature: TransactionSignature;
|
|
486
|
+
receiptPda: PublicKey;
|
|
487
|
+
}
|
|
488
|
+
interface SubmitFail {
|
|
489
|
+
ok: false;
|
|
490
|
+
error: SubmitError;
|
|
491
|
+
/** Optional underlying error message for diagnostics. */
|
|
492
|
+
detail?: string;
|
|
493
|
+
}
|
|
494
|
+
type SubmitResult = SubmitOk | SubmitFail;
|
|
495
|
+
type SubmitError = "validation_failed" | "session_pubkey_mismatch" | "blockhash_fetch_failed" | "enclave_sign_failed" | "transaction_send_failed" | "transaction_confirm_failed";
|
|
496
|
+
interface SubmitBatchOptions {
|
|
497
|
+
/** Maximum in-flight submissions. Defaults to 4. */
|
|
498
|
+
concurrency?: number;
|
|
499
|
+
/** Minimum ms between starting consecutive submissions. Defaults to 0. */
|
|
500
|
+
rateLimitMs?: number;
|
|
501
|
+
/** Per-attempt commitment override. */
|
|
502
|
+
commitment?: Commitment;
|
|
503
|
+
/** Optional progress callback fired after each receipt resolves. */
|
|
504
|
+
onProgress?: (event: {
|
|
505
|
+
index: number;
|
|
506
|
+
total: number;
|
|
507
|
+
result: SubmitResult;
|
|
508
|
+
}) => void;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Sign + submit BYOAA receipts to the Loop Protocol clearing rail.
|
|
512
|
+
*
|
|
513
|
+
* Standard usage:
|
|
514
|
+
*
|
|
515
|
+
* ```ts
|
|
516
|
+
* const submitter = new AttestedReceiptSubmitter({
|
|
517
|
+
* network: "devnet",
|
|
518
|
+
* enclaveSigner, // talks to your Nitro enclave
|
|
519
|
+
* vault: userVaultPda,
|
|
520
|
+
* session: agentSessionPda,
|
|
521
|
+
* });
|
|
522
|
+
*
|
|
523
|
+
* const r = await submitter.submit(receipt);
|
|
524
|
+
* if (r.ok) console.log("receipt:", r.receiptPda.toBase58(), "tx:", r.signature);
|
|
525
|
+
* ```
|
|
526
|
+
*
|
|
527
|
+
* For batched ingestion (a daily cron pulling N transactions), use
|
|
528
|
+
* `submitBatch` — it bounds concurrency + applies an inter-request delay
|
|
529
|
+
* so RPC rate limits don't trip mid-run.
|
|
530
|
+
*/
|
|
531
|
+
declare class AttestedReceiptSubmitter {
|
|
532
|
+
private readonly resolvedConfig;
|
|
533
|
+
private readonly enclaveSigner;
|
|
534
|
+
private readonly vault;
|
|
535
|
+
/**
|
|
536
|
+
* The on-chain `AgentSessionRegistration` PDA, derived once from
|
|
537
|
+
* `[b"agent_session", vault, sessionPubkey]` against the vault program.
|
|
538
|
+
* The on-chain handler reads it as `session_registration`.
|
|
539
|
+
*/
|
|
540
|
+
private readonly sessionRegistrationPda;
|
|
541
|
+
private readonly commitment;
|
|
542
|
+
/** Cached so we don't round-trip the enclave on every submit. */
|
|
543
|
+
private cachedSessionPubkey;
|
|
544
|
+
constructor(opts: AttestedReceiptSubmitterOptions);
|
|
545
|
+
/** Network selector this submitter was constructed against. */
|
|
546
|
+
get network(): Network;
|
|
547
|
+
/**
|
|
548
|
+
* Resolve the receipt PDA for a given txn id without submitting.
|
|
549
|
+
* Useful for off-chain state checks ("have we already submitted X?")
|
|
550
|
+
* before paying RPC fees.
|
|
551
|
+
*/
|
|
552
|
+
deriveReceiptPda(bankTxnId: Uint8Array): PublicKey;
|
|
553
|
+
/**
|
|
554
|
+
* Sign + submit one receipt. Returns the on-chain signature + the
|
|
555
|
+
* receipt's PDA on success. Failures are returned as discriminated
|
|
556
|
+
* unions rather than thrown — batch consumers want fail-isolation.
|
|
557
|
+
*/
|
|
558
|
+
submit(receipt: BankReceipt): Promise<SubmitResult>;
|
|
559
|
+
/**
|
|
560
|
+
* Submit N receipts with bounded concurrency + optional inter-request
|
|
561
|
+
* spacing. Fail-isolated: one bad receipt does not poison the rest.
|
|
562
|
+
*
|
|
563
|
+
* Order of `results` matches the order of `receipts`.
|
|
564
|
+
*/
|
|
565
|
+
submitBatch(receipts: BankReceipt[], opts?: SubmitBatchOptions): Promise<SubmitResult[]>;
|
|
566
|
+
private getSessionPubkey;
|
|
567
|
+
private buildSubmitIx;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* AttestedReceiptVerifier — fetch + decode `BankAttestedReceipt` accounts.
|
|
572
|
+
*
|
|
573
|
+
* Spec 08 § "Consumer-side API". Used by:
|
|
574
|
+
* - Merchant intel dashboards (joining receipts to merchant claim names)
|
|
575
|
+
* - Off-chain indexers backfilling Supabase mirror tables
|
|
576
|
+
* - Forensics / audit tooling cross-checking PCR0 against the
|
|
577
|
+
* EnclaveImageRegistry to flag unaudited submissions
|
|
578
|
+
*
|
|
579
|
+
* This module talks to chain via the same `ResolvedConfig` the submitter
|
|
580
|
+
* uses, so verification is network-switchable in lockstep — devnet today,
|
|
581
|
+
* mainnet the moment Bundle 2 deploys.
|
|
582
|
+
*
|
|
583
|
+
* Hand-rolled decoder (no Anchor IDL dep) so the package ships
|
|
584
|
+
* independently of the protocol's anchor-build cycle. The decoder mirrors
|
|
585
|
+
* the on-chain struct field order in `programs/loop-shopping/src/lib.rs`
|
|
586
|
+
* (`BankAttestedReceipt`) and `programs/loop-vault/src/lib.rs`
|
|
587
|
+
* (`EnclaveImageRegistry`).
|
|
588
|
+
*/
|
|
589
|
+
|
|
590
|
+
type ReceiptStatus = "recorded" | "honored" | "invalidated";
|
|
591
|
+
interface VerifiedReceipt extends BankReceipt {
|
|
592
|
+
/** Address of the on-chain `BankAttestedReceipt` PDA. */
|
|
593
|
+
pda: PublicKey;
|
|
594
|
+
/** Owner vault — should match the user's vault you queried for. */
|
|
595
|
+
vault: PublicKey;
|
|
596
|
+
/** The enclave session pubkey that signed the submission. */
|
|
597
|
+
sessionPubkey: PublicKey;
|
|
598
|
+
/** PCR0 of the enclave image that produced the receipt. */
|
|
599
|
+
pcr0: Uint8Array;
|
|
600
|
+
/** Block timestamp at submission (Solana clock). */
|
|
601
|
+
submittedAt: bigint;
|
|
602
|
+
/**
|
|
603
|
+
* Lifecycle:
|
|
604
|
+
* - "recorded" — submitted, not yet honored
|
|
605
|
+
* - "honored" — a merchant claimed + paid via spec 08a
|
|
606
|
+
* - "invalidated" — admin-revoked (fraud signal)
|
|
607
|
+
*/
|
|
608
|
+
status: ReceiptStatus;
|
|
609
|
+
/**
|
|
610
|
+
* `true` iff this receipt's `pcr0` matches a currently-approved entry in
|
|
611
|
+
* the on-chain `EnclaveImageRegistry`. Always populated by `fetch()` —
|
|
612
|
+
* pass `requireAuditedImage: true` to throw when false.
|
|
613
|
+
*
|
|
614
|
+
* `null` when the verifier was constructed with `skipImageAudit: true`
|
|
615
|
+
* (lighter-weight; useful when the caller already knows audit state).
|
|
616
|
+
*/
|
|
617
|
+
imageAudited: boolean | null;
|
|
618
|
+
}
|
|
619
|
+
interface AttestedReceiptVerifierOptions {
|
|
620
|
+
network?: Network | LoopByoaaConfig;
|
|
621
|
+
/**
|
|
622
|
+
* Cache the EnclaveImageRegistry decode for this many ms between
|
|
623
|
+
* fetches. Defaults to 60s — short enough to pick up admin updates
|
|
624
|
+
* within a minute, long enough that bulk verification doesn't
|
|
625
|
+
* hammer the RPC.
|
|
626
|
+
*/
|
|
627
|
+
registryCacheMs?: number;
|
|
628
|
+
/**
|
|
629
|
+
* Skip the EnclaveImageRegistry cross-check entirely. `imageAudited`
|
|
630
|
+
* on returned receipts will be `null`. Use only when you don't care
|
|
631
|
+
* about audit state and want to save an RPC round-trip.
|
|
632
|
+
*/
|
|
633
|
+
skipImageAudit?: boolean;
|
|
634
|
+
}
|
|
635
|
+
interface FetchOptions {
|
|
636
|
+
/**
|
|
637
|
+
* If `true`, throw `ReceiptNotAuditedError` when the receipt's PCR0
|
|
638
|
+
* doesn't match a current `EnclaveImageRegistry` entry. Default `false`
|
|
639
|
+
* — the field is populated and the caller decides.
|
|
640
|
+
*/
|
|
641
|
+
requireAuditedImage?: boolean;
|
|
642
|
+
}
|
|
643
|
+
declare class ReceiptNotAuditedError extends Error {
|
|
644
|
+
readonly receipt: VerifiedReceipt;
|
|
645
|
+
constructor(receipt: VerifiedReceipt);
|
|
646
|
+
}
|
|
647
|
+
declare class ReceiptNotFoundError extends Error {
|
|
648
|
+
readonly pda: PublicKey;
|
|
649
|
+
constructor(pda: PublicKey);
|
|
650
|
+
}
|
|
651
|
+
declare class UnsupportedProofTypeError extends Error {
|
|
652
|
+
readonly proofType: ReceiptProofType;
|
|
653
|
+
constructor(proofType: ReceiptProofType);
|
|
654
|
+
}
|
|
655
|
+
type AttestedReceiptProofVerification = {
|
|
656
|
+
ok: true;
|
|
657
|
+
proof_type: "cose_sign1_x509";
|
|
658
|
+
};
|
|
659
|
+
/**
|
|
660
|
+
* Dispatch verifier for the BYOAA attested-receipt proof wire contract.
|
|
661
|
+
*
|
|
662
|
+
* Today only the COSE+X.509 arm is accepted. Reserved ZK arms are part of the
|
|
663
|
+
* wire shape now so future SDKs can implement them without retargeting the
|
|
664
|
+
* published contract; until then they fail closed with UnsupportedProofTypeError.
|
|
665
|
+
*/
|
|
666
|
+
declare function verifyAttestedReceiptProof(receipt: AttestedReceipt): AttestedReceiptProofVerification;
|
|
667
|
+
declare class AttestedReceiptVerifier {
|
|
668
|
+
private readonly resolvedConfig;
|
|
669
|
+
private readonly registryCacheMs;
|
|
670
|
+
private readonly skipImageAudit;
|
|
671
|
+
/** Cache of approved PCR0s. */
|
|
672
|
+
private approvedPcr0s;
|
|
673
|
+
private approvedPcr0sExpiresAt;
|
|
674
|
+
constructor(opts?: AttestedReceiptVerifierOptions);
|
|
675
|
+
get network(): Network;
|
|
676
|
+
/**
|
|
677
|
+
* Fetch one receipt by PDA. Throws `ReceiptNotFoundError` if the
|
|
678
|
+
* account is missing or `ReceiptNotAuditedError` when
|
|
679
|
+
* `requireAuditedImage` is set and the PCR0 doesn't match the registry.
|
|
680
|
+
*/
|
|
681
|
+
fetch(pda: PublicKey, opts?: FetchOptions): Promise<VerifiedReceipt>;
|
|
682
|
+
/**
|
|
683
|
+
* Stream every receipt for a given vault. Uses `getProgramAccounts`
|
|
684
|
+
* with a memcmp filter on the `vault` field at fixed offset 8 (right
|
|
685
|
+
* after the 8-byte Anchor discriminator).
|
|
686
|
+
*
|
|
687
|
+
* Order is undefined — sort client-side if you need deterministic
|
|
688
|
+
* ordering (e.g. by `submittedAt` or `postedAt`).
|
|
689
|
+
*/
|
|
690
|
+
forVault(vault: PublicKey): AsyncIterable<VerifiedReceipt>;
|
|
691
|
+
/**
|
|
692
|
+
* Stream receipts whose `merchantNameRaw` (after `normalizeMerchantName`)
|
|
693
|
+
* matches the given normalized name.
|
|
694
|
+
*
|
|
695
|
+
* **Implementation note:** `merchant_name_raw` lives at variable byte
|
|
696
|
+
* offset (it follows a 4-byte length prefix that varies per receipt),
|
|
697
|
+
* so memcmp filtering on-chain is impractical. This method therefore
|
|
698
|
+
* fetches every `BankAttestedReceipt` account from the program and
|
|
699
|
+
* filters client-side. **Use the off-chain `attested_receipt_index`
|
|
700
|
+
* Supabase mirror (loop-site-v2 PR #27) for production-scale
|
|
701
|
+
* merchant queries.** This SDK method is fine for low-volume audits
|
|
702
|
+
* + correctness testing.
|
|
703
|
+
*/
|
|
704
|
+
forMerchant(merchantNameNormalized: string): AsyncIterable<VerifiedReceipt>;
|
|
705
|
+
/**
|
|
706
|
+
* Force a refresh of the EnclaveImageRegistry cache. Useful in long-
|
|
707
|
+
* running daemons that want to pick up admin updates immediately.
|
|
708
|
+
*/
|
|
709
|
+
refreshImageRegistry(): Promise<void>;
|
|
710
|
+
private auditPcr0;
|
|
711
|
+
private loadApprovedPcr0s;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Decode a `BankAttestedReceipt` account.
|
|
715
|
+
*
|
|
716
|
+
* On-chain layout (per programs/loop-shopping/src/lib.rs):
|
|
717
|
+
* 0..8 disc 8 bytes
|
|
718
|
+
* 8..40 vault Pubkey (32)
|
|
719
|
+
* 40..72 session_pubkey Pubkey (32)
|
|
720
|
+
* 72..120 pcr0 [u8; 48]
|
|
721
|
+
* 120..152 bank_txn_id [u8; 32]
|
|
722
|
+
* 152..156 merchant_name length u32 LE
|
|
723
|
+
* 156.. merchant_name bytes N bytes (variable)
|
|
724
|
+
* then mcc u16 LE
|
|
725
|
+
* amount_cents u64 LE
|
|
726
|
+
* posted_at i64 LE
|
|
727
|
+
* account_last4 u16 LE
|
|
728
|
+
* submitted_at i64 LE
|
|
729
|
+
* status u8
|
|
730
|
+
* bump u8
|
|
731
|
+
*
|
|
732
|
+
* Anchor's `#[max_len(64)]` allocates space for the maximum but the
|
|
733
|
+
* serialized form uses the actual length, so we read sequentially.
|
|
734
|
+
*/
|
|
735
|
+
declare function decodeBankAttestedReceipt(pda: PublicKey, account: AccountInfo<Buffer>): VerifiedReceipt;
|
|
736
|
+
/**
|
|
737
|
+
* Decode the EnclaveImageRegistry account into a Set of approved PCR0
|
|
738
|
+
* hex strings.
|
|
739
|
+
*
|
|
740
|
+
* On-chain layout (per programs/loop-vault/src/lib.rs):
|
|
741
|
+
* 0..8 disc
|
|
742
|
+
* 8..40 admin Pubkey
|
|
743
|
+
* 40..44 approved_images vec len u32 LE
|
|
744
|
+
* then N entries of ApprovedImage:
|
|
745
|
+
* pcr0 [u8; 48]
|
|
746
|
+
* pcr1 [u8; 48]
|
|
747
|
+
* pcr2 [u8; 48]
|
|
748
|
+
* label [u8; 32]
|
|
749
|
+
* approved_at i64 LE
|
|
750
|
+
* end bump (u8)
|
|
751
|
+
*/
|
|
752
|
+
declare function decodeRegistryPcr0Set(account: AccountInfo<Buffer>): Set<string>;
|
|
753
|
+
|
|
754
|
+
type LoopByoaaErrorCode = "auth" | "validation" | "rate_limit" | "quota" | "permission_denied" | "not_found" | "conflict" | "server_error" | "network_error" | "unknown";
|
|
755
|
+
interface LoopByoaaErrorContext {
|
|
756
|
+
status?: number;
|
|
757
|
+
request_id?: string;
|
|
758
|
+
documentation_url?: string;
|
|
759
|
+
retry_after_seconds?: number;
|
|
760
|
+
meter_name?: string;
|
|
761
|
+
upgrade_url?: string;
|
|
762
|
+
details?: unknown;
|
|
763
|
+
}
|
|
764
|
+
/** Base SDK error. Catch this when you want one branch for all Loop BYOAA failures. */
|
|
765
|
+
declare class LoopByoaaError extends Error {
|
|
766
|
+
readonly code: LoopByoaaErrorCode | string;
|
|
767
|
+
readonly status?: number;
|
|
768
|
+
readonly request_id?: string;
|
|
769
|
+
readonly documentation_url?: string;
|
|
770
|
+
readonly details?: unknown;
|
|
771
|
+
constructor(code: LoopByoaaErrorCode | string, message: string, context?: LoopByoaaErrorContext);
|
|
772
|
+
}
|
|
773
|
+
declare class LoopByoaaApiKeyError extends LoopByoaaError {
|
|
774
|
+
constructor(code: LoopByoaaErrorCode | string, message: string, context?: LoopByoaaErrorContext);
|
|
775
|
+
}
|
|
776
|
+
declare class LoopByoaaPermissionError extends LoopByoaaError {
|
|
777
|
+
constructor(code: LoopByoaaErrorCode | string, message: string, context?: LoopByoaaErrorContext);
|
|
778
|
+
}
|
|
779
|
+
declare class LoopByoaaRateLimitError extends LoopByoaaError {
|
|
780
|
+
readonly retry_after_seconds?: number;
|
|
781
|
+
constructor(code: LoopByoaaErrorCode | string, message: string, context?: LoopByoaaErrorContext);
|
|
782
|
+
}
|
|
783
|
+
declare class LoopByoaaQuotaError extends LoopByoaaError {
|
|
784
|
+
readonly meter_name?: string;
|
|
785
|
+
readonly upgrade_url?: string;
|
|
786
|
+
constructor(code: LoopByoaaErrorCode | string, message: string, context?: LoopByoaaErrorContext);
|
|
787
|
+
}
|
|
788
|
+
declare class LoopByoaaValidationError extends LoopByoaaError {
|
|
789
|
+
constructor(code: LoopByoaaErrorCode | string, message: string, context?: LoopByoaaErrorContext);
|
|
790
|
+
}
|
|
791
|
+
declare class LoopByoaaServerError extends LoopByoaaError {
|
|
792
|
+
constructor(code: LoopByoaaErrorCode | string, message: string, context?: LoopByoaaErrorContext);
|
|
793
|
+
}
|
|
794
|
+
declare class LoopByoaaNetworkError extends LoopByoaaError {
|
|
795
|
+
constructor(message: string, context?: LoopByoaaErrorContext);
|
|
796
|
+
}
|
|
797
|
+
type AgentRiskTier = "low" | "medium" | "high" | "critical";
|
|
798
|
+
type AgentStatus = "active" | "suspended" | "revoked" | "test" | "archived";
|
|
799
|
+
interface AgentAttestation {
|
|
800
|
+
type: string;
|
|
801
|
+
issuer?: string;
|
|
802
|
+
evidence?: Record<string, unknown>;
|
|
803
|
+
issued_at?: string;
|
|
804
|
+
}
|
|
805
|
+
interface AgentRegisterInput {
|
|
806
|
+
external_id: string;
|
|
807
|
+
display_name: string;
|
|
808
|
+
purpose: string;
|
|
809
|
+
risk_tier: AgentRiskTier;
|
|
810
|
+
attestations?: AgentAttestation[];
|
|
811
|
+
metadata?: Record<string, unknown>;
|
|
812
|
+
}
|
|
813
|
+
interface AgentRecord {
|
|
814
|
+
id: string;
|
|
815
|
+
external_id?: string;
|
|
816
|
+
agent_key?: string;
|
|
817
|
+
display_name: string;
|
|
818
|
+
purpose?: string;
|
|
819
|
+
risk_tier: AgentRiskTier | string;
|
|
820
|
+
status: AgentStatus | string;
|
|
821
|
+
attestations?: AgentAttestation[];
|
|
822
|
+
metadata?: Record<string, unknown>;
|
|
823
|
+
created_at: string;
|
|
824
|
+
updated_at?: string;
|
|
825
|
+
}
|
|
826
|
+
interface AgentListParams {
|
|
827
|
+
status?: AgentStatus | string;
|
|
828
|
+
page?: number;
|
|
829
|
+
limit?: number;
|
|
830
|
+
}
|
|
831
|
+
type AgentUpdateInput = Partial<Pick<AgentRegisterInput, "display_name" | "purpose" | "risk_tier" | "attestations" | "metadata">>;
|
|
832
|
+
interface PermissionConditions {
|
|
833
|
+
[key: string]: unknown;
|
|
834
|
+
}
|
|
835
|
+
interface PermissionGrantInput {
|
|
836
|
+
agent_id: string;
|
|
837
|
+
on_behalf_of_user_id: string;
|
|
838
|
+
layer: string;
|
|
839
|
+
action_class: string;
|
|
840
|
+
amount_cap_cents?: number;
|
|
841
|
+
amount_cap_period?: "day" | "week" | "month" | "total";
|
|
842
|
+
valid_from?: Date | string;
|
|
843
|
+
valid_until?: Date | string;
|
|
844
|
+
conditions?: PermissionConditions;
|
|
845
|
+
metadata?: Record<string, unknown>;
|
|
846
|
+
}
|
|
847
|
+
interface PermissionGrant {
|
|
848
|
+
id: string;
|
|
849
|
+
grant_id?: string;
|
|
850
|
+
agent_id: string;
|
|
851
|
+
on_behalf_of_user_id?: string;
|
|
852
|
+
layer?: string;
|
|
853
|
+
action_class?: string;
|
|
854
|
+
status: "active" | "suspended" | "revoked" | "expired" | string;
|
|
855
|
+
amount_cap_cents?: number;
|
|
856
|
+
amount_cap_period?: string;
|
|
857
|
+
valid_from?: string;
|
|
858
|
+
valid_until?: string;
|
|
859
|
+
conditions?: PermissionConditions;
|
|
860
|
+
created_at: string;
|
|
861
|
+
updated_at?: string;
|
|
862
|
+
}
|
|
863
|
+
interface PermissionListParams {
|
|
864
|
+
agent_id?: string;
|
|
865
|
+
user_id?: string;
|
|
866
|
+
status?: string;
|
|
867
|
+
page?: number;
|
|
868
|
+
limit?: number;
|
|
869
|
+
}
|
|
870
|
+
interface PermissionRevokeInput {
|
|
871
|
+
reason: string;
|
|
872
|
+
evidence?: string;
|
|
873
|
+
}
|
|
874
|
+
interface IntendedAction {
|
|
875
|
+
layer: string;
|
|
876
|
+
action_class: string;
|
|
877
|
+
amount_cents?: number;
|
|
878
|
+
target?: Record<string, unknown>;
|
|
879
|
+
}
|
|
880
|
+
interface StepUpEvidence {
|
|
881
|
+
type: string;
|
|
882
|
+
evidence_ref?: string;
|
|
883
|
+
payload?: Record<string, unknown>;
|
|
884
|
+
}
|
|
885
|
+
interface DecisionRequestInput {
|
|
886
|
+
agent_id: string;
|
|
887
|
+
on_behalf_of_user_id: string;
|
|
888
|
+
intended_action: IntendedAction;
|
|
889
|
+
step_up_evidence?: StepUpEvidence;
|
|
890
|
+
policy_mode?: "default" | "restricted" | "bounded" | "permissive";
|
|
891
|
+
metadata?: Record<string, unknown>;
|
|
892
|
+
}
|
|
893
|
+
interface DecisionEnvelope {
|
|
894
|
+
outcome: "allow" | "review" | "deny" | "review_required" | string;
|
|
895
|
+
envelope_id: string;
|
|
896
|
+
review_request_id?: string;
|
|
897
|
+
reason?: string;
|
|
898
|
+
reasons?: string[];
|
|
899
|
+
issued_at: string;
|
|
900
|
+
signature_ref?: string;
|
|
901
|
+
authority_bundle_ref?: string;
|
|
902
|
+
decision_envelope_ref?: string;
|
|
903
|
+
audit_ref?: string;
|
|
904
|
+
metadata?: Record<string, unknown>;
|
|
905
|
+
}
|
|
906
|
+
interface DecisionListParams {
|
|
907
|
+
agent_id?: string;
|
|
908
|
+
user_id?: string;
|
|
909
|
+
outcome?: string;
|
|
910
|
+
since?: Date | string;
|
|
911
|
+
until?: Date | string;
|
|
912
|
+
page?: number;
|
|
913
|
+
limit?: number;
|
|
914
|
+
}
|
|
915
|
+
interface AuditExportInput {
|
|
916
|
+
range: {
|
|
917
|
+
from: Date | string;
|
|
918
|
+
to: Date | string;
|
|
919
|
+
};
|
|
920
|
+
scope?: {
|
|
921
|
+
agent_ids?: string[];
|
|
922
|
+
include_authority_bundles?: boolean;
|
|
923
|
+
include_decision_envelopes?: boolean;
|
|
924
|
+
include_audit_events?: boolean;
|
|
925
|
+
};
|
|
926
|
+
countersign?: boolean;
|
|
927
|
+
format?: "zip" | "jsonl" | "json";
|
|
928
|
+
}
|
|
929
|
+
interface AuditPack {
|
|
930
|
+
pack_id?: string;
|
|
931
|
+
manifest: Record<string, unknown>;
|
|
932
|
+
body?: unknown;
|
|
933
|
+
format?: "zip" | "jsonl" | "json" | string;
|
|
934
|
+
signature_bundle?: unknown;
|
|
935
|
+
exported_at?: string;
|
|
936
|
+
}
|
|
937
|
+
interface AuditEvent {
|
|
938
|
+
id: string;
|
|
939
|
+
audit_ref: string;
|
|
940
|
+
decision_envelope_ref: string;
|
|
941
|
+
event_type: string;
|
|
942
|
+
payload_digest?: string;
|
|
943
|
+
signature_bundle?: unknown;
|
|
944
|
+
prev_audit_hash?: string;
|
|
945
|
+
this_audit_hash?: string;
|
|
946
|
+
nonce?: string;
|
|
947
|
+
created_at: string;
|
|
948
|
+
}
|
|
949
|
+
interface AuditListParams {
|
|
950
|
+
since?: Date | string;
|
|
951
|
+
until?: Date | string;
|
|
952
|
+
agent_id?: string;
|
|
953
|
+
page?: number;
|
|
954
|
+
limit?: number;
|
|
955
|
+
}
|
|
956
|
+
interface Paginated<T> {
|
|
957
|
+
items: T[];
|
|
958
|
+
page?: number;
|
|
959
|
+
limit?: number;
|
|
960
|
+
total?: number;
|
|
961
|
+
next_cursor?: string;
|
|
962
|
+
}
|
|
963
|
+
type LoopByoaaEnvironment = "dev" | "staging" | "prod";
|
|
964
|
+
interface ParsedLoopByoaaSdkKey {
|
|
965
|
+
env: LoopByoaaEnvironment;
|
|
966
|
+
prefix: string;
|
|
967
|
+
hashable: string;
|
|
968
|
+
}
|
|
969
|
+
interface LoopByoaaClientOptions {
|
|
970
|
+
/** Customer SDK key. Parsed at construction time and sent as bearer auth. */
|
|
971
|
+
apiKey: string;
|
|
972
|
+
/** OAR API root. Explicit overrides are kept for tests and federated tiers. */
|
|
973
|
+
baseUrl?: string;
|
|
974
|
+
timeout?: number;
|
|
975
|
+
fetch?: typeof fetch;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Parse a customer SDK key without making a network call.
|
|
979
|
+
*
|
|
980
|
+
* The suffix is intentionally restricted to 32 base62 characters for E.2 so
|
|
981
|
+
* malformed keys fail before the first request. Server-side key hashing can
|
|
982
|
+
* use `hashable` directly without the SDK needing to understand key storage.
|
|
983
|
+
*/
|
|
984
|
+
declare function parseLoopByoaaSdkKey(apiKey: string): ParsedLoopByoaaSdkKey;
|
|
985
|
+
type HttpMethod = "GET" | "POST" | "PATCH" | "DELETE";
|
|
986
|
+
declare class HttpClient {
|
|
987
|
+
private readonly apiKey;
|
|
988
|
+
private readonly baseUrl;
|
|
989
|
+
private readonly fetchImpl;
|
|
990
|
+
private readonly timeout;
|
|
991
|
+
constructor(apiKey: string, baseUrl: string, fetchImpl: typeof fetch, timeout: number);
|
|
992
|
+
request<T>(method: HttpMethod, path: string, body?: unknown, query?: object): Promise<T>;
|
|
993
|
+
}
|
|
994
|
+
declare class LoopByoaaClient {
|
|
995
|
+
readonly apiKey: string;
|
|
996
|
+
readonly env: LoopByoaaEnvironment;
|
|
997
|
+
readonly parsedKey: ParsedLoopByoaaSdkKey;
|
|
998
|
+
readonly baseUrl: string;
|
|
999
|
+
readonly agents: AgentsNamespace;
|
|
1000
|
+
readonly permissions: PermissionsNamespace;
|
|
1001
|
+
readonly decisions: DecisionsNamespace;
|
|
1002
|
+
readonly audit: AuditNamespace;
|
|
1003
|
+
constructor(options: LoopByoaaClientOptions);
|
|
1004
|
+
}
|
|
1005
|
+
declare class AgentsNamespace {
|
|
1006
|
+
private readonly http;
|
|
1007
|
+
constructor(http: HttpClient);
|
|
1008
|
+
register(input: AgentRegisterInput): Promise<AgentRecord>;
|
|
1009
|
+
list(params?: AgentListParams): Promise<Paginated<AgentRecord>>;
|
|
1010
|
+
get(agentIdOrExternalId: string): Promise<AgentRecord>;
|
|
1011
|
+
update(id: string, input: AgentUpdateInput): Promise<AgentRecord>;
|
|
1012
|
+
suspend(id: string, reason: string): Promise<AgentRecord>;
|
|
1013
|
+
revoke(id: string, reason: string): Promise<AgentRecord>;
|
|
1014
|
+
}
|
|
1015
|
+
declare class PermissionsNamespace {
|
|
1016
|
+
private readonly http;
|
|
1017
|
+
constructor(http: HttpClient);
|
|
1018
|
+
grant(input: PermissionGrantInput): Promise<PermissionGrant>;
|
|
1019
|
+
list(params?: PermissionListParams): Promise<Paginated<PermissionGrant>>;
|
|
1020
|
+
get(grantId: string): Promise<PermissionGrant>;
|
|
1021
|
+
revoke(grantId: string, input: PermissionRevokeInput): Promise<PermissionGrant>;
|
|
1022
|
+
}
|
|
1023
|
+
declare class DecisionsNamespace {
|
|
1024
|
+
private readonly http;
|
|
1025
|
+
constructor(http: HttpClient);
|
|
1026
|
+
request(input: DecisionRequestInput): Promise<DecisionEnvelope>;
|
|
1027
|
+
get(envelopeId: string): Promise<DecisionEnvelope>;
|
|
1028
|
+
list(params?: DecisionListParams): Promise<Paginated<DecisionEnvelope>>;
|
|
1029
|
+
}
|
|
1030
|
+
declare class AuditNamespace {
|
|
1031
|
+
private readonly http;
|
|
1032
|
+
constructor(http: HttpClient);
|
|
1033
|
+
/**
|
|
1034
|
+
* Export a tamper-evident KYA audit pack.
|
|
1035
|
+
*
|
|
1036
|
+
* TODO(F.5): switch this to the public audit-pack REST route once the
|
|
1037
|
+
* customer-facing endpoint ships. E.1 intentionally points at the current
|
|
1038
|
+
* internal export bridge so the method shape is stable for quickstart code.
|
|
1039
|
+
*/
|
|
1040
|
+
exportPack(input: AuditExportInput): Promise<AuditPack>;
|
|
1041
|
+
export(input: AuditExportInput): Promise<AuditPack>;
|
|
1042
|
+
list(params?: AuditListParams): Promise<Paginated<AuditEvent>>;
|
|
1043
|
+
get(eventId: string): Promise<AuditEvent>;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
export { type AgentAttestation, type AgentListParams, type AgentRecord, type AgentRegisterInput, type AgentRiskTier, type AgentStatus, type AgentUpdateInput, AgentsNamespace, type AttestedReceipt, AttestedReceiptSubmitter, type AttestedReceiptSubmitterOptions, AttestedReceiptVerifier, type AttestedReceiptVerifierOptions, type AuditEvent, type AuditExportInput, type AuditListParams, AuditNamespace, type AuditPack, type BankReceipt, type CoseSign1X509Proof, DEVNET_PROGRAM_IDS, type DecisionEnvelope, type DecisionListParams, type DecisionRequestInput, DecisionsNamespace, type EnclaveSigner, type FetchOptions, type Groth16AltBn128Proof, type IntendedAction, LoopByoaaApiKeyError, LoopByoaaClient, type LoopByoaaClientOptions, type LoopByoaaConfig, type LoopByoaaEnvironment, LoopByoaaError, type LoopByoaaErrorCode, type LoopByoaaErrorContext, LoopByoaaNetworkError, LoopByoaaPermissionError, LoopByoaaQuotaError, LoopByoaaRateLimitError, LoopByoaaServerError, LoopByoaaValidationError, MAINNET_PROGRAM_IDS, MAX_MERCHANT_NAME_RAW_LEN, MAX_RECEIPT_AGE_SECONDS, MAX_RECEIPT_CENTS, type Network, type Paginated, type ParsedLoopByoaaSdkKey, type PermissionConditions, type PermissionGrant, type PermissionGrantInput, type PermissionListParams, type PermissionRevokeInput, PermissionsNamespace, type PolicyInputs, ReceiptNotAuditedError, ReceiptNotFoundError, type ReceiptPayload, type ReceiptProofType, type ReceiptStatus, type ReceiptValidationError, type ReceiptValidationResult, type ResolvedConfig, type Risc0V1Proof, SESSION_INSTRUCTION_INDEX_SUBMIT_ATTESTED_RECEIPT, type StepUpEvidence, type SubmitBatchOptions, type SubmitError, type SubmitFail, type SubmitOk, type SubmitResult, UnsupportedProofTypeError, type VerifiedReceipt, decodeBankAttestedReceipt, decodeRegistryPcr0Set, defaultRpcUrl, deriveAgentSessionPda, deriveBankAttestedReceiptPda, deriveBankTxnId, deriveShoppingStatePda, encodeAttestedReceiptArgs, encodeSubmitAttestedReceiptIxData, getProgramIds, instructionDiscriminator, isMainnet, normalizeMerchantName, parseLoopByoaaSdkKey, resolveConfig, validateReceipt, verifyAttestedReceiptProof };
|