@heyanon-arp/sdk 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -98,7 +98,6 @@ verifyCosignature({ cosignature: cs, payload: cs.payload, signerIdentityPubkey:
98
98
  | `envelope` | `signEnvelope` / `verifyEnvelope` — the wire-level message signing |
99
99
  | `cosignature` | `ARP-RECEIPT-v1` + `ARP-DISPUTE-RESPONSE-v1` co-signatures |
100
100
  | `challenge` | `ARP-CHALLENGE-v1` ownership proof for identity registration |
101
- | `webhook` | `ARP-WEBHOOK-v1` HMAC for the outbox `X-ARP-Signature` header |
102
101
  | `attestation` | `ARP-KEY-LINK-v1` / `ARP-KEY-ROTATION-v1` (scrypt password proof) |
103
102
  | `server-chain` | `signed_message_hash` + `server_event_hash` builders for chain audit |
104
103
  | `settlement` | `ARP-SOLANA-RELEASE-v1.5` / `…-PARTIAL-RELEASE-v1.5` / `…-REFUND-v1` digests |
@@ -5,11 +5,14 @@
5
5
  * `identity_pubkey` from the document (not `decoded(did)`) because
6
6
  * `identity_pubkey` may have rotated since registration while the DID
7
7
  * itself stays stable.
8
+ *
9
+ * No `service[]` endpoint: delivery is server-mediated and agents
10
+ * pull (poll/SSE inbox), so there is no per-agent inbound endpoint
11
+ * to advertise.
8
12
  */
9
13
  export interface DidDocument {
10
14
  id: string;
11
15
  verificationMethod: VerificationMethod[];
12
- service: ServiceEndpoint[];
13
16
  metadata: {
14
17
  key_mode: KeyMode;
15
18
  owner_attestation_id: string;
@@ -23,8 +26,3 @@ export interface VerificationMethod {
23
26
  controller: string;
24
27
  publicKeyMultibase: string;
25
28
  }
26
- export interface ServiceEndpoint {
27
- id: string;
28
- type: 'ARPEndpoint';
29
- serviceEndpoint: string;
30
- }
@@ -1,2 +1,2 @@
1
1
  export { formatDid, parseDid, isValidDid } from './format';
2
- export type { DidDocument, KeyMode, VerificationMethod, ServiceEndpoint } from './document';
2
+ export type { DidDocument, KeyMode, VerificationMethod } from './document';
@@ -1,79 +1,66 @@
1
1
  import type { AssetIdentifier } from '../types';
2
2
  /**
3
- * Subset of contract terms that the on-chain `condition_hash` binds.
4
- * Chosen per the escrow design's "condition_hash semantics" decision:
5
- * fields that the parties must agree on at offer time AND that the
6
- * release flow must replay against. Excludes timestamps and
7
- * decline-reason fields (those only exist on terminal rows; including
8
- * them would make every accepted contract's hash dependent on
9
- * non-content metadata).
3
+ * Subset of DELEGATION terms that the on-chain `condition_hash` binds:
4
+ * - `delegationId` is the identity binding it is the root of the
5
+ * on-chain derivation chain (`lock_id = sha256("arp-lock-v1" ||
6
+ * delegationId_bytes16)`), so it is known before `create_lock` is
7
+ * built NOT circular.
8
+ * - `currency` is the single `delegation.currency` field (the one
9
+ * asset for both rate + amount); the projection reads the REAL
10
+ * delegation field, so a raw delegation row binds currency with no
11
+ * caller pre-mapping.
10
12
  *
11
- * The shape MUST match server-side `deriveConditionHash` byte-for-byte;
12
- * any drift breaks E5 settlement-digest reconstruction (which reads
13
- * `condition_hash` from the on-chain Lock and expects it to equal a
14
- * server-side recompute).
13
+ * `amount` is intentionally NOT here the concrete locked amount AND its
14
+ * mint are bound at settlement via the digest (`buildReleaseDigest` binds
15
+ * `amount` u64 + `mint` 32B) and on-chain in the Lock account. The
16
+ * condition_hash binds the agreed TERMS (scope / pricing / settlement /
17
+ * rate / unit / currency), not the amount.
18
+ *
19
+ * The shape MUST match server-side + CLI derivation byte-for-byte; the
20
+ * golden vectors in `condition-hash.test.ts` pin it.
15
21
  */
16
- export interface ContractTermsSubset {
17
- contractId: string;
18
- version: number;
22
+ export interface DelegationTermsSubset {
23
+ delegationId: string;
19
24
  scopeSummary: string;
20
25
  pricingModel: string;
21
26
  settlementModel: string;
22
27
  rateAmount?: string;
23
- rateCurrency?: AssetIdentifier;
24
28
  rateUnit?: string;
25
- allowedDelegationTags?: string[];
29
+ currency?: AssetIdentifier;
26
30
  }
27
31
  /**
28
- * Loose input shape the contract DTO / row / public response object
29
- * may carry many extra fields (`state`, `relationshipId`, timestamps,
30
- * decline reasons, etc.). Callers pass that whole object; we project
31
- * onto `ContractTermsSubset` so extra fields can NEVER influence the
32
- * condition_hash. This protects against silent drift if the public
33
- * contract shape grows new fields.
34
- *
35
- * All fields are typed as optional here so a row pulled directly from
36
- * Mongoose (where most contract fields are optional in the schema)
37
- * fits the shape. The actual validity check happens inside
38
- * `deriveConditionHash`, which throws if a required projected field
39
- * is undefined — that surface is the right place to fail because
40
- * `condition_hash` cannot be derived without it.
32
+ * Loose input for `deriveDelegationConditionHash`. Callers pass a whole
33
+ * delegation row / DTO; we project onto `DelegationTermsSubset` so extra
34
+ * fields (`state`, `relationshipId`, `amount`, timestamps, decline
35
+ * metadata, …) can NEVER influence the hash. Defensive projection:
36
+ * extra fields can NEVER influence the hash.
41
37
  */
42
- export interface ContractLikeInput {
43
- contractId?: string;
44
- version?: number;
38
+ export interface DelegationTermsInput {
39
+ delegationId?: string;
45
40
  scopeSummary?: string;
46
41
  pricingModel?: string;
47
42
  settlementModel?: string;
48
43
  rateAmount?: string;
49
- rateCurrency?: AssetIdentifier;
50
44
  rateUnit?: string;
51
- allowedDelegationTags?: string[];
45
+ currency?: AssetIdentifier;
52
46
  [other: string]: unknown;
53
47
  }
54
48
  /**
55
- * Compute the 32-byte sha256 condition_hash that binds an on-chain
56
- * lock to the off-chain contract terms.
57
- *
58
- * The function performs an EXPLICIT FIELD PROJECTION before
59
- * canonicalisation. Even if the caller passes a full contract row
60
- * with `state`, `createdAt`, `declineReason`, etc., only the eight
61
- * whitelisted fields are hashed. This is critical for digest
62
- * stability across SDK versions: adding a field to the public
63
- * contract shape MUST NOT change `condition_hash` values for existing
64
- * contracts.
49
+ * Compute the 32-byte sha256 condition_hash that binds an on-chain lock
50
+ * to the off-chain DELEGATION terms.
65
51
  *
66
- * Wire procedure:
67
- * 1. Project the caller's input onto the whitelisted subset.
68
- * 2. JCS-canonicalize via the SDK's `canonicalBytes`.
69
- * 3. sha256 32-byte digest.
52
+ * Wire procedure — explicit field
53
+ * projection JCS canonicalize (`canonicalBytes`) sha256. The
54
+ * projection is the security boundary: adding a field to the public
55
+ * delegation shape MUST NOT change `condition_hash` for existing
56
+ * delegations.
70
57
  *
71
- * The same procedure runs on the server during `delegation.offer`
72
- * validation AND during `receipt cosign` settlement-digest
73
- * reconstruction. Any field-set drift between offer-time and
74
- * cosign-time produces a different hash, which fails Ed25519 verify
75
- * on-chain.
58
+ * Runs in THREE places that MUST agree byte-for-byte: the buyer's
59
+ * offer-prep (CLI), the server's `delegation.offer` validation
60
+ * (recompute + compare to the on-chain bound hash), and the settlement
61
+ * digest reconstruction. Any field-set drift produces a different hash
62
+ * → Ed25519 settlement verify fails on-chain.
76
63
  *
77
64
  * @returns 32-byte condition_hash
78
65
  */
79
- export declare function deriveConditionHash(contract: ContractLikeInput): Uint8Array;
66
+ export declare function deriveDelegationConditionHash(delegation: DelegationTermsInput): Uint8Array;
@@ -4,7 +4,7 @@ export interface CreateLockArgs {
4
4
  lockId: Uint8Array;
5
5
  /** u64 amount in base units. */
6
6
  amount: bigint;
7
- /** 32-byte condition_hash (deriveConditionHash output). */
7
+ /** 32-byte condition_hash (deriveDelegationConditionHash output). */
8
8
  conditionHash: Uint8Array;
9
9
  /** u64 unix-seconds expiry. */
10
10
  expiry: bigint;
@@ -1,4 +1,4 @@
1
1
  export { deriveLockId, delegationIdToBytes16, bytes16ToDelegationId } from './lock-id';
2
- export { deriveConditionHash, type ContractTermsSubset, type ContractLikeInput } from './condition-hash';
2
+ export { deriveDelegationConditionHash, type DelegationTermsSubset, type DelegationTermsInput } from './condition-hash';
3
3
  export { parseCaip19SolanaAssetId, type ParsedSolanaAssetId } from './caip19';
4
4
  export { buildCreateLockIxData, computeCreateLockDiscriminator, CREATE_LOCK_DISCRIMINATOR, type CreateLockArgs, } from './create-lock';
@@ -41,7 +41,7 @@ export declare function deriveLockId(delegationId: string): Uint8Array;
41
41
  * Either way, the same 16-byte representation comes out (UUID body is
42
42
  * the lock-id substrate; the `del_` prefix is a wrapper that exists
43
43
  * only so the wire shape is unambiguously a delegation reference vs.
44
- * a contract / receipt id elsewhere in the canonical JSON). Pushing
44
+ * a receipt id elsewhere in the canonical JSON). Pushing
45
45
  * this normalization down into the SDK gives every caller — CLI,
46
46
  * server, future TypeScript clients — a single source of truth.
47
47
  *
package/dist/index.d.ts CHANGED
@@ -9,7 +9,6 @@
9
9
  * - envelope — sign / verify envelope (steps 4-6 of protocol verification)
10
10
  * - cosignature — receipt + dispute-response co-signatures
11
11
  * - challenge — ARP-CHALLENGE-v1 ownership proof (registration / rotation)
12
- * - webhook — ARP-WEBHOOK-v1 HMAC header (outbox delivery)
13
12
  * - attestation — scrypt key-link + key-rotation attestations
14
13
  * - server-chain — signed_message_hash, server_event_hash, audit walker
15
14
  * - settlement — ARP-SOLANA-* digest stubs (V1.5)
@@ -27,7 +26,6 @@ export * from './did';
27
26
  export * from './envelope';
28
27
  export * from './cosignature';
29
28
  export * from './challenge';
30
- export * from './webhook';
31
29
  export * from './attestation';
32
30
  export * from './server-chain';
33
31
  export * from './settlement';
package/dist/index.js CHANGED
@@ -160,8 +160,6 @@ var Purpose = {
160
160
  RECEIPT: "ARP-RECEIPT-v1",
161
161
  /** Dispute response co-signature payload. */
162
162
  DISPUTE_RESPONSE: "ARP-DISPUTE-RESPONSE-v1",
163
- /** Webhook delivery HMAC signature payload. */
164
- WEBHOOK: "ARP-WEBHOOK-v1",
165
163
  /** Identity ownership challenge proof. */
166
164
  CHALLENGE: "ARP-CHALLENGE-v1",
167
165
  /** Verifiable Credential issued by the platform. */
@@ -267,54 +265,6 @@ function verifyChallenge(challengeBytes, signature, identityPubkey) {
267
265
  if (signature.length !== 64) return false;
268
266
  return verify2(signature, buildSigningInput(challengeBytes), identityPubkey);
269
267
  }
270
- function buildWebhookSignatureHeader(input, sharedSecret) {
271
- const digest = sha2.sha256(canonicalBytes(input));
272
- const mac = hmac.hmac(sha2.sha256, sharedSecret, digest);
273
- return `${Purpose.WEBHOOK}=${base.base64.encode(mac)}`;
274
- }
275
- function verifyWebhookSignatureHeader(headerValue, input, sharedSecret) {
276
- const secrets = Array.isArray(sharedSecret) ? sharedSecret : [sharedSecret];
277
- if (secrets.length === 0) return false;
278
- let matched = false;
279
- for (const secret of secrets) {
280
- const expected = buildWebhookSignatureHeader(input, secret);
281
- matched = constantTimeEqual(expected, headerValue) || matched;
282
- }
283
- return matched;
284
- }
285
- function verifyAndCheckPayload(args) {
286
- const tolerance = args.servedAtToleranceMs === void 0 ? 5 * 6e4 : args.servedAtToleranceMs;
287
- if (tolerance !== null) {
288
- const servedAtMs = Date.parse(args.input.served_at);
289
- const nowMs = (args.now ?? /* @__PURE__ */ new Date()).getTime();
290
- if (Number.isNaN(servedAtMs) || Math.abs(nowMs - servedAtMs) > tolerance) return "stale_attempt";
291
- }
292
- const fullInput = { ...args.input, payload_sha256: sha256HexLower(args.rawBody) };
293
- if (!verifyWebhookSignatureHeader(args.headerValue, fullInput, args.secrets)) return "invalid_signature";
294
- return "ok";
295
- }
296
- function isProbeRequest(headers) {
297
- for (const [name, value] of Object.entries(headers)) {
298
- if (name.toLowerCase() !== "x-arp-probe") continue;
299
- const v = Array.isArray(value) ? value[0] : value;
300
- if (v === "1" || v === "true") return true;
301
- }
302
- return false;
303
- }
304
- function sha256HexLower(bytes) {
305
- const digest = sha2.sha256(bytes);
306
- let hex = "";
307
- for (const b of digest) hex += b.toString(16).padStart(2, "0");
308
- return hex;
309
- }
310
- function constantTimeEqual(a, b) {
311
- if (a.length !== b.length) return false;
312
- let diff = 0;
313
- for (let i = 0; i < a.length; i++) {
314
- diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
315
- }
316
- return diff === 0;
317
- }
318
268
 
319
269
  // src/types/identity.ts
320
270
  var SCRYPT_PARAMS = {
@@ -611,16 +561,15 @@ function u16LE(value) {
611
561
  var SPL_TOKEN_PROGRAM_ID_BASE58 = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
612
562
  var TOKEN_2022_PROGRAM_ID_BASE58 = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
613
563
  function detectTokenProgramFromOwner(mintAccountOwnerBase58) {
614
- switch (mintAccountOwnerBase58) {
615
- case SPL_TOKEN_PROGRAM_ID_BASE58:
616
- return { kind: "legacy", programIdBase58: SPL_TOKEN_PROGRAM_ID_BASE58 };
617
- case TOKEN_2022_PROGRAM_ID_BASE58:
618
- return { kind: "token-2022", programIdBase58: TOKEN_2022_PROGRAM_ID_BASE58 };
619
- default:
620
- throw new Error(
621
- `detectTokenProgram: unsupported mint owner ${mintAccountOwnerBase58}; ARP escrow supports legacy SPL Token (${SPL_TOKEN_PROGRAM_ID_BASE58}) and Token-2022 (${TOKEN_2022_PROGRAM_ID_BASE58}) only`
622
- );
564
+ if (mintAccountOwnerBase58 === SPL_TOKEN_PROGRAM_ID_BASE58) {
565
+ return { kind: "legacy", programIdBase58: SPL_TOKEN_PROGRAM_ID_BASE58 };
566
+ }
567
+ if (mintAccountOwnerBase58 === TOKEN_2022_PROGRAM_ID_BASE58) {
568
+ throw new Error(
569
+ `detectTokenProgram: mint owner is the Token-2022 program (${TOKEN_2022_PROGRAM_ID_BASE58}), which ARP escrow does not support; use a legacy SPL Token mint (${SPL_TOKEN_PROGRAM_ID_BASE58}) or native SOL`
570
+ );
623
571
  }
572
+ throw new Error(`detectTokenProgram: unsupported mint owner ${mintAccountOwnerBase58}; ARP escrow supports legacy SPL Token (${SPL_TOKEN_PROGRAM_ID_BASE58}) only`);
624
573
  }
625
574
  function detectTokenProgramFromOwnerBytes(mintAccountOwnerBytes) {
626
575
  if (mintAccountOwnerBytes.length !== 32) {
@@ -769,24 +718,22 @@ function resolveAsset(input) {
769
718
  return null;
770
719
  }
771
720
  var WELL_KNOWN_ASSET_KEYS = Object.keys(WELL_KNOWN_ASSETS);
772
- function deriveConditionHash(contract) {
773
- const required = ["contractId", "version", "scopeSummary", "pricingModel", "settlementModel"];
721
+ function deriveDelegationConditionHash(delegation) {
722
+ const required = ["delegationId", "scopeSummary", "pricingModel", "settlementModel"];
774
723
  for (const field of required) {
775
- if (contract[field] === void 0) {
776
- throw new Error(`deriveConditionHash: required field '${String(field)}' is missing from the contract input`);
724
+ if (delegation[field] === void 0) {
725
+ throw new Error(`deriveDelegationConditionHash: required field '${String(field)}' is missing from the delegation input`);
777
726
  }
778
727
  }
779
728
  const subset = {
780
- contractId: contract.contractId,
781
- version: contract.version,
782
- scopeSummary: contract.scopeSummary,
783
- pricingModel: contract.pricingModel,
784
- settlementModel: contract.settlementModel
729
+ delegationId: delegation.delegationId,
730
+ scopeSummary: delegation.scopeSummary,
731
+ pricingModel: delegation.pricingModel,
732
+ settlementModel: delegation.settlementModel
785
733
  };
786
- if (contract.rateAmount !== void 0) subset.rateAmount = contract.rateAmount;
787
- if (contract.rateCurrency !== void 0) subset.rateCurrency = contract.rateCurrency;
788
- if (contract.rateUnit !== void 0) subset.rateUnit = contract.rateUnit;
789
- if (contract.allowedDelegationTags !== void 0) subset.allowedDelegationTags = contract.allowedDelegationTags;
734
+ if (delegation.rateAmount !== void 0) subset.rateAmount = delegation.rateAmount;
735
+ if (delegation.rateUnit !== void 0) subset.rateUnit = delegation.rateUnit;
736
+ if (delegation.currency !== void 0) subset.currency = delegation.currency;
790
737
  const bytes = canonicalBytes(subset);
791
738
  return sha2.sha256(bytes);
792
739
  }
@@ -866,7 +813,6 @@ exports.SETTLEMENT_PURPOSES = SETTLEMENT_PURPOSES;
866
813
  exports.SLIP44_SOLANA = SLIP44_SOLANA;
867
814
  exports.SOLANA_CLUSTER_IDS = SOLANA_CLUSTER_IDS;
868
815
  exports.SPL_TOKEN_PROGRAM_ID_BASE58 = SPL_TOKEN_PROGRAM_ID_BASE58;
869
- exports.TOKEN_2022_PROGRAM_ID_BASE58 = TOKEN_2022_PROGRAM_ID_BASE58;
870
816
  exports.USDC_MINTS = USDC_MINTS;
871
817
  exports.WELL_KNOWN_ASSETS = WELL_KNOWN_ASSETS;
872
818
  exports.WELL_KNOWN_ASSET_KEYS = WELL_KNOWN_ASSET_KEYS;
@@ -876,14 +822,13 @@ exports.buildCreateLockIxData = buildCreateLockIxData;
876
822
  exports.buildPartialReleaseDigest = buildPartialReleaseDigest;
877
823
  exports.buildRefundDigest = buildRefundDigest;
878
824
  exports.buildReleaseDigest = buildReleaseDigest;
879
- exports.buildWebhookSignatureHeader = buildWebhookSignatureHeader;
880
825
  exports.bytes16ToDelegationId = bytes16ToDelegationId;
881
826
  exports.canonicalBytes = canonicalBytes;
882
827
  exports.canonicalJson = canonicalJson;
883
828
  exports.canonicalSha256Hex = canonicalSha256Hex;
884
829
  exports.computeCreateLockDiscriminator = computeCreateLockDiscriminator;
885
830
  exports.delegationIdToBytes16 = delegationIdToBytes16;
886
- exports.deriveConditionHash = deriveConditionHash;
831
+ exports.deriveDelegationConditionHash = deriveDelegationConditionHash;
887
832
  exports.deriveLockId = deriveLockId;
888
833
  exports.deriveScryptKey = deriveScryptKey;
889
834
  exports.detectTokenProgramFromOwner = detectTokenProgramFromOwner;
@@ -895,7 +840,6 @@ exports.generateKeyPair = generateKeyPair;
895
840
  exports.getPublicKey = getPublicKey2;
896
841
  exports.isAssetIdentifier = isAssetIdentifier;
897
842
  exports.isDeclineReason = isDeclineReason;
898
- exports.isProbeRequest = isProbeRequest;
899
843
  exports.isValidDid = isValidDid;
900
844
  exports.parseCaip19SolanaAssetId = parseCaip19SolanaAssetId;
901
845
  exports.parseDid = parseDid;
@@ -906,7 +850,6 @@ exports.scryptPasswordProofSign = scryptPasswordProofSign;
906
850
  exports.scryptPasswordProofVerify = scryptPasswordProofVerify;
907
851
  exports.senderNonce = senderNonce;
908
852
  exports.serverEventHash = serverEventHash;
909
- exports.sha256HexLower = sha256HexLower;
910
853
  exports.sign = sign2;
911
854
  exports.signChallenge = signChallenge;
912
855
  exports.signCosignature = signCosignature;
@@ -916,10 +859,8 @@ exports.signKeyRotationAttestation = signKeyRotationAttestation;
916
859
  exports.signedMessageHash = signedMessageHash;
917
860
  exports.uuidV4 = uuidV4;
918
861
  exports.verify = verify2;
919
- exports.verifyAndCheckPayload = verifyAndCheckPayload;
920
862
  exports.verifyChallenge = verifyChallenge;
921
863
  exports.verifyCosignature = verifyCosignature;
922
864
  exports.verifyEnvelope = verifyEnvelope;
923
865
  exports.verifyKeyLinkAttestation = verifyKeyLinkAttestation;
924
866
  exports.verifyKeyRotationAttestation = verifyKeyRotationAttestation;
925
- exports.verifyWebhookSignatureHeader = verifyWebhookSignatureHeader;
package/dist/index.mjs CHANGED
@@ -135,8 +135,6 @@ var Purpose = {
135
135
  RECEIPT: "ARP-RECEIPT-v1",
136
136
  /** Dispute response co-signature payload. */
137
137
  DISPUTE_RESPONSE: "ARP-DISPUTE-RESPONSE-v1",
138
- /** Webhook delivery HMAC signature payload. */
139
- WEBHOOK: "ARP-WEBHOOK-v1",
140
138
  /** Identity ownership challenge proof. */
141
139
  CHALLENGE: "ARP-CHALLENGE-v1",
142
140
  /** Verifiable Credential issued by the platform. */
@@ -242,54 +240,6 @@ function verifyChallenge(challengeBytes, signature, identityPubkey) {
242
240
  if (signature.length !== 64) return false;
243
241
  return verify2(signature, buildSigningInput(challengeBytes), identityPubkey);
244
242
  }
245
- function buildWebhookSignatureHeader(input, sharedSecret) {
246
- const digest = sha256(canonicalBytes(input));
247
- const mac = hmac(sha256, sharedSecret, digest);
248
- return `${Purpose.WEBHOOK}=${base64.encode(mac)}`;
249
- }
250
- function verifyWebhookSignatureHeader(headerValue, input, sharedSecret) {
251
- const secrets = Array.isArray(sharedSecret) ? sharedSecret : [sharedSecret];
252
- if (secrets.length === 0) return false;
253
- let matched = false;
254
- for (const secret of secrets) {
255
- const expected = buildWebhookSignatureHeader(input, secret);
256
- matched = constantTimeEqual(expected, headerValue) || matched;
257
- }
258
- return matched;
259
- }
260
- function verifyAndCheckPayload(args) {
261
- const tolerance = args.servedAtToleranceMs === void 0 ? 5 * 6e4 : args.servedAtToleranceMs;
262
- if (tolerance !== null) {
263
- const servedAtMs = Date.parse(args.input.served_at);
264
- const nowMs = (args.now ?? /* @__PURE__ */ new Date()).getTime();
265
- if (Number.isNaN(servedAtMs) || Math.abs(nowMs - servedAtMs) > tolerance) return "stale_attempt";
266
- }
267
- const fullInput = { ...args.input, payload_sha256: sha256HexLower(args.rawBody) };
268
- if (!verifyWebhookSignatureHeader(args.headerValue, fullInput, args.secrets)) return "invalid_signature";
269
- return "ok";
270
- }
271
- function isProbeRequest(headers) {
272
- for (const [name, value] of Object.entries(headers)) {
273
- if (name.toLowerCase() !== "x-arp-probe") continue;
274
- const v = Array.isArray(value) ? value[0] : value;
275
- if (v === "1" || v === "true") return true;
276
- }
277
- return false;
278
- }
279
- function sha256HexLower(bytes) {
280
- const digest = sha256(bytes);
281
- let hex = "";
282
- for (const b of digest) hex += b.toString(16).padStart(2, "0");
283
- return hex;
284
- }
285
- function constantTimeEqual(a, b) {
286
- if (a.length !== b.length) return false;
287
- let diff = 0;
288
- for (let i = 0; i < a.length; i++) {
289
- diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
290
- }
291
- return diff === 0;
292
- }
293
243
 
294
244
  // src/types/identity.ts
295
245
  var SCRYPT_PARAMS = {
@@ -586,16 +536,15 @@ function u16LE(value) {
586
536
  var SPL_TOKEN_PROGRAM_ID_BASE58 = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
587
537
  var TOKEN_2022_PROGRAM_ID_BASE58 = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
588
538
  function detectTokenProgramFromOwner(mintAccountOwnerBase58) {
589
- switch (mintAccountOwnerBase58) {
590
- case SPL_TOKEN_PROGRAM_ID_BASE58:
591
- return { kind: "legacy", programIdBase58: SPL_TOKEN_PROGRAM_ID_BASE58 };
592
- case TOKEN_2022_PROGRAM_ID_BASE58:
593
- return { kind: "token-2022", programIdBase58: TOKEN_2022_PROGRAM_ID_BASE58 };
594
- default:
595
- throw new Error(
596
- `detectTokenProgram: unsupported mint owner ${mintAccountOwnerBase58}; ARP escrow supports legacy SPL Token (${SPL_TOKEN_PROGRAM_ID_BASE58}) and Token-2022 (${TOKEN_2022_PROGRAM_ID_BASE58}) only`
597
- );
539
+ if (mintAccountOwnerBase58 === SPL_TOKEN_PROGRAM_ID_BASE58) {
540
+ return { kind: "legacy", programIdBase58: SPL_TOKEN_PROGRAM_ID_BASE58 };
541
+ }
542
+ if (mintAccountOwnerBase58 === TOKEN_2022_PROGRAM_ID_BASE58) {
543
+ throw new Error(
544
+ `detectTokenProgram: mint owner is the Token-2022 program (${TOKEN_2022_PROGRAM_ID_BASE58}), which ARP escrow does not support; use a legacy SPL Token mint (${SPL_TOKEN_PROGRAM_ID_BASE58}) or native SOL`
545
+ );
598
546
  }
547
+ throw new Error(`detectTokenProgram: unsupported mint owner ${mintAccountOwnerBase58}; ARP escrow supports legacy SPL Token (${SPL_TOKEN_PROGRAM_ID_BASE58}) only`);
599
548
  }
600
549
  function detectTokenProgramFromOwnerBytes(mintAccountOwnerBytes) {
601
550
  if (mintAccountOwnerBytes.length !== 32) {
@@ -744,24 +693,22 @@ function resolveAsset(input) {
744
693
  return null;
745
694
  }
746
695
  var WELL_KNOWN_ASSET_KEYS = Object.keys(WELL_KNOWN_ASSETS);
747
- function deriveConditionHash(contract) {
748
- const required = ["contractId", "version", "scopeSummary", "pricingModel", "settlementModel"];
696
+ function deriveDelegationConditionHash(delegation) {
697
+ const required = ["delegationId", "scopeSummary", "pricingModel", "settlementModel"];
749
698
  for (const field of required) {
750
- if (contract[field] === void 0) {
751
- throw new Error(`deriveConditionHash: required field '${String(field)}' is missing from the contract input`);
699
+ if (delegation[field] === void 0) {
700
+ throw new Error(`deriveDelegationConditionHash: required field '${String(field)}' is missing from the delegation input`);
752
701
  }
753
702
  }
754
703
  const subset = {
755
- contractId: contract.contractId,
756
- version: contract.version,
757
- scopeSummary: contract.scopeSummary,
758
- pricingModel: contract.pricingModel,
759
- settlementModel: contract.settlementModel
704
+ delegationId: delegation.delegationId,
705
+ scopeSummary: delegation.scopeSummary,
706
+ pricingModel: delegation.pricingModel,
707
+ settlementModel: delegation.settlementModel
760
708
  };
761
- if (contract.rateAmount !== void 0) subset.rateAmount = contract.rateAmount;
762
- if (contract.rateCurrency !== void 0) subset.rateCurrency = contract.rateCurrency;
763
- if (contract.rateUnit !== void 0) subset.rateUnit = contract.rateUnit;
764
- if (contract.allowedDelegationTags !== void 0) subset.allowedDelegationTags = contract.allowedDelegationTags;
709
+ if (delegation.rateAmount !== void 0) subset.rateAmount = delegation.rateAmount;
710
+ if (delegation.rateUnit !== void 0) subset.rateUnit = delegation.rateUnit;
711
+ if (delegation.currency !== void 0) subset.currency = delegation.currency;
765
712
  const bytes = canonicalBytes(subset);
766
713
  return sha256(bytes);
767
714
  }
@@ -826,4 +773,4 @@ function computeCreateLockDiscriminator() {
826
773
  return h.slice(0, 8);
827
774
  }
828
775
 
829
- 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, isProbeRequest, isValidDid, parseCaip19SolanaAssetId, parseDid, pollUntil, resolveAsset, rfc3339, scryptPasswordProofSign, scryptPasswordProofVerify, senderNonce, serverEventHash, sha256HexLower, sign2 as sign, signChallenge, signCosignature, signEnvelope, signKeyLinkAttestation, signKeyRotationAttestation, signedMessageHash, uuidV4, verify2 as verify, verifyAndCheckPayload, verifyChallenge, verifyCosignature, verifyEnvelope, verifyKeyLinkAttestation, verifyKeyRotationAttestation, verifyWebhookSignatureHeader };
776
+ 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, USDC_MINTS, WELL_KNOWN_ASSETS, WELL_KNOWN_ASSET_KEYS, base58btcDecode, base58btcEncode, buildCreateLockIxData, buildPartialReleaseDigest, buildRefundDigest, buildReleaseDigest, bytes16ToDelegationId, canonicalBytes, canonicalJson, canonicalSha256Hex, computeCreateLockDiscriminator, delegationIdToBytes16, deriveDelegationConditionHash, 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 };
package/dist/purpose.d.ts CHANGED
@@ -17,8 +17,6 @@ export declare const Purpose: {
17
17
  readonly RECEIPT: "ARP-RECEIPT-v1";
18
18
  /** Dispute response co-signature payload. */
19
19
  readonly DISPUTE_RESPONSE: "ARP-DISPUTE-RESPONSE-v1";
20
- /** Webhook delivery HMAC signature payload. */
21
- readonly WEBHOOK: "ARP-WEBHOOK-v1";
22
20
  /** Identity ownership challenge proof. */
23
21
  readonly CHALLENGE: "ARP-CHALLENGE-v1";
24
22
  /** Verifiable Credential issued by the platform. */
@@ -45,7 +43,7 @@ export declare const Purpose: {
45
43
  export type PurposeValue = (typeof Purpose)[keyof typeof Purpose];
46
44
  /**
47
45
  * `protected.purpose` accepts a subset — the others are payload-level
48
- * (co-signature / settlement-signature / webhook).
46
+ * (co-signature / settlement-signature).
49
47
  */
50
48
  export declare const PROTECTED_PURPOSES: readonly PurposeValue[];
51
49
  export declare const COSIGNATURE_PURPOSES: readonly PurposeValue[];
@@ -1,4 +1,4 @@
1
1
  export { buildReleaseDigest, buildPartialReleaseDigest, buildRefundDigest, REFUND_REASON_BYTES, PURPOSE_RELEASE_STRING, PURPOSE_PARTIAL_RELEASE_STRING, PURPOSE_REFUND_STRING, } from './settlement';
2
2
  export type { ReleaseDigestInput, PartialReleaseDigestInput, RefundDigestInput, RefundReasonByte } from './settlement';
3
- export { detectTokenProgramFromOwner, detectTokenProgramFromOwnerBytes, SPL_TOKEN_PROGRAM_ID_BASE58, TOKEN_2022_PROGRAM_ID_BASE58, } from './token-program';
3
+ export { detectTokenProgramFromOwner, detectTokenProgramFromOwnerBytes, SPL_TOKEN_PROGRAM_ID_BASE58 } from './token-program';
4
4
  export type { TokenProgramKind, TokenProgramDetection } from './token-program';
@@ -1,47 +1,34 @@
1
1
  /**
2
- * Token-2022 program detection helpers.
2
+ * Token program detection helpers.
3
3
  *
4
- * The escrow contract dispatches transfer / close-account CPIs based on
5
- * the mint's program kind: legacy SPL Token (`TokenkegQ...`) or Token-2022
6
- * (`TokenzQdB...`). The kind is detected on-chain from the mint account's
7
- * `owner` field; this helper mirrors that logic for off-chain consumers
8
- * (tx builders, indexers, decoders).
4
+ * ARP escrow settles in native SOL or a legacy SPL Token mint. The
5
+ * escrow contract dispatches transfer / close-account CPIs based on the
6
+ * mint's program kind, detected on-chain from the mint account's `owner`
7
+ * field; this helper mirrors that logic for off-chain consumers (tx
8
+ * builders, indexers, decoders).
9
9
  *
10
- * The function takes a 40-byte (or longer) account info buffer in the
11
- * standard Solana on-chain account layout the FIRST 32 bytes are the
12
- * account's owner pubkey (in the wire-level AccountInfoSerialised shape
13
- * used by getAccountInfo RPC's "encoding=base64" + custom layout). For
14
- * the more common `getAccountInfo` response, callers should pass the
15
- * `owner` field directly via a 32-byte buffer.
10
+ * Token-2022 (the token-extensions program) is NOT supported a mint
11
+ * owned by it is rejected here so it can never enter the escrow flow.
16
12
  *
17
- * NOTE: `mintAccountOwnerPubkey` is the OWNER of the mint account on
18
- * Solana — i.e. the token program that minted it — NOT the mint's
19
- * mint_authority. Confusingly, both are called "owner" in different
20
- * contexts.
13
+ * The function takes the mint account's `owner` pubkey the program
14
+ * that minted it — NOT the mint's mint_authority. Confusingly, both are
15
+ * called "owner" in different contexts.
21
16
  */
22
17
  /**
23
18
  * Legacy SPL Token program ID (base58: `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`).
24
19
  */
25
20
  export declare const SPL_TOKEN_PROGRAM_ID_BASE58 = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
26
21
  /**
27
- * Token-2022 program ID (base58: `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`).
28
- */
29
- export declare const TOKEN_2022_PROGRAM_ID_BASE58 = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
30
- /**
31
- * Token program kind. Maps to the new contract's `token_program_kind`
32
- * u8 emitted on `LockCreated` events
33
- * (`apps/arp-solana-contract/programs/arp-solana-contract/src/events.rs`):
34
- * 'native' → 0 (mint == `Pubkey::default()`; the program
35
- * ignores the token_program slot but Anchor
36
- * still requires SPL Token or Token-2022 in it)
37
- * 'legacy' → 1 (legacy SPL Token program)
38
- * 'token-2022' → 2 (Token-2022 program)
22
+ * Token program kind. Maps to the contract's `token_program_kind` u8 on
23
+ * `LockCreated` events: `native` → 0, `legacy` → 1. (The contract also
24
+ * defines `2` for Token-2022, but ARP no longer supports it — such a
25
+ * lock is rejected before it is tracked, so this type never carries it.)
39
26
  *
40
- * `detectTokenProgramFromOwner` below resolves the kind for a NON-
41
- * native mint by looking at its `.owner` field; native locks are
42
- * detected from the mint slot value, not from this helper.
27
+ * `detectTokenProgramFromOwner` resolves the kind for a NON-native mint
28
+ * by looking at its `.owner` field; native locks are detected from the
29
+ * mint slot value, not from this helper.
43
30
  */
44
- export type TokenProgramKind = 'legacy' | 'token-2022' | 'native';
31
+ export type TokenProgramKind = 'legacy' | 'native';
45
32
  /**
46
33
  * Result of `detectTokenProgram`: the program kind + a typed branding for
47
34
  * downstream consumers.
@@ -55,9 +42,9 @@ export interface TokenProgramDetection {
55
42
  * Detect a mint's token program kind from its OWNER pubkey (the program
56
43
  * that owns the mint account in Solana account terms).
57
44
  *
58
- * Throws if the owner is neither legacy SPL Token nor Token-2022 — escrow
59
- * does not support any other token program (would dispatch CPI to a
60
- * non-existent surface).
45
+ * Returns `legacy` for a legacy SPL Token mint. Throws for a Token-2022
46
+ * mint (unsupported) or any other owner escrow would otherwise
47
+ * dispatch a CPI to a surface it does not handle.
61
48
  *
62
49
  * @param mintAccountOwnerBase58 — the mint account's `.owner` field as
63
50
  * a base58 string. From `connection.getAccountInfo(mintPubkey)`, this is
@@ -9,8 +9,8 @@
9
9
  */
10
10
  import type { Body, Did, Sha256Hex } from './envelope';
11
11
  /**
12
- * Chain-qualified asset identifier carried by `contract.rate_currency`
13
- * and `delegation.currency`. Replaces the V1 string-enum `'USDC'`
12
+ * Chain-qualified asset identifier carried by `delegation.currency`
13
+ * (the single asset for both rate + amount). Replaces the V1 string-enum `'USDC'`
14
14
  * placeholder — that shape can't disambiguate `USDC on Solana mainnet`
15
15
  * from `USDC on Polygon` or `USDC.e bridged on Avalanche` (all the same
16
16
  * ticker, different on-chain assets). It also can't represent native
@@ -28,14 +28,10 @@ import type { Body, Did, Sha256Hex } from './envelope';
28
28
  * - `symbol` — short human-readable hint for UI ("USDC", "SOL").
29
29
  * Not used for any logic — purely display sugar. Optional.
30
30
  *
31
- * Validation invariants (server-enforced, both `rate_currency` on the
32
- * contract and `currency` on the delegation):
31
+ * Validation invariants (server-enforced on `delegation.currency`):
33
32
  * • `asset_id` ∈ /^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}\/[-a-z0-9]{3,8}:[-.%a-zA-Z0-9]{1,128}$/
34
33
  * • `decimals` ∈ [0, 18]
35
34
  * • `symbol` length ∈ [1, 16] if present
36
- * • For `delegation.currency`: must match the contract's
37
- * `rate_currency.asset_id` if the contract specifies one
38
- * (a delegation under a USDC-priced contract can't quote in SOL).
39
35
  */
40
36
  export interface AssetIdentifier {
41
37
  /** CAIP-19 chain-qualified asset id — e.g. `solana:5eykt.../spl:EPjFWdd5...` */
@@ -47,9 +43,8 @@ export interface AssetIdentifier {
47
43
  }
48
44
  /**
49
45
  * Closed enum of machine-readable decline reasons used across the
50
- * three decline sites in V1:
46
+ * two decline sites in V1:
51
47
  * • `handshake_response.content.decision === 'decline'`
52
- * • `contract.content.action === 'decline'`
53
48
  * • `delegation.content.action === 'decline'`
54
49
  *
55
50
  * Required on every decline envelope so the counterparty's reactor
@@ -77,7 +72,7 @@ export declare const DECLINE_REASONS: readonly DeclineReason[];
77
72
  export declare function isDeclineReason(v: unknown): v is DeclineReason;
78
73
  /**
79
74
  * `handshake` — first signed exchange between two agents. Establishes
80
- * relationship; not a contract.
75
+ * the relationship; carries no terms (those live on the delegation).
81
76
  */
82
77
  export interface HandshakeBody extends Body<HandshakeContent> {
83
78
  type: 'handshake';
@@ -110,32 +105,8 @@ export interface HandshakeResponseContent {
110
105
  [extra: string]: unknown;
111
106
  }
112
107
  /**
113
- * `contract` — co-signed agreement between two agents. `action`
114
- * discriminates lifecycle events.
115
- */
116
- export interface ContractBody extends Body<ContractContent> {
117
- type: 'contract';
118
- }
119
- export interface ContractContent {
120
- action: 'proposal' | 'counter' | 'sign' | 'decline';
121
- contract_id: string;
122
- version: number;
123
- scope_summary?: string;
124
- pricing_model?: 'flat' | 'usage_based';
125
- settlement_model?: 'prepaid' | 'escrow';
126
- rate_amount?: string;
127
- rate_currency?: AssetIdentifier;
128
- rate_unit?: 'task' | 'thread' | 'handoff';
129
- allowed_delegation_tags?: string[];
130
- /** Machine-readable reason — REQUIRED when `action === 'decline'`. See `DeclineReason`. */
131
- reason?: DeclineReason;
132
- /** Optional free-text elaboration (e.g. "rate floor 0.20 USDC for current model pricing"). */
133
- reason_detail?: string;
134
- [extra: string]: unknown;
135
- }
136
- /**
137
- * `delegation` — concrete task offer or lifecycle action under an
138
- * active contract. `action` discriminates the lifecycle event.
108
+ * `delegation` — concrete task offer or lifecycle action. `action`
109
+ * discriminates the lifecycle event.
139
110
  */
140
111
  export interface DelegationBody extends Body<DelegationContent> {
141
112
  type: 'delegation';
@@ -143,13 +114,17 @@ export interface DelegationBody extends Body<DelegationContent> {
143
114
  export interface DelegationContent {
144
115
  action: 'offer' | 'accept' | 'decline' | 'cancel';
145
116
  delegation_id: string;
146
- contract_id: string;
147
117
  title?: string;
148
118
  brief?: Record<string, unknown>;
149
119
  acceptance_criteria?: string[];
150
120
  deadline?: string;
151
121
  amount?: string;
152
122
  currency?: AssetIdentifier;
123
+ scope_summary?: string;
124
+ pricing_model?: 'flat' | 'usage_based';
125
+ settlement_model?: 'prepaid' | 'escrow';
126
+ rate_amount?: string;
127
+ rate_unit?: 'task' | 'thread' | 'handoff';
153
128
  /** Machine-readable reason — REQUIRED when `action === 'decline'`. See `DeclineReason`. */
154
129
  reason?: DeclineReason;
155
130
  /** Optional free-text elaboration (e.g. "delegation offer missing required brief.goal field"). */
@@ -210,18 +185,6 @@ export interface ReceiptContent {
210
185
  notes_hash?: Sha256Hex;
211
186
  [extra: string]: unknown;
212
187
  }
213
- /**
214
- * `memory_delta` — append-only update to a relationship's shared memory.
215
- */
216
- export interface MemoryDeltaBody extends Body<MemoryDeltaContent> {
217
- type: 'memory_delta';
218
- }
219
- export interface MemoryDeltaContent {
220
- kind: 'intro' | 'handoff' | 'preference' | 'note' | 'decision' | 'continuity';
221
- scope: 'thread_only' | 'thread_and_pilot';
222
- content: string;
223
- supersedes?: string;
224
- }
225
188
  /**
226
189
  * `dispute` — challenge against a delegation outcome. `action`
227
190
  * discriminates lifecycle events.
@@ -336,7 +299,7 @@ export interface SettlementSignatureContent {
336
299
  * Base-unit decimal-integer string — required when `purpose ===
337
300
  * 'ARP-SOLANA-PARTIAL-RELEASE-v1.5'`, omitted (or undefined) for
338
301
  * full RELEASE. The server cross-checks it against
339
- * `receipt.usage.computed_amount` for usage_based contracts before
302
+ * `receipt.usage.computed_amount` for usage_based delegations before
340
303
  * verifying the digest (same invariant
341
304
  * `ESC_USAGE_COMPUTED_AMOUNT_MISMATCH` already guards on the
342
305
  * propose-time receipt body).
@@ -348,7 +311,7 @@ export interface SettlementSignatureContent {
348
311
  * Union over every standard body type. Consumers can narrow on
349
312
  * `body.type` via discriminated dispatch.
350
313
  */
351
- export type AnyBody = HandshakeBody | HandshakeResponseBody | ContractBody | DelegationBody | WorkRequestBody | WorkResponseBody | ReceiptBody | MemoryDeltaBody | DisputeBody | SettlementSignatureBody;
314
+ export type AnyBody = HandshakeBody | HandshakeResponseBody | DelegationBody | WorkRequestBody | WorkResponseBody | ReceiptBody | DisputeBody | SettlementSignatureBody;
352
315
  /** Receipt co-signature payload — what gets `payload_hash`'d in `attachments.co_signature`. */
353
316
  export interface ReceiptCosignPayload {
354
317
  purpose: 'ARP-RECEIPT-v1';
@@ -3,7 +3,7 @@ import type { PurposeValue } from '../purpose';
3
3
  * Wire-level types per [00-core/protocol.md](../../../00-core/protocol.md)
4
4
  * and [00-core/schemas.md](../../../00-core/schemas.md).
5
5
  *
6
- * Body-type-specific shapes (handshake / contract / delegation / receipt
6
+ * Body-type-specific shapes (handshake / delegation / receipt
7
7
  * / etc.) live alongside their handlers in the consumer code — keeping
8
8
  * them out of this file avoids the SDK becoming a kitchen-sink of every
9
9
  * message type. A consumer who needs typed bodies can extend `Envelope<T>`
@@ -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, SettlementSignatureBody, SettlementSignatureContent, AnyBody, ReceiptCosignPayload, DisputeResponseCosignPayload, CosignPayload, DeclineReason, AssetIdentifier, } from './body';
2
+ export type { HandshakeBody, HandshakeContent, HandshakeResponseBody, HandshakeResponseContent, DelegationBody, DelegationContent, WorkRequestBody, WorkRequestContent, WorkResponseBody, WorkResponseContent, ReceiptBody, ReceiptContent, 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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyanon-arp/sdk",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
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": [
@@ -1,3 +0,0 @@
1
- export { buildWebhookSignatureHeader, isProbeRequest, sha256HexLower, verifyAndCheckPayload, verifyWebhookSignatureHeader } from './webhook';
2
- export type { WebhookSignableInput, WebhookSignableSource } from './webhook';
3
- export type { ChainEventRecoveryRow, ChainEventRecoveryRowCommon, ChainPayloadCommon, ChainWebhookPayload, DisputeResolvedWebhookPayload, EnvelopeWebhookPayload, LockCreatedWebhookPayload, LockPartiallyReleasedWebhookPayload, LockRefundedWebhookPayload, LockReleasedWebhookPayload, WebhookPayload, } from './payload-types';
@@ -1,198 +0,0 @@
1
- /**
2
- * SDK consumer types for the on-the-wire body the OutboxDeliveryWorker
3
- * POSTs to a recipient's `webhookUrl`. Mirrors the JSON shape the
4
- * server's `OutboxDeliveryWorkerService.deliver` builds.
5
- *
6
- * The body is a discriminated union on `event_type`:
7
- *
8
- * - `envelope.<type>` → `payload` is the envelope `EventPublic`
9
- * shape (eventId, messageId, sender/recipient DIDs, protected
10
- * block, body, attachments, server-derived hashes).
11
- * - `escrow.<event>` → `payload` is the per-event-type chain shape
12
- * (delegation/lock IDs, amounts, verdict, etc.).
13
- *
14
- * Recipient TypeScript consumers `switch (payload.event_type)` to
15
- * branch exhaustively without `default` — drift on either side
16
- * surfaces as a missing-case compile error.
17
- */
18
- /**
19
- * What the server-side `events` collection row looks like on the wire
20
- * after `buildEnvelopeDeliveryPayload` formatting. Recipients see the
21
- * full canonical envelope so they can re-verify the signature locally
22
- * with the SDK (same call they'd make on `/inbox?since=` rows).
23
- *
24
- * Body shape varies by `type` — keep `body` open (the SDK's
25
- * type-narrowing for each `BodyType` lives in
26
- * `packages/sdk/src/envelope/*` and is consumed when the recipient
27
- * branches on body.type).
28
- */
29
- export interface EnvelopeWebhookPayload {
30
- event_type: `envelope.${string}`;
31
- delivery_id: string;
32
- attempt_n: number;
33
- served_at: string;
34
- payload: {
35
- eventId: string;
36
- messageId: string;
37
- senderDid: string;
38
- recipientDid: string;
39
- relationshipId: string;
40
- senderSequence: number;
41
- protocolVersion: string;
42
- purpose: string;
43
- type: string;
44
- protectedBlock: Record<string, unknown>;
45
- body: Record<string, unknown>;
46
- attachments?: Record<string, unknown>;
47
- senderSignature: string;
48
- relationshipEventIndex: number;
49
- prevServerEventHash: string | null;
50
- serverTimestamp: string;
51
- signedMessageHash: string;
52
- serverEventHash: string;
53
- };
54
- }
55
- /**
56
- * Fields every chain-source payload carries. `chain_event_id` +
57
- * `instruction_idx` let the recipient reconstruct
58
- * `WebhookSignableInput.source` for HMAC verification from the body
59
- * alone — no reverse-parse of `delivery_id` required.
60
- */
61
- export interface ChainPayloadCommon {
62
- chain_event_id: string;
63
- instruction_idx: number;
64
- delegation_id: string;
65
- relationship_id: string;
66
- payer_did: string;
67
- payee_did: string;
68
- tx_signature: string;
69
- slot: number;
70
- block_time_iso?: string;
71
- lock_id: string;
72
- }
73
- export interface LockCreatedWebhookPayload {
74
- event_type: 'escrow.lock_created';
75
- delivery_id: string;
76
- attempt_n: number;
77
- served_at: string;
78
- payload: ChainPayloadCommon & {
79
- amount: string;
80
- asset_id?: string;
81
- expiry_unix?: number;
82
- fee_bps?: number;
83
- fee_recipient?: string;
84
- };
85
- }
86
- export interface LockReleasedWebhookPayload {
87
- event_type: 'escrow.lock_released';
88
- delivery_id: string;
89
- attempt_n: number;
90
- served_at: string;
91
- payload: ChainPayloadCommon & {
92
- released_amount: string;
93
- };
94
- }
95
- export interface LockPartiallyReleasedWebhookPayload {
96
- event_type: 'escrow.lock_partially_released';
97
- delivery_id: string;
98
- attempt_n: number;
99
- served_at: string;
100
- payload: ChainPayloadCommon & {
101
- released_amount: string;
102
- refunded_amount: string;
103
- };
104
- }
105
- export interface LockRefundedWebhookPayload {
106
- event_type: 'escrow.lock_refunded';
107
- delivery_id: string;
108
- attempt_n: number;
109
- served_at: string;
110
- payload: ChainPayloadCommon & {
111
- refunded_amount: string;
112
- reason?: string;
113
- };
114
- }
115
- export interface DisputeResolvedWebhookPayload {
116
- event_type: 'escrow.dispute_resolved';
117
- delivery_id: string;
118
- attempt_n: number;
119
- served_at: string;
120
- payload: ChainPayloadCommon & {
121
- verdict: string;
122
- };
123
- }
124
- /**
125
- * Discriminated union of every webhook delivery the recipient might
126
- * receive. Use exhaustive `switch` on `event_type`:
127
- *
128
- * ```ts
129
- * function handle(p: WebhookPayload) {
130
- * switch (p.event_type) {
131
- * case 'escrow.lock_created': return onLockCreated(p);
132
- * case 'escrow.lock_released': return onLockReleased(p);
133
- * case 'escrow.lock_partially_released': return onPartial(p);
134
- * case 'escrow.lock_refunded': return onRefund(p);
135
- * case 'escrow.dispute_resolved': return onDispute(p);
136
- * default:
137
- * // p.event_type is `envelope.${string}` here
138
- * return onEnvelope(p);
139
- * }
140
- * }
141
- * ```
142
- *
143
- * The chain branches narrow exhaustively because each is a string-
144
- * literal type; the envelope branch carries the catch-all
145
- * `envelope.${string}` template-literal type so recipients can
146
- * still narrow on `payload.type` if they care about specific body
147
- * types.
148
- */
149
- export type ChainWebhookPayload = LockCreatedWebhookPayload | LockReleasedWebhookPayload | LockPartiallyReleasedWebhookPayload | LockRefundedWebhookPayload | DisputeResolvedWebhookPayload;
150
- export type WebhookPayload = EnvelopeWebhookPayload | ChainWebhookPayload;
151
- /**
152
- * Fields every recovery row carries irrespective of event type — the
153
- * envelope around the per-event-type `payload`.
154
- */
155
- export interface ChainEventRecoveryRowCommon {
156
- chain_event_id: string;
157
- delegation_id: string;
158
- relationship_id: string;
159
- lock_id: string;
160
- tx_signature: string;
161
- slot: number;
162
- block_time_iso?: string;
163
- created_at: string;
164
- }
165
- /**
166
- * Recovery channel for chain events. Returned by the
167
- * `GET /v1/agents/me/chain-events?since=…` endpoint. Recipients call
168
- * this on handler restart / reconnection to catch any chain events
169
- * they missed while their handler was down.
170
- *
171
- * **Discriminated union**: both `event_type` and `payload` come from
172
- * the SAME tagged union — `switch (row.event_type)` narrows
173
- * `row.payload` to the right per-event-type shape so the advertised
174
- * "share a single handler between push and pull" actually type-checks.
175
- *
176
- * switch (row.event_type) {
177
- * case 'escrow.lock_released':
178
- * // row.payload is LockReleasedWebhookPayload['payload'] here
179
- * break;
180
- * ...
181
- * }
182
- */
183
- export type ChainEventRecoveryRow = ChainEventRecoveryRowCommon & ({
184
- event_type: 'escrow.lock_created';
185
- payload: LockCreatedWebhookPayload['payload'];
186
- } | {
187
- event_type: 'escrow.lock_released';
188
- payload: LockReleasedWebhookPayload['payload'];
189
- } | {
190
- event_type: 'escrow.lock_partially_released';
191
- payload: LockPartiallyReleasedWebhookPayload['payload'];
192
- } | {
193
- event_type: 'escrow.lock_refunded';
194
- payload: LockRefundedWebhookPayload['payload'];
195
- } | {
196
- event_type: 'escrow.dispute_resolved';
197
- payload: DisputeResolvedWebhookPayload['payload'];
198
- });
@@ -1,158 +0,0 @@
1
- /**
2
- * `ARP-WEBHOOK-v1` HMAC over the canonical webhook envelope. Used by
3
- * the OutboxDeliveryWorker for the `X-ARP-Signature` header.
4
- *
5
- * The signable input is a discriminated `source` union covering BOTH
6
- * envelope deliveries (`source.kind === 'envelope'`) AND chain
7
- * settlement events (`source.kind === 'chain'`). The single
8
- * canonical-JSON output binds the HMAC to the source variant so a
9
- * recipient receiving a chain webhook can't accidentally verify
10
- * against an envelope-shaped signable, and vice versa.
11
- *
12
- * The body-tampering defence (`payload_sha256`) is mandatory on the
13
- * new shape — recipients SHOULD verify the hash matches their
14
- * received raw body BEFORE the HMAC check (cheap, and surfaces
15
- * "wrong body" as a separate diagnostic from "wrong HMAC").
16
- */
17
- export type WebhookSignableSource = {
18
- kind: 'envelope';
19
- envelope_message_id: string;
20
- server_event_hash: string;
21
- } | {
22
- kind: 'chain';
23
- chain_event_id: string;
24
- tx_signature: string;
25
- instruction_idx: number;
26
- };
27
- export interface WebhookSignableInput {
28
- /** Outbox row id (deterministic per delivery attempt). */
29
- delivery_id: string;
30
- recipient_did: string;
31
- /** Public event-type discriminator (`envelope.<type>` or `escrow.<event>`). */
32
- event_type: string;
33
- /** Source-variant discriminated union. Binds the HMAC to the kind. */
34
- source: WebhookSignableSource;
35
- /** SHA-256 (lowercase hex, no `sha256:` prefix) of the canonical raw body bytes the recipient receives. */
36
- payload_sha256: string;
37
- /** 1-based retry counter — participates in canonical input so replays of an old header on a fresh attempt fail. */
38
- attempt_n: number;
39
- /** RFC 3339 wall-clock time the server generated this attempt. Recipient policy MAY reject outside a tolerance window. */
40
- served_at: string;
41
- }
42
- /**
43
- * Compute `X-ARP-Signature` value:
44
- * `<purpose>=<base64(HMAC-SHA256(secret, sha256(canonical_json(input))))>`.
45
- *
46
- * Recipients lookup their per-sender shared secret(s), recompute the
47
- * HMAC over the same canonical input, and compare against the
48
- * received header. See `verifyWebhookSignatureHeader` for the
49
- * standard recipient flow with rotation grace.
50
- */
51
- export declare function buildWebhookSignatureHeader(input: WebhookSignableInput, sharedSecret: Uint8Array): string;
52
- /**
53
- * Verify an inbound `X-ARP-Signature` header against the reconstructed
54
- * signable input. The 3-argument form is the convenience for
55
- * single-secret consumers (most use); the array form is the rotation-
56
- * grace path (two-phase rotate: recipient verifies against
57
- * `[current, pending, previous]` during the 1h grace window).
58
- *
59
- * Returns `false` on shape mismatch, missing purpose label, OR HMAC
60
- * mismatch under EVERY supplied secret. Never throws.
61
- *
62
- * **Timing-uniform across rotation slots** — the array form runs ALL
63
- * HMAC computations + constant-time compares before returning, so the
64
- * total time is proportional to N (number of secrets) regardless of
65
- * which slot matched. A short-circuiting implementation would leak
66
- * the position of the matching key via total time. Practical impact
67
- * is small (attacker would only learn "current vs pending vs previous
68
- * matched", not key material), but timing-uniform is the right
69
- * default for a verification primitive.
70
- *
71
- * Recipient flow:
72
- * 1. Receive POST with `X-ARP-Signature` header + raw body bytes.
73
- * 2. Compute `payload_sha256 = sha256-hex(rawBodyBytes)`.
74
- * 3. Reconstruct `WebhookSignableInput` from headers + payload sha256.
75
- * 4. Call `verifyWebhookSignatureHeader(header, input, [current, pending, previous].filter(Boolean))`.
76
- * 5. On `true`, accept. On `false`, 401 the request.
77
- *
78
- * Helper `verifyAndCheckPayload` (below) bundles steps 2-4 into one
79
- * call for the common path.
80
- */
81
- export declare function verifyWebhookSignatureHeader(headerValue: string, input: WebhookSignableInput, sharedSecret: Uint8Array | Uint8Array[]): boolean;
82
- /**
83
- * Recipient convenience: combine body-hash binding + HMAC verify +
84
- * optional `served_at` clock-skew tolerance into one call.
85
- *
86
- * 1. Compute `payload_sha256 = sha256-hex(rawBody)` and OVERLAY it
87
- * onto `input.payload_sha256` (caller's value, if any, is
88
- * replaced).
89
- * 2. Verify `served_at` is within `toleranceMs` of `now()` (default
90
- * ±5 minutes; pass `null` to skip).
91
- * 3. Verify HMAC against each secret in `secrets`.
92
- *
93
- * Returns a tagged outcome the caller can branch on:
94
- * - `'ok'` — accept the delivery
95
- * - `'stale_attempt'` — served_at outside the tolerance window
96
- * - `'invalid_signature'` — HMAC didn't verify under any secret
97
- *
98
- * **Why no `body_mismatch` outcome** — `payload_sha256` is a derived
99
- * field bound to the raw body. The HMAC signs the input INCLUDING
100
- * `payload_sha256`, so a tampered body causes the recipient's
101
- * recomputed hash to differ from the server-signed one, which fails
102
- * the HMAC check as `'invalid_signature'`. Body tampering is
103
- * detected via the HMAC failure; if you need a dedicated diagnostic,
104
- * compute `sha256HexLower(rawBody)` yourself and compare to an
105
- * out-of-band trusted source (e.g. a signed mirror of the body).
106
- */
107
- export declare function verifyAndCheckPayload(args: {
108
- headerValue: string;
109
- /** Reconstructed signable input MINUS `payload_sha256` — helper computes that field from `rawBody`. */
110
- input: Omit<WebhookSignableInput, 'payload_sha256'>;
111
- rawBody: Uint8Array;
112
- secrets: Uint8Array | Uint8Array[];
113
- servedAtToleranceMs?: number | null;
114
- now?: Date;
115
- }): 'ok' | 'stale_attempt' | 'invalid_signature';
116
- /**
117
- * Probe-skip helper for recipient handlers. The `POST /v1/agents/me/webhook-config`
118
- * flow fires an UNSIGNED probe POST with the `X-ARP-Probe: 1` header.
119
- * Recipient handlers MUST 2xx without HMAC verification on probe
120
- * requests — otherwise the operator can't set their webhook URL
121
- * (the probe gets rejected as unsigned, the URL persist step fails).
122
- *
123
- * Use this at the top of your handler:
124
- *
125
- * ```ts
126
- * if (isProbeRequest(req.headers)) {
127
- * res.statusCode = 200;
128
- * res.end();
129
- * return;
130
- * }
131
- * // Otherwise: verify signature normally.
132
- * ```
133
- *
134
- * Header values are inspected case-insensitively per Node's
135
- * IncomingMessage convention.
136
- *
137
- * **SECURITY (spoofability)** — the helper is a pure header check;
138
- * it cannot itself defend against an attacker setting
139
- * `X-ARP-Probe: 1` to bypass HMAC verification. The probe branch
140
- * in your handler MUST be side-effect-free:
141
- *
142
- * - Respond 2xx with an empty body — no DB write, no downstream
143
- * fan-out, no metric increment that an attacker could amplify.
144
- * - Consider rate-limiting probe-skipped requests per source IP if
145
- * the surrounding stack doesn't already.
146
- * - Do NOT log full request bodies on the probe branch (an
147
- * attacker can pump log noise).
148
- * - If your handler genuinely has zero side-effects on a 200
149
- * response, this is automatic; otherwise treat the probe branch
150
- * as you would an unauthenticated health-check endpoint.
151
- */
152
- export declare function isProbeRequest(headers: Record<string, string | string[] | undefined>): boolean;
153
- /**
154
- * SHA-256 hex digest of the input bytes, lowercase. Exported for unit
155
- * tests + the rare consumer that needs to compute payload_sha256
156
- * outside `verifyAndCheckPayload`.
157
- */
158
- export declare function sha256HexLower(bytes: Uint8Array): string;