@heyanon-arp/sdk 0.0.2 → 0.0.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/index.js CHANGED
@@ -645,6 +645,43 @@ function expiresAt(ttlSeconds, now = /* @__PURE__ */ new Date()) {
645
645
  return rfc3339(new Date(now.getTime() + ttlSeconds * 1e3));
646
646
  }
647
647
 
648
+ // src/utils/poll.ts
649
+ async function pollUntil(opts) {
650
+ const intervalMs = Math.max(100, opts.intervalMs ?? 3e3);
651
+ const timeoutMs = Math.max(intervalMs, opts.timeoutMs ?? 3e5);
652
+ const deadline = Date.now() + timeoutMs;
653
+ let last;
654
+ if (opts.abortSignal?.aborted) return { kind: "aborted", last: void 0 };
655
+ for (; ; ) {
656
+ try {
657
+ last = await opts.fetch();
658
+ if (opts.predicate(last)) return { kind: "matched", value: last };
659
+ } catch (err) {
660
+ if (!opts.swallowFetchErrors) throw err;
661
+ opts.onFetchError?.(err);
662
+ }
663
+ const remaining = deadline - Date.now();
664
+ if (remaining <= 0) return { kind: "timeout", last };
665
+ const sleepMs = Math.min(intervalMs, remaining);
666
+ await sleepWithAbort(sleepMs, opts.abortSignal);
667
+ if (opts.abortSignal?.aborted) return { kind: "aborted", last };
668
+ }
669
+ }
670
+ function sleepWithAbort(ms, signal) {
671
+ if (signal?.aborted) return Promise.resolve();
672
+ return new Promise((resolve) => {
673
+ const timer = setTimeout(() => {
674
+ signal?.removeEventListener("abort", onAbort);
675
+ resolve();
676
+ }, ms);
677
+ const onAbort = () => {
678
+ clearTimeout(timer);
679
+ resolve();
680
+ };
681
+ signal?.addEventListener("abort", onAbort, { once: true });
682
+ });
683
+ }
684
+
648
685
  // src/types/body.ts
649
686
  var DECLINE_REASONS = ["missing_brief", "rate_too_low", "out_of_scope", "policy", "expired_proposal", "capacity", "unspecified", "other"];
650
687
  function isDeclineReason(v) {
@@ -830,6 +867,7 @@ exports.isDeclineReason = isDeclineReason;
830
867
  exports.isValidDid = isValidDid;
831
868
  exports.parseCaip19SolanaAssetId = parseCaip19SolanaAssetId;
832
869
  exports.parseDid = parseDid;
870
+ exports.pollUntil = pollUntil;
833
871
  exports.resolveAsset = resolveAsset;
834
872
  exports.rfc3339 = rfc3339;
835
873
  exports.scryptPasswordProofSign = scryptPasswordProofSign;
package/dist/index.mjs CHANGED
@@ -620,6 +620,43 @@ function expiresAt(ttlSeconds, now = /* @__PURE__ */ new Date()) {
620
620
  return rfc3339(new Date(now.getTime() + ttlSeconds * 1e3));
621
621
  }
622
622
 
623
+ // src/utils/poll.ts
624
+ async function pollUntil(opts) {
625
+ const intervalMs = Math.max(100, opts.intervalMs ?? 3e3);
626
+ const timeoutMs = Math.max(intervalMs, opts.timeoutMs ?? 3e5);
627
+ const deadline = Date.now() + timeoutMs;
628
+ let last;
629
+ if (opts.abortSignal?.aborted) return { kind: "aborted", last: void 0 };
630
+ for (; ; ) {
631
+ try {
632
+ last = await opts.fetch();
633
+ if (opts.predicate(last)) return { kind: "matched", value: last };
634
+ } catch (err) {
635
+ if (!opts.swallowFetchErrors) throw err;
636
+ opts.onFetchError?.(err);
637
+ }
638
+ const remaining = deadline - Date.now();
639
+ if (remaining <= 0) return { kind: "timeout", last };
640
+ const sleepMs = Math.min(intervalMs, remaining);
641
+ await sleepWithAbort(sleepMs, opts.abortSignal);
642
+ if (opts.abortSignal?.aborted) return { kind: "aborted", last };
643
+ }
644
+ }
645
+ function sleepWithAbort(ms, signal) {
646
+ if (signal?.aborted) return Promise.resolve();
647
+ return new Promise((resolve) => {
648
+ const timer = setTimeout(() => {
649
+ signal?.removeEventListener("abort", onAbort);
650
+ resolve();
651
+ }, ms);
652
+ const onAbort = () => {
653
+ clearTimeout(timer);
654
+ resolve();
655
+ };
656
+ signal?.addEventListener("abort", onAbort, { once: true });
657
+ });
658
+ }
659
+
623
660
  // src/types/body.ts
624
661
  var DECLINE_REASONS = ["missing_brief", "rate_too_low", "out_of_scope", "policy", "expired_proposal", "capacity", "unspecified", "other"];
625
662
  function isDeclineReason(v) {
@@ -758,4 +795,4 @@ function computeCreateLockDiscriminator() {
758
795
  return h.slice(0, 8);
759
796
  }
760
797
 
761
- export { CAIP19_REGEX, COSIGNATURE_PURPOSES, CREATE_LOCK_DISCRIMINATOR, DECLINE_REASONS, PROTECTED_PURPOSES, PURPOSE_PARTIAL_RELEASE_STRING, PURPOSE_REFUND_STRING, PURPOSE_RELEASE_STRING, Purpose, REFUND_REASON_BYTES, SCRYPT_PARAMS, SETTLEMENT_PURPOSES, SLIP44_SOLANA, SOLANA_CLUSTER_IDS, SPL_TOKEN_PROGRAM_ID_BASE58, TOKEN_2022_PROGRAM_ID_BASE58, USDC_MINTS, WELL_KNOWN_ASSETS, WELL_KNOWN_ASSET_KEYS, base58btcDecode, base58btcEncode, buildCreateLockIxData, buildPartialReleaseDigest, buildRefundDigest, buildReleaseDigest, buildWebhookSignatureHeader, bytes16ToDelegationId, canonicalBytes, canonicalJson, canonicalSha256Hex, computeCreateLockDiscriminator, delegationIdToBytes16, deriveConditionHash, deriveLockId, deriveScryptKey, detectTokenProgramFromOwner, detectTokenProgramFromOwnerBytes, expiresAt, findFirstChainDivergence, formatDid, generateKeyPair, getPublicKey2 as getPublicKey, isAssetIdentifier, isDeclineReason, isValidDid, parseCaip19SolanaAssetId, parseDid, resolveAsset, rfc3339, scryptPasswordProofSign, scryptPasswordProofVerify, senderNonce, serverEventHash, sign2 as sign, signChallenge, signCosignature, signEnvelope, signKeyLinkAttestation, signKeyRotationAttestation, signedMessageHash, uuidV4, verify2 as verify, verifyChallenge, verifyCosignature, verifyEnvelope, verifyKeyLinkAttestation, verifyKeyRotationAttestation, verifyWebhookSignatureHeader };
798
+ export { CAIP19_REGEX, COSIGNATURE_PURPOSES, CREATE_LOCK_DISCRIMINATOR, DECLINE_REASONS, PROTECTED_PURPOSES, PURPOSE_PARTIAL_RELEASE_STRING, PURPOSE_REFUND_STRING, PURPOSE_RELEASE_STRING, Purpose, REFUND_REASON_BYTES, SCRYPT_PARAMS, SETTLEMENT_PURPOSES, SLIP44_SOLANA, SOLANA_CLUSTER_IDS, SPL_TOKEN_PROGRAM_ID_BASE58, TOKEN_2022_PROGRAM_ID_BASE58, USDC_MINTS, WELL_KNOWN_ASSETS, WELL_KNOWN_ASSET_KEYS, base58btcDecode, base58btcEncode, buildCreateLockIxData, buildPartialReleaseDigest, buildRefundDigest, buildReleaseDigest, buildWebhookSignatureHeader, bytes16ToDelegationId, canonicalBytes, canonicalJson, canonicalSha256Hex, computeCreateLockDiscriminator, delegationIdToBytes16, deriveConditionHash, deriveLockId, deriveScryptKey, detectTokenProgramFromOwner, detectTokenProgramFromOwnerBytes, expiresAt, findFirstChainDivergence, formatDid, generateKeyPair, getPublicKey2 as getPublicKey, isAssetIdentifier, isDeclineReason, isValidDid, parseCaip19SolanaAssetId, parseDid, pollUntil, resolveAsset, rfc3339, scryptPasswordProofSign, scryptPasswordProofVerify, senderNonce, serverEventHash, sign2 as sign, signChallenge, signCosignature, signEnvelope, signKeyLinkAttestation, signKeyRotationAttestation, signedMessageHash, uuidV4, verify2 as verify, verifyChallenge, verifyCosignature, verifyEnvelope, verifyKeyLinkAttestation, verifyKeyRotationAttestation, verifyWebhookSignatureHeader };
@@ -238,11 +238,117 @@ export interface DisputeContent {
238
238
  evidence_refs?: string[];
239
239
  response_text?: string;
240
240
  }
241
+ /**
242
+ * `settlement_signature` — payee's settlement-key signature on a
243
+ * release-digest, sent as a STAND-ALONE envelope AFTER the matching
244
+ * receipt-propose envelope is committed.
245
+ *
246
+ * Why a separate envelope (and not an attachment on receipt-propose)?
247
+ * The release digest (`buildReleaseDigest` / `buildPartialReleaseDigest`
248
+ * in `packages/sdk/src/settlement/settlement.ts`) takes `receiptEventHash`
249
+ * as one of its inputs. `receipt_event_hash` is the SERVER-ASSIGNED
250
+ * hash of the receipt envelope — it is computed only AFTER server-side
251
+ * validation completes (it includes server-assigned `prev_server_event_hash`
252
+ * + `server_timestamp` in its canonical input). The payee CANNOT
253
+ * compute the digest at receipt-propose time because the digest input
254
+ * does not yet exist. So the wire flow is necessarily two envelopes:
255
+ *
256
+ * 1. Payee sends `receipt` (propose) — server commits + returns
257
+ * `IngestResultDto.serverEventHash`.
258
+ * 2. Payee reads `serverEventHash`, computes the release digest,
259
+ * signs it, and sends THIS `settlement_signature` envelope.
260
+ * 3. Buyer reads the `settlement_signature` envelope from inbox SSE
261
+ * (sender-recipient routing is standard), then assembles the
262
+ * `attachments.settlement_signatures` block on the cosign envelope.
263
+ *
264
+ * This body type carries ONLY the payee side. The payer's settlement
265
+ * sig still lands on the cosign envelope's `attachments` block — that
266
+ * stays unchanged. The server's handler for `settlement_signature`
267
+ * also performs Ed25519 verification of the payee sig against the
268
+ * reconstructed digest as defence-in-depth: a bad sig fails loudly on
269
+ * the worker side instead of stranding the buyer's cosign later.
270
+ *
271
+ * Refund flow is NOT served by this body type — `buildRefundDigest`
272
+ * does NOT bind to `receipt_event_hash` (it binds to lock-derived
273
+ * fields only), so refund signatures can be computed independent of
274
+ * any envelope timing and ride their own dedicated paths. The
275
+ * `purpose` field below is intentionally narrowed to RELEASE +
276
+ * PARTIAL_RELEASE for the same reason — REFUND-v1 sigs do not belong
277
+ * on this envelope.
278
+ */
279
+ export interface SettlementSignatureBody extends Body<SettlementSignatureContent> {
280
+ type: 'settlement_signature';
281
+ }
282
+ export interface SettlementSignatureContent {
283
+ /** The delegation whose receipt is being settlement-signed. */
284
+ delegation_id: string;
285
+ /**
286
+ * Server-assigned hash of the receipt-propose envelope this signature
287
+ * settles. Must match `Receipt.receiptEventHash` server-side; that is
288
+ * the value the server's cosign-validator feeds into the digest
289
+ * reconstruction as `receiptEventHash`.
290
+ */
291
+ receipt_event_hash: Sha256Hex;
292
+ /**
293
+ * Which digest the `sig` was computed over. Narrowed to RELEASE +
294
+ * PARTIAL_RELEASE: REFUND-v1 sigs ride other paths because the
295
+ * refund digest is independent of `receipt_event_hash` and never
296
+ * needs this envelope as a transport.
297
+ */
298
+ purpose: 'ARP-SOLANA-RELEASE-v1.5' | 'ARP-SOLANA-PARTIAL-RELEASE-v1.5';
299
+ /**
300
+ * Payee's settlement-key public key (base58, 32 bytes raw). The
301
+ * server cross-checks this against the registered settlement key on
302
+ * the payee's DID document before re-verifying the sig — a sig
303
+ * signed by an unregistered key is rejected even if it
304
+ * mathematically verifies.
305
+ */
306
+ payee_settlement_pubkey: string;
307
+ /**
308
+ * Ed25519 sig over the reconstructed release/partial-release digest,
309
+ * **raw base64 — NO `ed25519:` prefix.** Wire format intentionally
310
+ * mismatches the SDK's prefixed `Ed25519Sig` type because the buyer
311
+ * forwards this exact string verbatim into
312
+ * `attachments.settlement_signatures.payee.sig` at cosign time, and
313
+ * the server's `ReceiptCosignValidatorService.parseBase64Sig`
314
+ * (`apps/arp-server/src/escrow/services/receipt-cosign-validator.service.ts:557-565`)
315
+ * `Buffer.from(input, 'base64')` directly — no prefix stripping.
316
+ * Aligns with the CLI's `--payer-settlement-sig <base64>` /
317
+ * `--payee-settlement-sig <base64>` flags that consumers already
318
+ * wire to (see `packages/cli/src/commands/receipt.ts:670-673`).
319
+ *
320
+ * Server re-builds the same digest from lock + receipt + envelope
321
+ * content and verifies this sig as defence-in-depth at handler time
322
+ * — a malformed or wrong-digest sig is rejected pre-projection so
323
+ * the bad receipt sig never reaches the buyer's cosign attempt.
324
+ */
325
+ sig: string;
326
+ /**
327
+ * Unix seconds — the `expires_at` value the payee bound into the
328
+ * digest. Server re-uses it when reconstructing the digest and
329
+ * cross-checks against `expires_at > now` + `expires_at <=
330
+ * lock.expiry - DISPUTE_BUFFER_SECONDS` (ADR-12 §4). The payer
331
+ * echoes the SAME value on the cosign envelope's settlement_signatures
332
+ * block so both sides sign the same bytes.
333
+ */
334
+ expires_at: number;
335
+ /**
336
+ * Base-unit decimal-integer string — required when `purpose ===
337
+ * 'ARP-SOLANA-PARTIAL-RELEASE-v1.5'`, omitted (or undefined) for
338
+ * full RELEASE. The server cross-checks it against
339
+ * `receipt.usage.computed_amount` for usage_based contracts before
340
+ * verifying the digest (same invariant
341
+ * `ESC_USAGE_COMPUTED_AMOUNT_MISMATCH` already guards on the
342
+ * propose-time receipt body).
343
+ */
344
+ payee_amount?: string;
345
+ [extra: string]: unknown;
346
+ }
241
347
  /**
242
348
  * Union over every standard body type. Consumers can narrow on
243
349
  * `body.type` via discriminated dispatch.
244
350
  */
245
- export type AnyBody = HandshakeBody | HandshakeResponseBody | ContractBody | DelegationBody | WorkRequestBody | WorkResponseBody | ReceiptBody | MemoryDeltaBody | DisputeBody;
351
+ export type AnyBody = HandshakeBody | HandshakeResponseBody | ContractBody | DelegationBody | WorkRequestBody | WorkResponseBody | ReceiptBody | MemoryDeltaBody | DisputeBody | SettlementSignatureBody;
246
352
  /** Receipt co-signature payload — what gets `payload_hash`'d in `attachments.co_signature`. */
247
353
  export interface ReceiptCosignPayload {
248
354
  purpose: 'ARP-RECEIPT-v1';
@@ -97,7 +97,22 @@ export interface SettlementSignatures {
97
97
  }
98
98
  export interface SettlementParty {
99
99
  settlement_pubkey: string;
100
- sig: Ed25519Sig;
100
+ /**
101
+ * Ed25519 signature, **raw base64 — NO `ed25519:` prefix.** Wire
102
+ * format intentionally mismatches the prefixed `Ed25519Sig` type
103
+ * because this block is consumed by the Solana Ed25519Program at
104
+ * release-tx-builder time, where prefixed strings would break. The
105
+ * server's `ReceiptCosignValidatorService.parseBase64Sig`
106
+ * (`apps/arp-server/src/escrow/services/receipt-cosign-validator.service.ts:557-565`)
107
+ * decodes 64 bytes directly via `Buffer.from(input, 'base64')`
108
+ * without any prefix stripping. The CLI's
109
+ * `heyarp wallet sign-settlement-release` already emits raw base64
110
+ * (`packages/cli/src/commands/wallet.ts:1718-1722`). Matches the
111
+ * raw-base64 shape of `SettlementSignatureContent.sig` — divergent
112
+ * types here would leave buyers unable to copy the value verbatim
113
+ * into the cosign attachment.
114
+ */
115
+ sig: string;
101
116
  }
102
117
  /** Top-level envelope as it appears on the wire. */
103
118
  export interface Envelope<TBody extends Body = Body> {
@@ -1,5 +1,5 @@
1
1
  export type { Sha256Hex, Ed25519Sig, Did, ProtectedBlock, Body, Attachments, CoSignature, SettlementSignatures, SettlementParty, EscrowLockAttachment, Envelope, PersistedEvent, } from './envelope';
2
- export type { HandshakeBody, HandshakeContent, HandshakeResponseBody, HandshakeResponseContent, ContractBody, ContractContent, DelegationBody, DelegationContent, WorkRequestBody, WorkRequestContent, WorkResponseBody, WorkResponseContent, ReceiptBody, ReceiptContent, MemoryDeltaBody, MemoryDeltaContent, DisputeBody, DisputeContent, AnyBody, ReceiptCosignPayload, DisputeResponseCosignPayload, CosignPayload, DeclineReason, AssetIdentifier, } from './body';
2
+ export type { HandshakeBody, HandshakeContent, HandshakeResponseBody, HandshakeResponseContent, ContractBody, ContractContent, DelegationBody, DelegationContent, WorkRequestBody, WorkRequestContent, WorkResponseBody, WorkResponseContent, ReceiptBody, ReceiptContent, MemoryDeltaBody, MemoryDeltaContent, DisputeBody, DisputeContent, SettlementSignatureBody, SettlementSignatureContent, AnyBody, ReceiptCosignPayload, DisputeResponseCosignPayload, CosignPayload, DeclineReason, AssetIdentifier, } from './body';
3
3
  export { DECLINE_REASONS, isDeclineReason } from './body';
4
4
  export type { OwnerSigningMethod, KeyLinkPayload, KeyRotationPayload, ScryptPasswordAttestation } from './identity';
5
5
  export { SCRYPT_PARAMS } from './identity';
@@ -1,3 +1,4 @@
1
1
  export { uuidV4 } from './uuid';
2
2
  export { senderNonce } from './nonce';
3
3
  export { rfc3339, expiresAt } from './timestamp';
4
+ export { pollUntil, type PollUntilOptions, type PollUntilResult } from './poll';
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Generic polling helper for SDK consumers that need to block until
3
+ * an FSM transition lands.
4
+ *
5
+ * The SDK is wire-format-only — it has no HTTP client and no
6
+ * awareness of the relationship/delegation/receipt state-machine
7
+ * strings. Callers bring their own `fetch` function (axios, native
8
+ * fetch, custom transport) and their own predicate; this helper
9
+ * supplies the polling discipline (interval, timeout, cancellable
10
+ * AbortSignal, exponential backoff on transient errors).
11
+ *
12
+ * Why this is useful enough to ship even without a state-machine
13
+ * type in the SDK: third-party bot authors today write the loop
14
+ * themselves and routinely get the discipline wrong — they tight-loop
15
+ * the RPC, ignore timeouts, swallow errors that should propagate, or
16
+ * miss state transitions that landed between polls. A 40-LOC
17
+ * primitive with documented behaviour is worth more than the
18
+ * duplicated `setTimeout` chains it replaces.
19
+ *
20
+ * Pairs with the CLI's `--wait-until <phase>` flag on FSM-advancing
21
+ * commands: same polling shape, same exit-on-predicate semantics,
22
+ * ported to the API-client layer for callers who don't shell out to
23
+ * `heyarp`. Either path can be skipped — `heyarp status <rel-id>
24
+ * --wait --until <phase>` remains the documented escape hatch.
25
+ */
26
+ /**
27
+ * Outcome of a `pollUntil` call.
28
+ *
29
+ * - `{ kind: 'matched', value }` — predicate returned true; `value`
30
+ * is the matched fetcher return.
31
+ * - `{ kind: 'timeout', last }` — wall-clock exceeded `timeoutMs`;
32
+ * `last` is the most recent fetcher return (which DID NOT match the
33
+ * predicate) for the caller's diagnostic / retry decision.
34
+ * - `{ kind: 'aborted', last }` — `abortSignal.aborted` became true;
35
+ * `last` is the most recent fetcher return, or `undefined` if the
36
+ * abort fired before the first fetch landed.
37
+ *
38
+ * Discriminated union (`kind` literal) so consumers can `switch`
39
+ * exhaustively. No exceptions are thrown for the timeout/abort
40
+ * outcomes — they're modelled as values so the caller's branching
41
+ * stays explicit. Fetcher errors are a different story: they bubble
42
+ * unless `swallowFetchErrors` is true.
43
+ */
44
+ export type PollUntilResult<T> = {
45
+ kind: 'matched';
46
+ value: T;
47
+ } | {
48
+ kind: 'timeout';
49
+ last: T | undefined;
50
+ } | {
51
+ kind: 'aborted';
52
+ last: T | undefined;
53
+ };
54
+ export interface PollUntilOptions<T> {
55
+ /**
56
+ * Async fetcher invoked once per poll tick. Should return the
57
+ * domain object the predicate evaluates against. Implementations
58
+ * typically read the same row the CLI's `composeStatus` reads —
59
+ * a single signed-GET against the relationship's entities is
60
+ * common.
61
+ */
62
+ fetch: () => Promise<T>;
63
+ /**
64
+ * Returns `true` when `value` satisfies the wait condition. The
65
+ * helper exits with `{ kind: 'matched', value }` on the first
66
+ * `true`. Predicate exceptions bubble (treat as a fail-fast
67
+ * programmer error — the predicate shouldn't be side-effectful or
68
+ * I/O-bound).
69
+ */
70
+ predicate: (value: T) => boolean;
71
+ /** Milliseconds between fetches. Defaults to 3000 (matches CLI WAIT_DEFAULT_INTERVAL_SEC). Lower bound is 100ms to avoid tight-loop accidents. */
72
+ intervalMs?: number;
73
+ /** Wall-clock budget in milliseconds. Defaults to 300000 (5 min). On exceed, returns `{ kind: 'timeout', last }`. */
74
+ timeoutMs?: number;
75
+ /**
76
+ * If true, fetch errors are caught and logged via `onFetchError`
77
+ * (or silently swallowed when that's absent); the loop continues
78
+ * to the next tick. If false (default), the first fetch error
79
+ * rejects the returned promise — appropriate for "fail loud on
80
+ * the first network blip" scripts.
81
+ */
82
+ swallowFetchErrors?: boolean;
83
+ /**
84
+ * Called once per fetch error when `swallowFetchErrors` is true.
85
+ * Gets the raw error so the caller can log / sample / surface to
86
+ * an observability sink without coupling the polling primitive to
87
+ * any particular logger.
88
+ */
89
+ onFetchError?: (err: unknown) => void;
90
+ /**
91
+ * Cancellation signal — when its `aborted` flag flips, the
92
+ * helper resolves with `{ kind: 'aborted', last }` after the
93
+ * current sleep completes (or immediately if currently mid-sleep
94
+ * and the signal supports listener-driven abort). The fetcher
95
+ * itself is not abort-aware here; if it needs to be, the caller
96
+ * should pass the same signal into its own implementation.
97
+ */
98
+ abortSignal?: AbortSignal;
99
+ }
100
+ /**
101
+ * Poll `fetch` until `predicate` returns true, the timeout fires, or
102
+ * the optional `abortSignal` aborts. Returns a discriminated outcome
103
+ * (no exceptions for the expected paths).
104
+ *
105
+ * Behaviour notes:
106
+ * - The first fetch happens immediately (no leading sleep). If the
107
+ * predicate matches on the first call, returns without any sleep
108
+ * — useful when the caller already knows the state may have
109
+ * transitioned in the background.
110
+ * - Sleeps are clamped to the remaining budget; the final sleep
111
+ * will be shorter than `intervalMs` when timeout is near.
112
+ * - Lower-bound `intervalMs` to 100ms. Higher-frequency polling is
113
+ * an accident; if you really need it, build your own loop with
114
+ * direct `setTimeout` calls.
115
+ */
116
+ export declare function pollUntil<T>(opts: PollUntilOptions<T>): Promise<PollUntilResult<T>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyanon-arp/sdk",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "TypeScript SDK for the Agent Relationship Protocol — canonical JSON, Ed25519 envelope sign/verify, did:arp identity, receipt co-signatures, scrypt key attestation, chain-audit helpers.",
5
5
  "license": "MIT",
6
6
  "keywords": [