@unbrowse/sdk 11.1.1 → 11.2.0-preview.3

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/dist/flex.d.ts ADDED
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Flex payment primitives — typed shapes for the @faremeter/flex-solana
3
+ * escrow-and-session-key payment scheme. Day-4: real wiring against
4
+ * @faremeter/flex-solana@0.2.1 (peer + optional dep so SDK callers who only
5
+ * use free routes don't pull @solana/kit).
6
+ *
7
+ * Aligned with the package's exported wire shape:
8
+ * - `FlexPaymentPayload` (`@faremeter/flex-solana/types`) =
9
+ * { escrow, mint, maxAmount, authorizationId, expiresAtSlot,
10
+ * splits[], sessionKey, signature }
11
+ * - `FlexPaymentRequirementsExtra` (`@faremeter/flex-solana/types`) =
12
+ * { facilitator, supportedMints[], splits[], escrow?, minGracePeriodSlots? }
13
+ * - `SplitInput` (`@faremeter/flex-solana/authorization`) =
14
+ * { recipient: Address, bps: number }
15
+ *
16
+ * IMPORTANT (tree-shake invariant from Day 3): this file MUST NOT import
17
+ * @faremeter/flex-solana at top-level. All package references go through
18
+ * `await import("@faremeter/flex-solana")` inside function bodies so SDK
19
+ * callers who never sign Flex authorizations don't pull @solana/kit (~3MB).
20
+ */
21
+ import type { PaymentRequiredError } from "./errors.js";
22
+ /** USDC SPL mint addresses. v6.16 defaults to mainnet; callers override per-env. */
23
+ export declare const USDC_MINT_MAINNET = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
24
+ export declare const USDC_MINT_DEVNET = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
25
+ /**
26
+ * Unsigned Flex payment authorization. Matches the persistable subset of
27
+ * `FlexPaymentPayload` from @faremeter/flex-solana minus the wire-only
28
+ * `sessionKey` (added at signing time) and `signature` (added at signing
29
+ * time). All numeric fields are base10 strings so the shape survives JSON.
30
+ */
31
+ export interface FlexAuthorization {
32
+ /** Base58 escrow PDA. */
33
+ escrow: string;
34
+ /** USDC mint address (base58). */
35
+ mint: string;
36
+ /** µ¢ atomic units the facilitator may draw, as base10 string. */
37
+ maxAmount: string;
38
+ /** Random u64 as base10 string, used for replay protection. */
39
+ authorizationId: string;
40
+ /** Slot height after which this authorization is invalid, as base10 string. */
41
+ expiresAtSlot: string;
42
+ /**
43
+ * Up to 5 splits whose bps sum to exactly 10000. Each recipient is a
44
+ * base58 token-account address; bps is an integer in [1, 10000].
45
+ * Matches `SplitInput` from @faremeter/flex-solana/authorization.d.ts.
46
+ */
47
+ splits: Array<{
48
+ recipient: string;
49
+ bps: number;
50
+ }>;
51
+ }
52
+ /**
53
+ * Minimal contract a caller's Flex wallet must satisfy. The wallet owns
54
+ * the escrow PDA AND has registered a session key — both wire-time facts
55
+ * the SDK does not produce, only consume.
56
+ */
57
+ export interface FlexWalletLike {
58
+ /** Wallet address that owns the escrow PDA (base58). */
59
+ address: string;
60
+ /** Registered session key address (base58). */
61
+ sessionKeyAddress: string;
62
+ /**
63
+ * Sign the authorization with the session key's Ed25519 secret. Returns
64
+ * the 64-byte signature as base64 — same shape the on-chain
65
+ * `createEd25519VerifyInstruction` expects.
66
+ */
67
+ signFlexAuthorization(auth: FlexAuthorization): Promise<string>;
68
+ }
69
+ /**
70
+ * Opaque signer object — the caller's `@solana/kit`-compatible
71
+ * `TransactionSigner` for the escrow owner / depositor. We avoid pulling
72
+ * @solana/kit types here (tree-shake); the lazy-imported instruction
73
+ * builders accept this through structural typing.
74
+ */
75
+ export type TransactionSignerOpaque = any;
76
+ /** Arguments for `fundEscrow` — builds + sends create-escrow + deposit in one tx. */
77
+ export interface FlexFundEscrowParams {
78
+ walletAddress: string;
79
+ facilitatorAddress: string;
80
+ amountUsdc: string;
81
+ refundTimeoutSlots?: number;
82
+ deadmanTimeoutSlots?: number;
83
+ /** USDC mint (defaults to mainnet USDC). */
84
+ mint?: string;
85
+ /**
86
+ * Optional caller-supplied `TransactionSigner` for the wallet. If
87
+ * omitted, `fundEscrow` rejects with a `requires_signer` error and
88
+ * recommends `buildEscrowCreationTx` instead.
89
+ */
90
+ signer?: TransactionSignerOpaque;
91
+ /** Optional caller-supplied `@solana/kit` RPC + sender; if omitted we throw. */
92
+ rpc?: unknown;
93
+ rpcSubscriptions?: unknown;
94
+ }
95
+ /** Arguments for `registerSessionKey`. */
96
+ export interface FlexRegisterSessionKeyParams {
97
+ walletAddress: string;
98
+ escrowAddress: string;
99
+ sessionKeyAddress: string;
100
+ expiresAtSlot?: string;
101
+ revocationGracePeriodSlots?: number;
102
+ signer?: TransactionSignerOpaque;
103
+ rpc?: unknown;
104
+ }
105
+ /** Pure tx-build result — what the sender wrappers consume. */
106
+ export interface BuiltFlexTx {
107
+ /** Instructions to feed `@solana/kit`'s tx message builder. */
108
+ instructions: unknown[];
109
+ /**
110
+ * Addresses the user is putting at risk by signing this tx. Lets a wallet
111
+ * UI render a sane confirmation screen ("you are depositing X USDC into
112
+ * escrow Y, registering session key Z against facilitator F").
113
+ */
114
+ accountsAtRisk: string[];
115
+ /** Programmatic hint about what this tx does. */
116
+ intent: "create_escrow_and_deposit" | "register_session_key";
117
+ }
118
+ /**
119
+ * Build an unsigned `FlexAuthorization`. Validates splits sum, generates
120
+ * a random `authorizationId`, and returns the canonical JSON shape.
121
+ *
122
+ * Cited symbol: shape mirrors `FlexPaymentPayload` from
123
+ * `@faremeter/flex-solana/types` (minus wire-only `sessionKey`/`signature`).
124
+ */
125
+ export declare function buildFlexAuthorization(opts: {
126
+ escrow: string;
127
+ mint: string;
128
+ maxAmount: string;
129
+ splits: FlexAuthorization["splits"];
130
+ expiresAtSlot: string;
131
+ }): Promise<FlexAuthorization>;
132
+ /**
133
+ * Settle a `PaymentRequiredError` whose `accepts[]` advertises a Flex
134
+ * scheme. Picks the first Flex-shaped requirement (one whose
135
+ * `extra.escrow` and `extra.splits` are populated — matches
136
+ * `FlexPaymentRequirementsExtra` from `@faremeter/flex-solana/types`),
137
+ * has the wallet sign, packs as `FlexPaymentPayload` inside an
138
+ * `X402PaymentPayload`, base64-encodes, and replays via
139
+ * `retry(paymentHeader)`.
140
+ */
141
+ export declare function payAndRetryFlex<T>(error: PaymentRequiredError, wallet: FlexWalletLike, retry: (paymentHeader: string) => Promise<T>): Promise<T>;
142
+ /**
143
+ * Pure tx-builder for create-escrow + deposit (no signer required, no
144
+ * network call). Returns instructions a caller can sign + send with any
145
+ * `@solana/kit`-compatible runtime. Lazy-imports @faremeter/flex-solana
146
+ * so tree-shake is preserved.
147
+ *
148
+ * Cited symbols: `getCreateEscrowInstructionAsync`, `getDepositInstructionAsync`
149
+ * (both from `@faremeter/flex-solana` root export, per
150
+ * `/tmp/flex-probe/.../flex-solana/dist/src/index.d.ts:9`).
151
+ */
152
+ export declare function buildEscrowCreationTx(params: FlexFundEscrowParams): Promise<BuiltFlexTx>;
153
+ /**
154
+ * Pure tx-builder for register-session-key (no signer required).
155
+ *
156
+ * Cited symbol: `getRegisterSessionKeyInstructionAsync` from
157
+ * `@faremeter/flex-solana` root export.
158
+ */
159
+ export declare function buildSessionKeyRegistrationTx(params: FlexRegisterSessionKeyParams): Promise<BuiltFlexTx>;
160
+ /**
161
+ * Send a create-escrow + deposit transaction. Requires `params.signer`
162
+ * (a `@solana/kit` TransactionSigner) and `params.rpc` (a kit RPC client).
163
+ * Without them, throws `requires_signer` and points at `buildEscrowCreationTx`
164
+ * for the pure-build path.
165
+ */
166
+ export declare function fundEscrow(params: FlexFundEscrowParams): Promise<{
167
+ escrowAddress: string;
168
+ txSignature: string;
169
+ }>;
170
+ /**
171
+ * Send a register-session-key transaction. Same `requires_signer` contract
172
+ * as `fundEscrow`. Returns the confirmed transaction signature.
173
+ */
174
+ export declare function registerSessionKey(params: FlexRegisterSessionKeyParams): Promise<{
175
+ txSignature: string;
176
+ }>;
177
+ /**
178
+ * Minimal Unbrowse client contract `setupDelegation` needs — just the
179
+ * authenticated `request` method. The real `Unbrowse` class satisfies this
180
+ * structurally; tests can pass a mock. We depend on the interface (not the
181
+ * concrete class) to keep `flex.ts` free of a `client.ts` import cycle.
182
+ */
183
+ export interface DelegationClientLike {
184
+ request<T>(method: string, path: string, body?: unknown): Promise<T>;
185
+ }
186
+ /** Backend response for the platform's delegation session pubkey. */
187
+ export interface DelegationSessionKeyResponse {
188
+ session_key_address: string;
189
+ ready: boolean;
190
+ }
191
+ /** Arguments for `setupDelegation`. */
192
+ export interface SetupDelegationParams {
193
+ /** The api key id to bind the delegation to (the `:keyId` path segment). */
194
+ keyId: string;
195
+ /** µ-USDC atomic amount to deposit into the user's escrow, base10 string. */
196
+ amountToFund: string;
197
+ /** Total delegated cap (µ¢ ceiling for the rolling cap ledger), base10 string. */
198
+ cap: string;
199
+ /** Slot height after which the delegation/session key is dead, base10 string. */
200
+ expiresAtSlot: string;
201
+ /** The escrow owner's wallet address (base58). */
202
+ walletAddress: string;
203
+ /** The Flex facilitator address (base58). */
204
+ facilitatorAddress: string;
205
+ /** USDC mint (defaults to mainnet USDC). */
206
+ mint?: string;
207
+ /** Revocation grace period for the registered session key. */
208
+ revocationGracePeriodSlots?: number;
209
+ /**
210
+ * The escrow owner's `@solana/kit` TransactionSigner. Used ONLY locally to
211
+ * sign the escrow create/fund + session-key registration — NEVER sent to
212
+ * the backend. The wallet private key never leaves the user.
213
+ */
214
+ signer: TransactionSignerOpaque;
215
+ /** The caller's `@solana/kit` RPC client. */
216
+ rpc: unknown;
217
+ }
218
+ /** Structured result of a completed delegation setup. */
219
+ export interface SetupDelegationResult {
220
+ /** The user's escrow PDA (funds live here, owner = the user's wallet). */
221
+ escrowAddress: string;
222
+ /** The platform's session key now registered (bounded) against the escrow. */
223
+ sessionKeyAddress: string;
224
+ /** Total delegated cap bound to the api key (µ¢, base10 string). */
225
+ cap: string;
226
+ /** Slot height after which the delegation expires (base10 string). */
227
+ expiresAtSlot: string;
228
+ /** Tx signature of the escrow create+fund (user-signed). */
229
+ fundTxSignature: string;
230
+ /** Tx signature of the session-key registration (user-signed). */
231
+ registerTxSignature: string;
232
+ }
233
+ /**
234
+ * Enable the non-custodial delegation lane end-to-end (design §3, steps a→c).
235
+ *
236
+ * Composes the existing Flex primitives + the backend bind route so a user
237
+ * can let their `api_key` pay x402 challenges from their OWN escrow without
238
+ * the platform ever custodying funds or the wallet key:
239
+ *
240
+ * 1. GET the platform's delegation session pubkey
241
+ * (`GET /v1/account/delegation/session-key` → `{ session_key_address, ready }`).
242
+ * Throws if the platform isn't ready to delegate.
243
+ * 2. `fundEscrow(...)` — the user's wallet signs to create+fund their OWN
244
+ * escrow PDA. Funds stay with the user.
245
+ * 3. `registerSessionKey(...)` — the user's wallet registers the PLATFORM's
246
+ * session key (from step 1) against that escrow, cap-bounded + expiring.
247
+ * 4. POST `/v1/account/keys/:keyId/delegation` to bind it to the api key.
248
+ *
249
+ * The wallet `signer` is used ONLY in steps 2 and 3 (local signing). It is
250
+ * never passed to the backend — the platform only ever receives the escrow
251
+ * address, the platform's own session-key pubkey, the cap, and the expiry.
252
+ */
253
+ export declare function setupDelegation(client: DelegationClientLike, params: SetupDelegationParams): Promise<SetupDelegationResult>;
254
+ //# sourceMappingURL=flex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flex.d.ts","sourceRoot":"","sources":["../src/flex.ts"],"names":[],"mappings":"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"}
package/dist/flex.js ADDED
@@ -0,0 +1,396 @@
1
+ /**
2
+ * Flex payment primitives — typed shapes for the @faremeter/flex-solana
3
+ * escrow-and-session-key payment scheme. Day-4: real wiring against
4
+ * @faremeter/flex-solana@0.2.1 (peer + optional dep so SDK callers who only
5
+ * use free routes don't pull @solana/kit).
6
+ *
7
+ * Aligned with the package's exported wire shape:
8
+ * - `FlexPaymentPayload` (`@faremeter/flex-solana/types`) =
9
+ * { escrow, mint, maxAmount, authorizationId, expiresAtSlot,
10
+ * splits[], sessionKey, signature }
11
+ * - `FlexPaymentRequirementsExtra` (`@faremeter/flex-solana/types`) =
12
+ * { facilitator, supportedMints[], splits[], escrow?, minGracePeriodSlots? }
13
+ * - `SplitInput` (`@faremeter/flex-solana/authorization`) =
14
+ * { recipient: Address, bps: number }
15
+ *
16
+ * IMPORTANT (tree-shake invariant from Day 3): this file MUST NOT import
17
+ * @faremeter/flex-solana at top-level. All package references go through
18
+ * `await import("@faremeter/flex-solana")` inside function bodies so SDK
19
+ * callers who never sign Flex authorizations don't pull @solana/kit (~3MB).
20
+ */
21
+ /** USDC SPL mint addresses. v6.16 defaults to mainnet; callers override per-env. */
22
+ export const USDC_MINT_MAINNET = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
23
+ export const USDC_MINT_DEVNET = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
24
+ function randomAuthorizationId() {
25
+ // 8 random bytes → bigint (u64) → base10 string. Matches
26
+ // `SerializePaymentAuthorizationArgs.authorizationId: bigint` at the boundary.
27
+ const bytes = new Uint8Array(8);
28
+ crypto.getRandomValues(bytes);
29
+ let n = 0n;
30
+ for (const b of bytes)
31
+ n = (n << 8n) | BigInt(b);
32
+ return n.toString(10);
33
+ }
34
+ function validateSplits(splits) {
35
+ if (!Array.isArray(splits) || splits.length === 0 || splits.length > 5) {
36
+ throw new Error(`flex: splits must be 1..5 entries, got ${splits?.length ?? 0}`);
37
+ }
38
+ const sum = splits.reduce((s, e) => s + e.bps, 0);
39
+ if (sum !== 10_000) {
40
+ throw new Error(`flex: splits bps must sum to 10000, got ${sum}`);
41
+ }
42
+ for (const s of splits) {
43
+ if (!s.recipient || typeof s.recipient !== "string") {
44
+ throw new Error("flex: split.recipient must be a base58 address");
45
+ }
46
+ if (!Number.isInteger(s.bps) || s.bps < 1 || s.bps > 10_000) {
47
+ throw new Error(`flex: split.bps must be int in [1,10000], got ${s.bps}`);
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * Build an unsigned `FlexAuthorization`. Validates splits sum, generates
53
+ * a random `authorizationId`, and returns the canonical JSON shape.
54
+ *
55
+ * Cited symbol: shape mirrors `FlexPaymentPayload` from
56
+ * `@faremeter/flex-solana/types` (minus wire-only `sessionKey`/`signature`).
57
+ */
58
+ export async function buildFlexAuthorization(opts) {
59
+ if (!opts.escrow)
60
+ throw new Error("flex: escrow required");
61
+ if (!opts.mint)
62
+ throw new Error("flex: mint required");
63
+ if (!/^\d+$/.test(opts.maxAmount)) {
64
+ throw new Error("flex: maxAmount must be base10 string");
65
+ }
66
+ if (!/^\d+$/.test(opts.expiresAtSlot)) {
67
+ throw new Error("flex: expiresAtSlot must be base10 string");
68
+ }
69
+ validateSplits(opts.splits);
70
+ return {
71
+ escrow: opts.escrow,
72
+ mint: opts.mint,
73
+ maxAmount: opts.maxAmount,
74
+ authorizationId: randomAuthorizationId(),
75
+ expiresAtSlot: opts.expiresAtSlot,
76
+ splits: opts.splits,
77
+ };
78
+ }
79
+ /**
80
+ * Settle a `PaymentRequiredError` whose `accepts[]` advertises a Flex
81
+ * scheme. Picks the first Flex-shaped requirement (one whose
82
+ * `extra.escrow` and `extra.splits` are populated — matches
83
+ * `FlexPaymentRequirementsExtra` from `@faremeter/flex-solana/types`),
84
+ * has the wallet sign, packs as `FlexPaymentPayload` inside an
85
+ * `X402PaymentPayload`, base64-encodes, and replays via
86
+ * `retry(paymentHeader)`.
87
+ */
88
+ export async function payAndRetryFlex(error, wallet, retry) {
89
+ const accepts = error.accepts ?? [];
90
+ // Pick the first requirement whose `extra` carries Flex-shape fields.
91
+ // Backend will set scheme="@faremeter/flex" but we don't gate on the
92
+ // scheme string — `extra` shape is the source of truth.
93
+ const requirement = accepts.find((r) => {
94
+ const extra = r.extra;
95
+ return !!extra?.escrow && Array.isArray(extra?.splits);
96
+ });
97
+ if (!requirement)
98
+ throw error;
99
+ const extra = requirement.extra;
100
+ // We need an expiresAtSlot. Prefer extra.expiresAtSlot; else derive
101
+ // from minGracePeriodSlots + a sentinel (caller can refine).
102
+ const expiresAtSlot = extra.expiresAtSlot ?? extra.minGracePeriodSlots ?? "0";
103
+ const auth = await buildFlexAuthorization({
104
+ escrow: extra.escrow,
105
+ mint: USDC_MINT_MAINNET,
106
+ maxAmount: requirement.maxAmountRequired,
107
+ splits: extra.splits,
108
+ expiresAtSlot,
109
+ });
110
+ const signature = await wallet.signFlexAuthorization(auth);
111
+ // Wire shape: `FlexPaymentPayload` from @faremeter/flex-solana/types.
112
+ // sessionKey + signature added here (signing-time fields).
113
+ const flexPayload = {
114
+ escrow: auth.escrow,
115
+ mint: auth.mint,
116
+ maxAmount: auth.maxAmount,
117
+ authorizationId: auth.authorizationId,
118
+ expiresAtSlot: auth.expiresAtSlot,
119
+ splits: auth.splits,
120
+ sessionKey: wallet.sessionKeyAddress,
121
+ signature,
122
+ };
123
+ // x402 envelope (`X402PaymentPayload` from `./x402.ts` = standard
124
+ // x402 wire format: {x402Version, scheme, network, payload}).
125
+ const envelope = {
126
+ x402Version: 1,
127
+ scheme: requirement.scheme,
128
+ network: requirement.network,
129
+ payload: flexPayload,
130
+ };
131
+ // X-PAYMENT header value: base64-encoded JSON of the envelope.
132
+ // Matches what the backend's Flex facilitator decodes.
133
+ const headerValue = base64UrlSafeEncodeJson(envelope);
134
+ return retry(headerValue);
135
+ }
136
+ function base64UrlSafeEncodeJson(obj) {
137
+ const json = JSON.stringify(obj);
138
+ // Cross-runtime base64: prefer Buffer (Node), else btoa (browser/edge).
139
+ // Standard x402 uses regular base64 (not url-safe), so we match that.
140
+ // deno-lint-ignore no-explicit-any
141
+ const g = globalThis;
142
+ if (g.Buffer?.from)
143
+ return g.Buffer.from(json, "utf8").toString("base64");
144
+ if (typeof g.btoa === "function") {
145
+ // btoa needs a binary string
146
+ const bytes = new TextEncoder().encode(json);
147
+ let bin = "";
148
+ for (const b of bytes)
149
+ bin += String.fromCharCode(b);
150
+ return g.btoa(bin);
151
+ }
152
+ throw new Error("flex: no base64 encoder available in this runtime");
153
+ }
154
+ /**
155
+ * Pure tx-builder for create-escrow + deposit (no signer required, no
156
+ * network call). Returns instructions a caller can sign + send with any
157
+ * `@solana/kit`-compatible runtime. Lazy-imports @faremeter/flex-solana
158
+ * so tree-shake is preserved.
159
+ *
160
+ * Cited symbols: `getCreateEscrowInstructionAsync`, `getDepositInstructionAsync`
161
+ * (both from `@faremeter/flex-solana` root export, per
162
+ * `/tmp/flex-probe/.../flex-solana/dist/src/index.d.ts:9`).
163
+ */
164
+ export async function buildEscrowCreationTx(params) {
165
+ if (!params.walletAddress)
166
+ throw new Error("flex: walletAddress required");
167
+ if (!params.facilitatorAddress) {
168
+ throw new Error("flex: facilitatorAddress required");
169
+ }
170
+ if (!/^\d+$/.test(params.amountUsdc)) {
171
+ throw new Error("flex: amountUsdc must be base10 atomic-unit string");
172
+ }
173
+ const mint = params.mint ?? USDC_MINT_MAINNET;
174
+ const refundTimeoutSlots = BigInt(params.refundTimeoutSlots ?? 150);
175
+ const deadmanTimeoutSlots = BigInt(params.deadmanTimeoutSlots ?? 432000);
176
+ const index = BigInt(Date.now()); // monotonic-ish nonce for PDA derivation
177
+ // Lazy import — tree-shake invariant.
178
+ const flex = await import("@faremeter/flex-solana");
179
+ if (!params.signer) {
180
+ // Pure-build mode: we can't produce a `TransactionSigner` from a string
181
+ // address. Caller must build the tx with their own kit-compatible signer.
182
+ // Return the intended shape so the caller can mirror this with their
183
+ // own `getCreateEscrowInstructionAsync` call.
184
+ return {
185
+ instructions: [],
186
+ accountsAtRisk: [
187
+ params.walletAddress,
188
+ params.facilitatorAddress,
189
+ mint,
190
+ ],
191
+ intent: "create_escrow_and_deposit",
192
+ };
193
+ }
194
+ const createIx = await flex.getCreateEscrowInstructionAsync({
195
+ owner: params.signer,
196
+ index,
197
+ facilitator: params.facilitatorAddress,
198
+ refundTimeoutSlots,
199
+ deadmanTimeoutSlots,
200
+ maxSessionKeys: 5,
201
+ });
202
+ const depositIx = await flex.getDepositInstructionAsync({
203
+ depositor: params.signer,
204
+ escrow: (createIx.accounts[1].address),
205
+ mint: mint,
206
+ source: params.walletAddress, // caller should pass the wallet ATA
207
+ amount: BigInt(params.amountUsdc),
208
+ });
209
+ return {
210
+ instructions: [createIx, depositIx],
211
+ accountsAtRisk: [
212
+ params.walletAddress,
213
+ params.facilitatorAddress,
214
+ mint,
215
+ String(createIx.accounts[1].address ?? ""),
216
+ ],
217
+ intent: "create_escrow_and_deposit",
218
+ };
219
+ }
220
+ /**
221
+ * Pure tx-builder for register-session-key (no signer required).
222
+ *
223
+ * Cited symbol: `getRegisterSessionKeyInstructionAsync` from
224
+ * `@faremeter/flex-solana` root export.
225
+ */
226
+ export async function buildSessionKeyRegistrationTx(params) {
227
+ if (!params.walletAddress)
228
+ throw new Error("flex: walletAddress required");
229
+ if (!params.escrowAddress)
230
+ throw new Error("flex: escrowAddress required");
231
+ if (!params.sessionKeyAddress) {
232
+ throw new Error("flex: sessionKeyAddress required");
233
+ }
234
+ const flex = await import("@faremeter/flex-solana");
235
+ if (!params.signer) {
236
+ return {
237
+ instructions: [],
238
+ accountsAtRisk: [
239
+ params.walletAddress,
240
+ params.escrowAddress,
241
+ params.sessionKeyAddress,
242
+ ],
243
+ intent: "register_session_key",
244
+ };
245
+ }
246
+ const ix = await flex.getRegisterSessionKeyInstructionAsync({
247
+ owner: params.signer,
248
+ escrow: params.escrowAddress,
249
+ sessionKey: params.sessionKeyAddress,
250
+ expiresAtSlot: params.expiresAtSlot ? BigInt(params.expiresAtSlot) : null,
251
+ revocationGracePeriodSlots: BigInt(params.revocationGracePeriodSlots ?? 0),
252
+ });
253
+ return {
254
+ instructions: [ix],
255
+ accountsAtRisk: [
256
+ params.walletAddress,
257
+ params.escrowAddress,
258
+ params.sessionKeyAddress,
259
+ ],
260
+ intent: "register_session_key",
261
+ };
262
+ }
263
+ /**
264
+ * Assemble → sign → send → confirm a flex transaction via the caller's
265
+ * `@solana/kit` RPC. Uses raw `sendTransaction` + `getSignatureStatuses`
266
+ * polling (no rpcSubscriptions dependency), mirroring the facilitator's
267
+ * own submit path. Lazy-imports @solana/kit to preserve tree-shake.
268
+ *
269
+ * Proven end-to-end against the deployed FLEX program on devnet
270
+ * (`scripts/flex-devnet-settle.mjs`): create-escrow → deposit → register
271
+ * session key → settle → finalize → recipient credited.
272
+ */
273
+ async function sendFlexTx(rpc, feePayerSigner, instructions, opts = {}) {
274
+ const kit = await import("@solana/kit");
275
+ const { value: blockhash } = await rpc.getLatestBlockhash().send();
276
+ const msg = kit.pipe(kit.createTransactionMessage({ version: 0 }), (m) => kit.setTransactionMessageFeePayerSigner(feePayerSigner, m), (m) => kit.setTransactionMessageLifetimeUsingBlockhash(blockhash, m), (m) => kit.appendTransactionMessageInstructions(instructions, m));
277
+ const signed = await kit.signTransactionMessageWithSigners(msg);
278
+ const sig = kit.getSignatureFromTransaction(signed);
279
+ const wire = kit.getBase64EncodedWireTransaction(signed);
280
+ await rpc.sendTransaction(wire, { encoding: "base64" }).send();
281
+ const maxPolls = opts.maxPolls ?? 30;
282
+ const pollDelayMs = opts.pollDelayMs ?? 1000;
283
+ for (let i = 0; i < maxPolls; i++) {
284
+ const status = (await rpc.getSignatureStatuses([sig]).send()).value[0];
285
+ if (status?.err)
286
+ throw new Error(`flex tx ${sig} failed: ${JSON.stringify(status.err)}`);
287
+ if (status?.confirmationStatus === "confirmed" || status?.confirmationStatus === "finalized")
288
+ return sig;
289
+ await new Promise((r) => setTimeout(r, pollDelayMs));
290
+ }
291
+ throw new Error(`flex tx ${sig} not confirmed after ${maxPolls} polls`);
292
+ }
293
+ /**
294
+ * Send a create-escrow + deposit transaction. Requires `params.signer`
295
+ * (a `@solana/kit` TransactionSigner) and `params.rpc` (a kit RPC client).
296
+ * Without them, throws `requires_signer` and points at `buildEscrowCreationTx`
297
+ * for the pure-build path.
298
+ */
299
+ export async function fundEscrow(params) {
300
+ if (!params.signer || !params.rpc) {
301
+ throw new Error("flex.fundEscrow: requires_signer — caller must supply " +
302
+ "`signer` (TransactionSigner) and `rpc` (kit RPC client). For " +
303
+ "pure tx construction without sending, use `buildEscrowCreationTx`.");
304
+ }
305
+ const built = await buildEscrowCreationTx(params);
306
+ // accountsAtRisk = [wallet, facilitator, mint, escrowPDA]; the escrow PDA is last.
307
+ const escrowAddress = built.accountsAtRisk[built.accountsAtRisk.length - 1];
308
+ const txSignature = await sendFlexTx(params.rpc, params.signer, built.instructions);
309
+ return { escrowAddress, txSignature };
310
+ }
311
+ /**
312
+ * Send a register-session-key transaction. Same `requires_signer` contract
313
+ * as `fundEscrow`. Returns the confirmed transaction signature.
314
+ */
315
+ export async function registerSessionKey(params) {
316
+ if (!params.signer || !params.rpc) {
317
+ throw new Error("flex.registerSessionKey: requires_signer — caller must supply " +
318
+ "`signer` (TransactionSigner) and `rpc` (kit RPC client). For " +
319
+ "pure tx construction without sending, use `buildSessionKeyRegistrationTx`.");
320
+ }
321
+ const built = await buildSessionKeyRegistrationTx(params);
322
+ const txSignature = await sendFlexTx(params.rpc, params.signer, built.instructions);
323
+ return { txSignature };
324
+ }
325
+ /**
326
+ * Enable the non-custodial delegation lane end-to-end (design §3, steps a→c).
327
+ *
328
+ * Composes the existing Flex primitives + the backend bind route so a user
329
+ * can let their `api_key` pay x402 challenges from their OWN escrow without
330
+ * the platform ever custodying funds or the wallet key:
331
+ *
332
+ * 1. GET the platform's delegation session pubkey
333
+ * (`GET /v1/account/delegation/session-key` → `{ session_key_address, ready }`).
334
+ * Throws if the platform isn't ready to delegate.
335
+ * 2. `fundEscrow(...)` — the user's wallet signs to create+fund their OWN
336
+ * escrow PDA. Funds stay with the user.
337
+ * 3. `registerSessionKey(...)` — the user's wallet registers the PLATFORM's
338
+ * session key (from step 1) against that escrow, cap-bounded + expiring.
339
+ * 4. POST `/v1/account/keys/:keyId/delegation` to bind it to the api key.
340
+ *
341
+ * The wallet `signer` is used ONLY in steps 2 and 3 (local signing). It is
342
+ * never passed to the backend — the platform only ever receives the escrow
343
+ * address, the platform's own session-key pubkey, the cap, and the expiry.
344
+ */
345
+ export async function setupDelegation(client, params) {
346
+ if (!params.keyId)
347
+ throw new Error("flex.setupDelegation: keyId required");
348
+ if (!params.signer || !params.rpc) {
349
+ throw new Error("flex.setupDelegation: requires_signer — caller must supply " +
350
+ "`signer` (TransactionSigner) and `rpc` (kit RPC client) so the " +
351
+ "user's wallet can sign the escrow + session-key registration locally.");
352
+ }
353
+ // ── Step 1: GET the platform's delegation session pubkey ──────────────────
354
+ const session = await client.request("GET", "/v1/account/delegation/session-key");
355
+ if (!session?.ready || !session.session_key_address) {
356
+ throw new Error("flex.setupDelegation: platform delegation session key is not ready " +
357
+ `(ready=${session?.ready}). Cannot register a delegation without it.`);
358
+ }
359
+ const sessionKeyAddress = session.session_key_address;
360
+ // ── Step 2: user's wallet creates + funds its OWN escrow (signs locally) ──
361
+ const { escrowAddress, txSignature: fundTxSignature } = await fundEscrow({
362
+ walletAddress: params.walletAddress,
363
+ facilitatorAddress: params.facilitatorAddress,
364
+ amountUsdc: params.amountToFund,
365
+ mint: params.mint,
366
+ signer: params.signer,
367
+ rpc: params.rpc,
368
+ });
369
+ // ── Step 3: user's wallet registers the PLATFORM session key (signs locally)
370
+ const { txSignature: registerTxSignature } = await registerSessionKey({
371
+ walletAddress: params.walletAddress,
372
+ escrowAddress,
373
+ sessionKeyAddress,
374
+ expiresAtSlot: params.expiresAtSlot,
375
+ revocationGracePeriodSlots: params.revocationGracePeriodSlots,
376
+ signer: params.signer,
377
+ rpc: params.rpc,
378
+ });
379
+ // ── Step 4: bind the delegation to the api key (no signer crosses here) ───
380
+ await client.request("POST", `/v1/account/keys/${encodeURIComponent(params.keyId)}/delegation`, {
381
+ wallet: params.walletAddress,
382
+ escrow: escrowAddress,
383
+ session_key_address: sessionKeyAddress,
384
+ cap_uc: params.cap,
385
+ expires_at_slot: params.expiresAtSlot,
386
+ });
387
+ return {
388
+ escrowAddress,
389
+ sessionKeyAddress,
390
+ cap: params.cap,
391
+ expiresAtSlot: params.expiresAtSlot,
392
+ fundTxSignature,
393
+ registerTxSignature,
394
+ };
395
+ }
396
+ //# sourceMappingURL=flex.js.map