@heyanon-arp/sdk 0.0.2
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/LICENSE +21 -0
- package/README.md +147 -0
- package/dist/assets.d.ts +101 -0
- package/dist/attestation/attestation.d.ts +29 -0
- package/dist/attestation/index.d.ts +2 -0
- package/dist/attestation/scrypt-proof.d.ts +28 -0
- package/dist/canonical/canonicalize.d.ts +33 -0
- package/dist/canonical/index.d.ts +1 -0
- package/dist/challenge/challenge.d.ts +11 -0
- package/dist/challenge/index.d.ts +1 -0
- package/dist/cosignature/cosign.d.ts +35 -0
- package/dist/cosignature/index.d.ts +2 -0
- package/dist/did/document.d.ts +30 -0
- package/dist/did/format.d.ts +23 -0
- package/dist/did/index.d.ts +2 -0
- package/dist/envelope/index.d.ts +4 -0
- package/dist/envelope/sign.d.ts +28 -0
- package/dist/envelope/verify.d.ts +37 -0
- package/dist/escrow/caip19.d.ts +41 -0
- package/dist/escrow/condition-hash.d.ts +79 -0
- package/dist/escrow/create-lock.d.ts +39 -0
- package/dist/escrow/index.d.ts +4 -0
- package/dist/escrow/lock-id.d.ts +67 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +853 -0
- package/dist/index.mjs +761 -0
- package/dist/keys/base58btc.d.ts +10 -0
- package/dist/keys/ed25519.d.ts +33 -0
- package/dist/keys/index.d.ts +3 -0
- package/dist/purpose.d.ts +52 -0
- package/dist/server-chain/chain.d.ts +52 -0
- package/dist/server-chain/index.d.ts +2 -0
- package/dist/settlement/index.d.ts +4 -0
- package/dist/settlement/settlement.d.ts +111 -0
- package/dist/settlement/token-program.d.ts +72 -0
- package/dist/types/body.d.ts +263 -0
- package/dist/types/envelope.d.ts +121 -0
- package/dist/types/identity.d.ts +62 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/nonce.d.ts +9 -0
- package/dist/utils/timestamp.d.ts +17 -0
- package/dist/utils/uuid.d.ts +10 -0
- package/dist/webhook/index.d.ts +2 -0
- package/dist/webhook/webhook.d.ts +38 -0
- package/package.json +58 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
import { sha512, sha256 } from '@noble/hashes/sha2';
|
|
2
|
+
import { bytesToHex, randomBytes } from '@noble/hashes/utils';
|
|
3
|
+
import canonicalize from 'canonicalize';
|
|
4
|
+
import { base58, base64, base64urlnopad } from '@scure/base';
|
|
5
|
+
import * as ed from '@noble/ed25519';
|
|
6
|
+
import { hmac } from '@noble/hashes/hmac';
|
|
7
|
+
import { scrypt } from '@noble/hashes/scrypt';
|
|
8
|
+
|
|
9
|
+
// src/canonical/canonicalize.ts
|
|
10
|
+
function canonicalJson(value) {
|
|
11
|
+
const result = canonicalize(value);
|
|
12
|
+
if (result === void 0) {
|
|
13
|
+
throw new Error("canonicalJson: value serialized to undefined (top-level undefined?)");
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
function canonicalBytes(value) {
|
|
18
|
+
return new TextEncoder().encode(canonicalJson(value));
|
|
19
|
+
}
|
|
20
|
+
function canonicalSha256Hex(value) {
|
|
21
|
+
const digest = sha256(canonicalBytes(value));
|
|
22
|
+
return `sha256:${bytesToHex(digest)}`;
|
|
23
|
+
}
|
|
24
|
+
function base58btcEncode(bytes) {
|
|
25
|
+
return base58.encode(bytes);
|
|
26
|
+
}
|
|
27
|
+
function base58btcDecode(text) {
|
|
28
|
+
return base58.decode(text);
|
|
29
|
+
}
|
|
30
|
+
ed.etc.sha512Sync = (...messages) => sha512(ed.etc.concatBytes(...messages));
|
|
31
|
+
function generateKeyPair() {
|
|
32
|
+
const secretKey = ed.utils.randomPrivateKey();
|
|
33
|
+
const publicKey = ed.getPublicKey(secretKey);
|
|
34
|
+
return { secretKey, publicKey };
|
|
35
|
+
}
|
|
36
|
+
function getPublicKey2(secretKey) {
|
|
37
|
+
if (secretKey.length !== 32) {
|
|
38
|
+
throw new Error(`getPublicKey: expected 32-byte seed, got ${secretKey.length}`);
|
|
39
|
+
}
|
|
40
|
+
return ed.getPublicKey(secretKey);
|
|
41
|
+
}
|
|
42
|
+
function sign2(message, secretKey) {
|
|
43
|
+
return ed.sign(message, secretKey);
|
|
44
|
+
}
|
|
45
|
+
function verify2(signature, message, publicKey) {
|
|
46
|
+
try {
|
|
47
|
+
return ed.verify(signature, message, publicKey);
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/did/format.ts
|
|
54
|
+
var DID_PREFIX = "did:arp:";
|
|
55
|
+
var ED25519_PUBKEY_LENGTH = 32;
|
|
56
|
+
function formatDid(identityPublicKey) {
|
|
57
|
+
if (identityPublicKey.length !== ED25519_PUBKEY_LENGTH) {
|
|
58
|
+
throw new Error(`formatDid: expected ${ED25519_PUBKEY_LENGTH}-byte Ed25519 pubkey, got ${identityPublicKey.length}`);
|
|
59
|
+
}
|
|
60
|
+
return `${DID_PREFIX}${base58btcEncode(identityPublicKey)}`;
|
|
61
|
+
}
|
|
62
|
+
function parseDid(did) {
|
|
63
|
+
if (!did.startsWith(DID_PREFIX)) return null;
|
|
64
|
+
const body = did.slice(DID_PREFIX.length);
|
|
65
|
+
if (body.length === 0) return null;
|
|
66
|
+
let decoded;
|
|
67
|
+
try {
|
|
68
|
+
decoded = base58btcDecode(body);
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (decoded.length !== ED25519_PUBKEY_LENGTH) return null;
|
|
73
|
+
return decoded;
|
|
74
|
+
}
|
|
75
|
+
function isValidDid(did) {
|
|
76
|
+
return parseDid(did) !== null;
|
|
77
|
+
}
|
|
78
|
+
function signEnvelope(input) {
|
|
79
|
+
const body_hash = canonicalSha256Hex(input.body);
|
|
80
|
+
const attachments_hash = input.attachments === void 0 ? null : canonicalSha256Hex(input.attachments);
|
|
81
|
+
const protectedBlock = {
|
|
82
|
+
...input.protected,
|
|
83
|
+
body_hash,
|
|
84
|
+
attachments_hash
|
|
85
|
+
};
|
|
86
|
+
const signingInputBytes = canonicalBytes(
|
|
87
|
+
input.attachments === void 0 ? { protected: protectedBlock, body: input.body } : { protected: protectedBlock, body: input.body, attachments: input.attachments }
|
|
88
|
+
);
|
|
89
|
+
const sigBytes = sign2(signingInputBytes, input.identitySecretKey);
|
|
90
|
+
const sender_signature = `ed25519:${base64.encode(sigBytes)}`;
|
|
91
|
+
const envelope = {
|
|
92
|
+
protected: protectedBlock,
|
|
93
|
+
body: input.body,
|
|
94
|
+
sender_signature
|
|
95
|
+
};
|
|
96
|
+
if (input.attachments !== void 0) {
|
|
97
|
+
envelope.attachments = input.attachments;
|
|
98
|
+
}
|
|
99
|
+
return envelope;
|
|
100
|
+
}
|
|
101
|
+
function verifyEnvelope(envelope, senderIdentityPubkey) {
|
|
102
|
+
const recomputedBodyHash = canonicalSha256Hex(envelope.body);
|
|
103
|
+
if (recomputedBodyHash !== envelope.protected.body_hash) {
|
|
104
|
+
return { ok: false, reason: "body_hash_mismatch" };
|
|
105
|
+
}
|
|
106
|
+
const expectedAttachmentsHash = envelope.attachments === void 0 ? null : canonicalSha256Hex(envelope.attachments);
|
|
107
|
+
if (expectedAttachmentsHash !== envelope.protected.attachments_hash) {
|
|
108
|
+
return { ok: false, reason: "attachments_hash_mismatch" };
|
|
109
|
+
}
|
|
110
|
+
const sigPrefix = "ed25519:";
|
|
111
|
+
if (!envelope.sender_signature.startsWith(sigPrefix)) {
|
|
112
|
+
return { ok: false, reason: "signature_malformed", detail: "sender_signature missing ed25519: prefix" };
|
|
113
|
+
}
|
|
114
|
+
let sigBytes;
|
|
115
|
+
try {
|
|
116
|
+
sigBytes = base64.decode(envelope.sender_signature.slice(sigPrefix.length));
|
|
117
|
+
} catch {
|
|
118
|
+
return { ok: false, reason: "signature_malformed", detail: "sender_signature base64 decode failed" };
|
|
119
|
+
}
|
|
120
|
+
if (sigBytes.length !== 64) {
|
|
121
|
+
return { ok: false, reason: "signature_malformed", detail: `expected 64-byte signature, got ${sigBytes.length}` };
|
|
122
|
+
}
|
|
123
|
+
const signingInputBytes = canonicalBytes(
|
|
124
|
+
envelope.attachments === void 0 ? { protected: envelope.protected, body: envelope.body } : { protected: envelope.protected, body: envelope.body, attachments: envelope.attachments }
|
|
125
|
+
);
|
|
126
|
+
const ok = verify2(sigBytes, signingInputBytes, senderIdentityPubkey);
|
|
127
|
+
return ok ? { ok: true } : { ok: false, reason: "signature_invalid" };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/purpose.ts
|
|
131
|
+
var Purpose = {
|
|
132
|
+
/** Default for `protected.purpose` on body messages. */
|
|
133
|
+
ENVELOPE: "ARP-ENVELOPE-v1",
|
|
134
|
+
/** Receipt co-signature payload (delegation completion). */
|
|
135
|
+
RECEIPT: "ARP-RECEIPT-v1",
|
|
136
|
+
/** Dispute response co-signature payload. */
|
|
137
|
+
DISPUTE_RESPONSE: "ARP-DISPUTE-RESPONSE-v1",
|
|
138
|
+
/** Webhook delivery HMAC signature payload. */
|
|
139
|
+
WEBHOOK: "ARP-WEBHOOK-v1",
|
|
140
|
+
/** Identity ownership challenge proof. */
|
|
141
|
+
CHALLENGE: "ARP-CHALLENGE-v1",
|
|
142
|
+
/** Verifiable Credential issued by the platform. */
|
|
143
|
+
VC: "ARP-VC-v1",
|
|
144
|
+
/** Owner attestation linking identity ↔ settlement keys at registration. */
|
|
145
|
+
KEY_LINK: "ARP-KEY-LINK-v1",
|
|
146
|
+
/** Owner attestation for an identity-key rotation event. */
|
|
147
|
+
KEY_ROTATION: "ARP-KEY-ROTATION-v1",
|
|
148
|
+
/**
|
|
149
|
+
* Settlement-key signature authorising an on-chain `release_lock`.
|
|
150
|
+
* V1.5 — digest now binds fee_bps_at_lock + fee_recipient_at_lock.
|
|
151
|
+
*/
|
|
152
|
+
SOLANA_RELEASE: "ARP-SOLANA-RELEASE-v1.5",
|
|
153
|
+
/**
|
|
154
|
+
* Settlement-key signature authorising an on-chain `partial_release`.
|
|
155
|
+
* V1.5 — digest now binds fee_bps_at_lock + fee_recipient_at_lock.
|
|
156
|
+
*/
|
|
157
|
+
SOLANA_PARTIAL_RELEASE: "ARP-SOLANA-PARTIAL-RELEASE-v1.5",
|
|
158
|
+
/** Settlement-key signature authorising an on-chain co-signed `refund_lock`. */
|
|
159
|
+
SOLANA_REFUND: "ARP-SOLANA-REFUND-v1",
|
|
160
|
+
/** Admin (platform multisig) signature authorising `resolve_dispute`. */
|
|
161
|
+
SOLANA_RESOLVE_DISPUTE: "ARP-SOLANA-RESOLVE-DISPUTE-v1"
|
|
162
|
+
};
|
|
163
|
+
var PROTECTED_PURPOSES = [Purpose.ENVELOPE];
|
|
164
|
+
var COSIGNATURE_PURPOSES = [Purpose.RECEIPT, Purpose.DISPUTE_RESPONSE];
|
|
165
|
+
var SETTLEMENT_PURPOSES = [
|
|
166
|
+
Purpose.SOLANA_RELEASE,
|
|
167
|
+
Purpose.SOLANA_PARTIAL_RELEASE,
|
|
168
|
+
Purpose.SOLANA_REFUND,
|
|
169
|
+
Purpose.SOLANA_RESOLVE_DISPUTE
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
// src/cosignature/cosign.ts
|
|
173
|
+
var COSIGN_ALLOWED = [Purpose.RECEIPT, Purpose.DISPUTE_RESPONSE];
|
|
174
|
+
function signCosignature(input) {
|
|
175
|
+
const purpose = input.payload.purpose;
|
|
176
|
+
if (!COSIGN_ALLOWED.includes(purpose)) {
|
|
177
|
+
throw new Error(`signCosignature: purpose "${purpose}" is not allowed for co_signature`);
|
|
178
|
+
}
|
|
179
|
+
const digest = sha256(canonicalBytes(input.payload));
|
|
180
|
+
const sigBytes = sign2(digest, input.identitySecretKey);
|
|
181
|
+
const payload_hash = `sha256:${bytesToHex2(digest)}`;
|
|
182
|
+
const sig = `ed25519:${base64.encode(sigBytes)}`;
|
|
183
|
+
return {
|
|
184
|
+
agent_did: input.signerDid,
|
|
185
|
+
purpose,
|
|
186
|
+
payload_hash,
|
|
187
|
+
sig
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function verifyCosignature(input) {
|
|
191
|
+
if (input.cosignature.purpose !== input.payload.purpose) {
|
|
192
|
+
return { ok: false, reason: "purpose_mismatch" };
|
|
193
|
+
}
|
|
194
|
+
const digest = sha256(canonicalBytes(input.payload));
|
|
195
|
+
const expectedHash = `sha256:${bytesToHex2(digest)}`;
|
|
196
|
+
if (expectedHash !== input.cosignature.payload_hash) {
|
|
197
|
+
return { ok: false, reason: "payload_hash_mismatch" };
|
|
198
|
+
}
|
|
199
|
+
const sigPrefix = "ed25519:";
|
|
200
|
+
if (!input.cosignature.sig.startsWith(sigPrefix)) {
|
|
201
|
+
return { ok: false, reason: "signature_malformed", detail: "sig missing ed25519: prefix" };
|
|
202
|
+
}
|
|
203
|
+
let sigBytes;
|
|
204
|
+
try {
|
|
205
|
+
sigBytes = base64.decode(input.cosignature.sig.slice(sigPrefix.length));
|
|
206
|
+
} catch {
|
|
207
|
+
return { ok: false, reason: "signature_malformed", detail: "sig base64 decode failed" };
|
|
208
|
+
}
|
|
209
|
+
if (sigBytes.length !== 64) {
|
|
210
|
+
return { ok: false, reason: "signature_malformed", detail: `expected 64-byte signature, got ${sigBytes.length}` };
|
|
211
|
+
}
|
|
212
|
+
return verify2(sigBytes, digest, input.signerIdentityPubkey) ? { ok: true } : { ok: false, reason: "signature_invalid" };
|
|
213
|
+
}
|
|
214
|
+
function bytesToHex2(b) {
|
|
215
|
+
let s = "";
|
|
216
|
+
for (let i = 0; i < b.length; i++) {
|
|
217
|
+
s += b[i].toString(16).padStart(2, "0");
|
|
218
|
+
}
|
|
219
|
+
return s;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/challenge/challenge.ts
|
|
223
|
+
var CHALLENGE_PURPOSE_BYTES = new TextEncoder().encode(Purpose.CHALLENGE);
|
|
224
|
+
function buildSigningInput(challengeBytes) {
|
|
225
|
+
if (CHALLENGE_PURPOSE_BYTES.length > 255) {
|
|
226
|
+
throw new Error("purpose label too long for 1-byte prefix");
|
|
227
|
+
}
|
|
228
|
+
const out = new Uint8Array(1 + CHALLENGE_PURPOSE_BYTES.length + challengeBytes.length);
|
|
229
|
+
out[0] = CHALLENGE_PURPOSE_BYTES.length;
|
|
230
|
+
out.set(CHALLENGE_PURPOSE_BYTES, 1);
|
|
231
|
+
out.set(challengeBytes, 1 + CHALLENGE_PURPOSE_BYTES.length);
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
function signChallenge(challengeBytes, identitySecretKey) {
|
|
235
|
+
if (challengeBytes.length !== 32) {
|
|
236
|
+
throw new Error(`signChallenge: expected 32-byte challenge, got ${challengeBytes.length}`);
|
|
237
|
+
}
|
|
238
|
+
return sign2(buildSigningInput(challengeBytes), identitySecretKey);
|
|
239
|
+
}
|
|
240
|
+
function verifyChallenge(challengeBytes, signature, identityPubkey) {
|
|
241
|
+
if (challengeBytes.length !== 32) return false;
|
|
242
|
+
if (signature.length !== 64) return false;
|
|
243
|
+
return verify2(signature, buildSigningInput(challengeBytes), identityPubkey);
|
|
244
|
+
}
|
|
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 expected = buildWebhookSignatureHeader(input, sharedSecret);
|
|
252
|
+
return constantTimeEqual(expected, headerValue);
|
|
253
|
+
}
|
|
254
|
+
function constantTimeEqual(a, b) {
|
|
255
|
+
if (a.length !== b.length) return false;
|
|
256
|
+
let diff = 0;
|
|
257
|
+
for (let i = 0; i < a.length; i++) {
|
|
258
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
259
|
+
}
|
|
260
|
+
return diff === 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/types/identity.ts
|
|
264
|
+
var SCRYPT_PARAMS = {
|
|
265
|
+
N: 32768,
|
|
266
|
+
r: 8,
|
|
267
|
+
p: 1,
|
|
268
|
+
dkLen: 32
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// src/attestation/scrypt-proof.ts
|
|
272
|
+
function deriveScryptKey(password, salt) {
|
|
273
|
+
if (salt.length !== 16) {
|
|
274
|
+
throw new Error(`deriveScryptKey: expected 16-byte salt, got ${salt.length}`);
|
|
275
|
+
}
|
|
276
|
+
const passwordBytes = new TextEncoder().encode(password);
|
|
277
|
+
return scrypt(passwordBytes, salt, {
|
|
278
|
+
N: SCRYPT_PARAMS.N,
|
|
279
|
+
r: SCRYPT_PARAMS.r,
|
|
280
|
+
p: SCRYPT_PARAMS.p,
|
|
281
|
+
dkLen: SCRYPT_PARAMS.dkLen
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
function scryptPasswordProofSign(payload, scryptKey) {
|
|
285
|
+
if (scryptKey.length !== SCRYPT_PARAMS.dkLen) {
|
|
286
|
+
throw new Error(`scryptPasswordProofSign: expected ${SCRYPT_PARAMS.dkLen}-byte scrypt key, got ${scryptKey.length}`);
|
|
287
|
+
}
|
|
288
|
+
const digest = sha256(canonicalBytes(payload));
|
|
289
|
+
const mac = hmac(sha256, scryptKey, digest);
|
|
290
|
+
return base64.encode(mac);
|
|
291
|
+
}
|
|
292
|
+
function scryptPasswordProofVerify(payload, sigBase64, scryptKey) {
|
|
293
|
+
if (scryptKey.length !== SCRYPT_PARAMS.dkLen) return false;
|
|
294
|
+
let providedMac;
|
|
295
|
+
try {
|
|
296
|
+
providedMac = base64.decode(sigBase64);
|
|
297
|
+
} catch {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
if (providedMac.length !== 32) return false;
|
|
301
|
+
const digest = sha256(canonicalBytes(payload));
|
|
302
|
+
const expectedMac = hmac(sha256, scryptKey, digest);
|
|
303
|
+
return constantTimeEqualBytes(providedMac, expectedMac);
|
|
304
|
+
}
|
|
305
|
+
function constantTimeEqualBytes(a, b) {
|
|
306
|
+
if (a.length !== b.length) return false;
|
|
307
|
+
let diff = 0;
|
|
308
|
+
for (let i = 0; i < a.length; i++) {
|
|
309
|
+
diff |= a[i] ^ b[i];
|
|
310
|
+
}
|
|
311
|
+
return diff === 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/attestation/attestation.ts
|
|
315
|
+
function signKeyLinkAttestation(input) {
|
|
316
|
+
if (input.payload.purpose !== "ARP-KEY-LINK-v1") {
|
|
317
|
+
throw new Error(`signKeyLinkAttestation: expected purpose ARP-KEY-LINK-v1, got ${input.payload.purpose}`);
|
|
318
|
+
}
|
|
319
|
+
if (input.payload.owner_signing_method !== "scrypt_password_proof") {
|
|
320
|
+
throw new Error(`signKeyLinkAttestation: this helper handles scrypt_password_proof only; got ${input.payload.owner_signing_method}`);
|
|
321
|
+
}
|
|
322
|
+
const sig = scryptPasswordProofSign(input.payload, input.scryptKey);
|
|
323
|
+
return { payload: input.payload, sig, scrypt_salt_id: input.scryptSaltId };
|
|
324
|
+
}
|
|
325
|
+
function verifyKeyLinkAttestation(attestation, scryptKey) {
|
|
326
|
+
if (attestation.payload.purpose !== "ARP-KEY-LINK-v1") return false;
|
|
327
|
+
return scryptPasswordProofVerify(attestation.payload, attestation.sig, scryptKey);
|
|
328
|
+
}
|
|
329
|
+
function signKeyRotationAttestation(input) {
|
|
330
|
+
if (input.payload.purpose !== "ARP-KEY-ROTATION-v1") {
|
|
331
|
+
throw new Error(`signKeyRotationAttestation: expected purpose ARP-KEY-ROTATION-v1, got ${input.payload.purpose}`);
|
|
332
|
+
}
|
|
333
|
+
if (input.payload.owner_signing_method !== "scrypt_password_proof") {
|
|
334
|
+
throw new Error(`signKeyRotationAttestation: this helper handles scrypt_password_proof only; got ${input.payload.owner_signing_method}`);
|
|
335
|
+
}
|
|
336
|
+
const sig = scryptPasswordProofSign(input.payload, input.scryptKey);
|
|
337
|
+
return { payload: input.payload, sig, scrypt_salt_id: input.scryptSaltId };
|
|
338
|
+
}
|
|
339
|
+
function verifyKeyRotationAttestation(attestation, scryptKey) {
|
|
340
|
+
if (attestation.payload.purpose !== "ARP-KEY-ROTATION-v1") return false;
|
|
341
|
+
return scryptPasswordProofVerify(attestation.payload, attestation.sig, scryptKey);
|
|
342
|
+
}
|
|
343
|
+
function signedMessageHash(envelope) {
|
|
344
|
+
const input = envelope.attachments === void 0 ? { protected: envelope.protected, body: envelope.body } : { protected: envelope.protected, body: envelope.body, attachments: envelope.attachments };
|
|
345
|
+
const digest = sha256(canonicalBytes(input));
|
|
346
|
+
return `sha256:${bytesToHex3(digest)}`;
|
|
347
|
+
}
|
|
348
|
+
function serverEventHash(input) {
|
|
349
|
+
const canonicalInput = {
|
|
350
|
+
prev_server_event_hash: input.prevServerEventHash,
|
|
351
|
+
relationship_event_index: input.relationshipEventIndex,
|
|
352
|
+
server_timestamp: input.serverTimestamp,
|
|
353
|
+
signed_message_hash: input.signedMessageHash,
|
|
354
|
+
sender_signature: input.senderSignature
|
|
355
|
+
};
|
|
356
|
+
const digest = sha256(canonicalBytes(canonicalInput));
|
|
357
|
+
return `sha256:${bytesToHex3(digest)}`;
|
|
358
|
+
}
|
|
359
|
+
function findFirstChainDivergence(events) {
|
|
360
|
+
for (let i = 0; i < events.length; i++) {
|
|
361
|
+
const e = events[i];
|
|
362
|
+
const expected = serverEventHash({
|
|
363
|
+
prevServerEventHash: e.prev_server_event_hash,
|
|
364
|
+
relationshipEventIndex: e.relationship_event_index,
|
|
365
|
+
serverTimestamp: e.server_timestamp,
|
|
366
|
+
signedMessageHash: e.signed_message_hash,
|
|
367
|
+
senderSignature: e.sender_signature
|
|
368
|
+
});
|
|
369
|
+
if (expected !== e.server_event_hash) return i;
|
|
370
|
+
if (i > 0 && e.prev_server_event_hash !== events[i - 1].server_event_hash) return i;
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
function bytesToHex3(b) {
|
|
375
|
+
let s = "";
|
|
376
|
+
for (let i = 0; i < b.length; i++) {
|
|
377
|
+
s += b[i].toString(16).padStart(2, "0");
|
|
378
|
+
}
|
|
379
|
+
return s;
|
|
380
|
+
}
|
|
381
|
+
function deriveLockId(delegationId) {
|
|
382
|
+
const bytes16 = delegationIdToBytes16(delegationId);
|
|
383
|
+
const prefix = new TextEncoder().encode("arp-lock-v1");
|
|
384
|
+
const input = new Uint8Array(prefix.length + 16);
|
|
385
|
+
input.set(prefix, 0);
|
|
386
|
+
input.set(bytes16, prefix.length);
|
|
387
|
+
return sha256(input);
|
|
388
|
+
}
|
|
389
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
390
|
+
function delegationIdToBytes16(delegationId) {
|
|
391
|
+
const uuid = delegationId.startsWith("del_") ? delegationId.slice(4) : delegationId;
|
|
392
|
+
if (!UUID_RE.test(uuid)) {
|
|
393
|
+
if (delegationId.startsWith("del_")) {
|
|
394
|
+
throw new Error(`Invalid delegation_id UUID (must be canonical lowercase): ${JSON.stringify(uuid)}`);
|
|
395
|
+
}
|
|
396
|
+
throw new Error(`Invalid delegation_id format: expected "del_<uuid>" or bare canonical-lowercase UUID, got ${JSON.stringify(delegationId)}`);
|
|
397
|
+
}
|
|
398
|
+
const hex = uuid.replace(/-/g, "");
|
|
399
|
+
const out = new Uint8Array(16);
|
|
400
|
+
for (let i = 0; i < 16; i++) {
|
|
401
|
+
out[i] = Number.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
|
402
|
+
}
|
|
403
|
+
return out;
|
|
404
|
+
}
|
|
405
|
+
function bytes16ToDelegationId(bytes) {
|
|
406
|
+
if (bytes.length !== 16) {
|
|
407
|
+
throw new Error(`bytes16ToDelegationId: expected 16 bytes, got ${bytes.length}`);
|
|
408
|
+
}
|
|
409
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
410
|
+
return `del_${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/settlement/settlement.ts
|
|
414
|
+
var PURPOSE_RELEASE_STRING = "ARP-SOLANA-RELEASE-v1.5";
|
|
415
|
+
var PURPOSE_PARTIAL_RELEASE_STRING = "ARP-SOLANA-PARTIAL-RELEASE-v1.5";
|
|
416
|
+
var PURPOSE_REFUND_STRING = "ARP-SOLANA-REFUND-v1";
|
|
417
|
+
var PURPOSE_RELEASE = new TextEncoder().encode(PURPOSE_RELEASE_STRING);
|
|
418
|
+
var PURPOSE_PARTIAL_RELEASE = new TextEncoder().encode(PURPOSE_PARTIAL_RELEASE_STRING);
|
|
419
|
+
var PURPOSE_REFUND = new TextEncoder().encode(PURPOSE_REFUND_STRING);
|
|
420
|
+
var ZERO_PUBKEY = new Uint8Array(32);
|
|
421
|
+
function buildReleaseDigest(input) {
|
|
422
|
+
requireLen(input.programId, 32, "programId");
|
|
423
|
+
requireLen(input.lockId, 32, "lockId");
|
|
424
|
+
requireLen(input.payerSettlementPubkey, 32, "payerSettlementPubkey");
|
|
425
|
+
requireLen(input.payeeSettlementPubkey, 32, "payeeSettlementPubkey");
|
|
426
|
+
requireLen(input.mint, 32, "mint");
|
|
427
|
+
requireLen(input.conditionHash, 32, "conditionHash");
|
|
428
|
+
requireLen(input.receiptEventHash, 32, "receiptEventHash");
|
|
429
|
+
requireLen(input.deliverableHash, 32, "deliverableHash");
|
|
430
|
+
const feeBps = input.feeBpsAtLock ?? 0;
|
|
431
|
+
requireFeeBps(feeBps);
|
|
432
|
+
const feeRecipient = input.feeRecipientAtLock ?? ZERO_PUBKEY;
|
|
433
|
+
requireLen(feeRecipient, 32, "feeRecipientAtLock");
|
|
434
|
+
const delegationBytes = delegationIdToBytes16(input.delegationId);
|
|
435
|
+
const buf = concatBytes(
|
|
436
|
+
PURPOSE_RELEASE,
|
|
437
|
+
new Uint8Array([input.clusterTag]),
|
|
438
|
+
input.programId,
|
|
439
|
+
input.lockId,
|
|
440
|
+
input.payerSettlementPubkey,
|
|
441
|
+
input.payeeSettlementPubkey,
|
|
442
|
+
input.mint,
|
|
443
|
+
u64LE(input.amount),
|
|
444
|
+
input.conditionHash,
|
|
445
|
+
delegationBytes,
|
|
446
|
+
input.receiptEventHash,
|
|
447
|
+
input.deliverableHash,
|
|
448
|
+
u64LE(input.expiresAt),
|
|
449
|
+
u16LE(feeBps),
|
|
450
|
+
feeRecipient
|
|
451
|
+
);
|
|
452
|
+
return sha256(buf);
|
|
453
|
+
}
|
|
454
|
+
function buildPartialReleaseDigest(input) {
|
|
455
|
+
requireLen(input.programId, 32, "programId");
|
|
456
|
+
requireLen(input.lockId, 32, "lockId");
|
|
457
|
+
requireLen(input.payerSettlementPubkey, 32, "payerSettlementPubkey");
|
|
458
|
+
requireLen(input.payeeSettlementPubkey, 32, "payeeSettlementPubkey");
|
|
459
|
+
requireLen(input.mint, 32, "mint");
|
|
460
|
+
requireLen(input.conditionHash, 32, "conditionHash");
|
|
461
|
+
requireLen(input.receiptEventHash, 32, "receiptEventHash");
|
|
462
|
+
requireLen(input.deliverableHash, 32, "deliverableHash");
|
|
463
|
+
const feeBps = input.feeBpsAtLock ?? 0;
|
|
464
|
+
requireFeeBps(feeBps);
|
|
465
|
+
const feeRecipient = input.feeRecipientAtLock ?? ZERO_PUBKEY;
|
|
466
|
+
requireLen(feeRecipient, 32, "feeRecipientAtLock");
|
|
467
|
+
const delegationBytes = delegationIdToBytes16(input.delegationId);
|
|
468
|
+
const buf = concatBytes(
|
|
469
|
+
PURPOSE_PARTIAL_RELEASE,
|
|
470
|
+
new Uint8Array([input.clusterTag]),
|
|
471
|
+
input.programId,
|
|
472
|
+
input.lockId,
|
|
473
|
+
input.payerSettlementPubkey,
|
|
474
|
+
input.payeeSettlementPubkey,
|
|
475
|
+
input.mint,
|
|
476
|
+
u64LE(input.amount),
|
|
477
|
+
u64LE(input.payeeAmount),
|
|
478
|
+
input.conditionHash,
|
|
479
|
+
delegationBytes,
|
|
480
|
+
input.receiptEventHash,
|
|
481
|
+
input.deliverableHash,
|
|
482
|
+
u64LE(input.expiresAt),
|
|
483
|
+
u16LE(feeBps),
|
|
484
|
+
feeRecipient
|
|
485
|
+
);
|
|
486
|
+
return sha256(buf);
|
|
487
|
+
}
|
|
488
|
+
var REFUND_REASON_BYTES = {
|
|
489
|
+
expired: 0,
|
|
490
|
+
dispute_resolution: 1,
|
|
491
|
+
payer_cancellation: 2,
|
|
492
|
+
both_parties_agreed: 3
|
|
493
|
+
};
|
|
494
|
+
function buildRefundDigest(input) {
|
|
495
|
+
requireLen(input.programId, 32, "programId");
|
|
496
|
+
requireLen(input.lockId, 32, "lockId");
|
|
497
|
+
requireLen(input.payerSettlementPubkey, 32, "payerSettlementPubkey");
|
|
498
|
+
requireLen(input.payeeSettlementPubkey, 32, "payeeSettlementPubkey");
|
|
499
|
+
requireLen(input.mint, 32, "mint");
|
|
500
|
+
const buf = concatBytes(
|
|
501
|
+
PURPOSE_REFUND,
|
|
502
|
+
new Uint8Array([input.clusterTag]),
|
|
503
|
+
input.programId,
|
|
504
|
+
input.lockId,
|
|
505
|
+
input.payerSettlementPubkey,
|
|
506
|
+
input.payeeSettlementPubkey,
|
|
507
|
+
input.mint,
|
|
508
|
+
u64LE(input.amount),
|
|
509
|
+
new Uint8Array([input.reasonByte]),
|
|
510
|
+
u64LE(input.expiresAt)
|
|
511
|
+
);
|
|
512
|
+
return sha256(buf);
|
|
513
|
+
}
|
|
514
|
+
function requireLen(bytes, expected, name) {
|
|
515
|
+
if (bytes.length !== expected) {
|
|
516
|
+
throw new Error(`settlement digest: ${name} must be ${expected} bytes, got ${bytes.length}`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function requireFeeBps(bps) {
|
|
520
|
+
if (!Number.isInteger(bps) || bps < 0 || bps > 65535) {
|
|
521
|
+
throw new Error(`settlement digest: feeBpsAtLock must be a u16 (0..65535), got ${bps}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function concatBytes(...parts) {
|
|
525
|
+
let total = 0;
|
|
526
|
+
for (const p of parts) total += p.length;
|
|
527
|
+
const out = new Uint8Array(total);
|
|
528
|
+
let off = 0;
|
|
529
|
+
for (const p of parts) {
|
|
530
|
+
out.set(p, off);
|
|
531
|
+
off += p.length;
|
|
532
|
+
}
|
|
533
|
+
return out;
|
|
534
|
+
}
|
|
535
|
+
function u64LE(value) {
|
|
536
|
+
if (value < 0n || value > 0xffffffffffffffffn) {
|
|
537
|
+
throw new Error(`settlement digest: u64 value out of range: ${value}`);
|
|
538
|
+
}
|
|
539
|
+
const out = new Uint8Array(8);
|
|
540
|
+
let v = value;
|
|
541
|
+
for (let i = 0; i < 8; i++) {
|
|
542
|
+
out[i] = Number(v & 0xffn);
|
|
543
|
+
v >>= 8n;
|
|
544
|
+
}
|
|
545
|
+
return out;
|
|
546
|
+
}
|
|
547
|
+
function u16LE(value) {
|
|
548
|
+
const out = new Uint8Array(2);
|
|
549
|
+
out[0] = value & 255;
|
|
550
|
+
out[1] = value >> 8 & 255;
|
|
551
|
+
return out;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/settlement/token-program.ts
|
|
555
|
+
var SPL_TOKEN_PROGRAM_ID_BASE58 = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
556
|
+
var TOKEN_2022_PROGRAM_ID_BASE58 = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
|
|
557
|
+
function detectTokenProgramFromOwner(mintAccountOwnerBase58) {
|
|
558
|
+
switch (mintAccountOwnerBase58) {
|
|
559
|
+
case SPL_TOKEN_PROGRAM_ID_BASE58:
|
|
560
|
+
return { kind: "legacy", programIdBase58: SPL_TOKEN_PROGRAM_ID_BASE58 };
|
|
561
|
+
case TOKEN_2022_PROGRAM_ID_BASE58:
|
|
562
|
+
return { kind: "token-2022", programIdBase58: TOKEN_2022_PROGRAM_ID_BASE58 };
|
|
563
|
+
default:
|
|
564
|
+
throw new Error(
|
|
565
|
+
`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`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function detectTokenProgramFromOwnerBytes(mintAccountOwnerBytes) {
|
|
570
|
+
if (mintAccountOwnerBytes.length !== 32) {
|
|
571
|
+
throw new Error(`detectTokenProgram: mintAccountOwnerBytes must be 32 bytes, got ${mintAccountOwnerBytes.length}`);
|
|
572
|
+
}
|
|
573
|
+
return detectTokenProgramFromOwner(bytesToBase58(mintAccountOwnerBytes));
|
|
574
|
+
}
|
|
575
|
+
function bytesToBase58(bytes) {
|
|
576
|
+
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
577
|
+
let zeros = 0;
|
|
578
|
+
for (let i = 0; i < bytes.length && bytes[i] === 0; i++) zeros++;
|
|
579
|
+
let num = 0n;
|
|
580
|
+
for (const b of bytes) {
|
|
581
|
+
num = num * 256n + BigInt(b);
|
|
582
|
+
}
|
|
583
|
+
let out = "";
|
|
584
|
+
while (num > 0n) {
|
|
585
|
+
const rem = Number(num % 58n);
|
|
586
|
+
num = num / 58n;
|
|
587
|
+
out = ALPHABET[rem] + out;
|
|
588
|
+
}
|
|
589
|
+
return "1".repeat(zeros) + out;
|
|
590
|
+
}
|
|
591
|
+
function uuidV4() {
|
|
592
|
+
const bytes = randomBytes(16);
|
|
593
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
594
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
595
|
+
const hex = bytesToHex16(bytes);
|
|
596
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
597
|
+
}
|
|
598
|
+
function bytesToHex16(b) {
|
|
599
|
+
let s = "";
|
|
600
|
+
for (let i = 0; i < b.length; i++) {
|
|
601
|
+
s += b[i].toString(16).padStart(2, "0");
|
|
602
|
+
}
|
|
603
|
+
return s;
|
|
604
|
+
}
|
|
605
|
+
function senderNonce() {
|
|
606
|
+
return base64urlnopad.encode(randomBytes(16));
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/utils/timestamp.ts
|
|
610
|
+
function rfc3339(at = /* @__PURE__ */ new Date()) {
|
|
611
|
+
return `${at.toISOString().slice(0, 19)}Z`;
|
|
612
|
+
}
|
|
613
|
+
function expiresAt(ttlSeconds, now = /* @__PURE__ */ new Date()) {
|
|
614
|
+
if (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0) {
|
|
615
|
+
throw new Error(`expiresAt: ttlSeconds must be positive, got ${ttlSeconds}`);
|
|
616
|
+
}
|
|
617
|
+
if (ttlSeconds > 24 * 60 * 60) {
|
|
618
|
+
throw new Error(`expiresAt: ttlSeconds exceeds 24h cap, got ${ttlSeconds}`);
|
|
619
|
+
}
|
|
620
|
+
return rfc3339(new Date(now.getTime() + ttlSeconds * 1e3));
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/types/body.ts
|
|
624
|
+
var DECLINE_REASONS = ["missing_brief", "rate_too_low", "out_of_scope", "policy", "expired_proposal", "capacity", "unspecified", "other"];
|
|
625
|
+
function isDeclineReason(v) {
|
|
626
|
+
return typeof v === "string" && DECLINE_REASONS.includes(v);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/assets.ts
|
|
630
|
+
var SOLANA_CLUSTER_IDS = {
|
|
631
|
+
"solana-mainnet": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
632
|
+
"solana-devnet": "EtWTRABZaYq6iMfeYKouRu166VU2xqa1"
|
|
633
|
+
};
|
|
634
|
+
var SLIP44_SOLANA = 501;
|
|
635
|
+
var USDC_MINTS = {
|
|
636
|
+
"solana-mainnet": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
637
|
+
"solana-devnet": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
|
|
638
|
+
};
|
|
639
|
+
var WELL_KNOWN_ASSETS = {
|
|
640
|
+
"USDC:solana-mainnet": {
|
|
641
|
+
asset_id: `solana:${SOLANA_CLUSTER_IDS["solana-mainnet"]}/spl:${USDC_MINTS["solana-mainnet"]}`,
|
|
642
|
+
decimals: 6,
|
|
643
|
+
symbol: "USDC"
|
|
644
|
+
},
|
|
645
|
+
"USDC:solana-devnet": {
|
|
646
|
+
asset_id: `solana:${SOLANA_CLUSTER_IDS["solana-devnet"]}/spl:${USDC_MINTS["solana-devnet"]}`,
|
|
647
|
+
decimals: 6,
|
|
648
|
+
symbol: "USDC"
|
|
649
|
+
},
|
|
650
|
+
"SOL:solana-mainnet": {
|
|
651
|
+
asset_id: `solana:${SOLANA_CLUSTER_IDS["solana-mainnet"]}/slip44:${SLIP44_SOLANA}`,
|
|
652
|
+
decimals: 9,
|
|
653
|
+
symbol: "SOL"
|
|
654
|
+
},
|
|
655
|
+
"SOL:solana-devnet": {
|
|
656
|
+
asset_id: `solana:${SOLANA_CLUSTER_IDS["solana-devnet"]}/slip44:${SLIP44_SOLANA}`,
|
|
657
|
+
decimals: 9,
|
|
658
|
+
symbol: "SOL"
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
var CAIP19_REGEX = /^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}\/[-a-z0-9]{3,8}:[-.%a-zA-Z0-9]{1,128}$/;
|
|
662
|
+
function isAssetIdentifier(value) {
|
|
663
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
664
|
+
const v = value;
|
|
665
|
+
if (typeof v.asset_id !== "string" || !CAIP19_REGEX.test(v.asset_id)) return false;
|
|
666
|
+
if (typeof v.decimals !== "number" || !Number.isInteger(v.decimals) || v.decimals < 0 || v.decimals > 18) return false;
|
|
667
|
+
if (v.symbol !== void 0 && (typeof v.symbol !== "string" || v.symbol.length === 0 || v.symbol.length > 16)) return false;
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
function resolveAsset(input) {
|
|
671
|
+
const hit = WELL_KNOWN_ASSETS[input];
|
|
672
|
+
if (hit) return { ...hit };
|
|
673
|
+
if (CAIP19_REGEX.test(input)) {
|
|
674
|
+
return { asset_id: input, decimals: NaN };
|
|
675
|
+
}
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
var WELL_KNOWN_ASSET_KEYS = Object.keys(WELL_KNOWN_ASSETS);
|
|
679
|
+
function deriveConditionHash(contract) {
|
|
680
|
+
const required = ["contractId", "version", "scopeSummary", "pricingModel", "settlementModel"];
|
|
681
|
+
for (const field of required) {
|
|
682
|
+
if (contract[field] === void 0) {
|
|
683
|
+
throw new Error(`deriveConditionHash: required field '${String(field)}' is missing from the contract input`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const subset = {
|
|
687
|
+
contractId: contract.contractId,
|
|
688
|
+
version: contract.version,
|
|
689
|
+
scopeSummary: contract.scopeSummary,
|
|
690
|
+
pricingModel: contract.pricingModel,
|
|
691
|
+
settlementModel: contract.settlementModel
|
|
692
|
+
};
|
|
693
|
+
if (contract.rateAmount !== void 0) subset.rateAmount = contract.rateAmount;
|
|
694
|
+
if (contract.rateCurrency !== void 0) subset.rateCurrency = contract.rateCurrency;
|
|
695
|
+
if (contract.rateUnit !== void 0) subset.rateUnit = contract.rateUnit;
|
|
696
|
+
if (contract.allowedDelegationTags !== void 0) subset.allowedDelegationTags = contract.allowedDelegationTags;
|
|
697
|
+
const bytes = canonicalBytes(subset);
|
|
698
|
+
return sha256(bytes);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/escrow/caip19.ts
|
|
702
|
+
var CAIP19_RE = /^solana:([-_a-zA-Z0-9]{1,32})\/([-a-z0-9]{3,8}):([-.%a-zA-Z0-9]{1,128})$/;
|
|
703
|
+
function parseCaip19SolanaAssetId(assetId) {
|
|
704
|
+
const m = CAIP19_RE.exec(assetId);
|
|
705
|
+
if (!m) {
|
|
706
|
+
throw new Error(`CAIP-19 asset_id is malformed: ${JSON.stringify(assetId)}`);
|
|
707
|
+
}
|
|
708
|
+
const cluster = m[1];
|
|
709
|
+
const namespaceRaw = m[2];
|
|
710
|
+
const reference = m[3];
|
|
711
|
+
if (namespaceRaw === "slip44") {
|
|
712
|
+
if (reference !== "501") {
|
|
713
|
+
throw new Error(`Unsupported slip44 coin index: only 501 (SOL) is supported, got ${reference}`);
|
|
714
|
+
}
|
|
715
|
+
return { cluster, namespace: "slip44", reference, isNative: true };
|
|
716
|
+
}
|
|
717
|
+
if (namespaceRaw === "spl") {
|
|
718
|
+
return { cluster, namespace: "spl", reference, isNative: false };
|
|
719
|
+
}
|
|
720
|
+
throw new Error(`Unsupported CAIP-19 namespace: ${JSON.stringify(namespaceRaw)} (only 'spl' and 'slip44' are supported on Solana)`);
|
|
721
|
+
}
|
|
722
|
+
var CREATE_LOCK_DISCRIMINATOR = new Uint8Array([171, 216, 92, 167, 165, 8, 153, 90]);
|
|
723
|
+
function buildCreateLockIxData(args) {
|
|
724
|
+
if (args.lockId.length !== 32) throw new Error("create_lock: lockId must be 32 bytes");
|
|
725
|
+
if (args.conditionHash.length !== 32) throw new Error("create_lock: conditionHash must be 32 bytes");
|
|
726
|
+
requireU64(args.amount, "amount");
|
|
727
|
+
requireU64(args.expiry, "expiry");
|
|
728
|
+
const total = 8 + 32 + 8 + 32 + 8;
|
|
729
|
+
const out = new Uint8Array(total);
|
|
730
|
+
let off = 0;
|
|
731
|
+
out.set(CREATE_LOCK_DISCRIMINATOR, off);
|
|
732
|
+
off += 8;
|
|
733
|
+
out.set(args.lockId, off);
|
|
734
|
+
off += 32;
|
|
735
|
+
writeU64LE(out, off, args.amount);
|
|
736
|
+
off += 8;
|
|
737
|
+
out.set(args.conditionHash, off);
|
|
738
|
+
off += 32;
|
|
739
|
+
writeU64LE(out, off, args.expiry);
|
|
740
|
+
off += 8;
|
|
741
|
+
if (off !== total) throw new Error(`create_lock ix data layout drift: ${off} != ${total}`);
|
|
742
|
+
return out;
|
|
743
|
+
}
|
|
744
|
+
function requireU64(value, name) {
|
|
745
|
+
if (value < 0n || value > 0xffffffffffffffffn) {
|
|
746
|
+
throw new Error(`create_lock: ${name} out of u64 range`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
function writeU64LE(buf, off, value) {
|
|
750
|
+
let v = value;
|
|
751
|
+
for (let i = 0; i < 8; i++) {
|
|
752
|
+
buf[off + i] = Number(v & 0xffn);
|
|
753
|
+
v >>= 8n;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function computeCreateLockDiscriminator() {
|
|
757
|
+
const h = sha256(new TextEncoder().encode("global:create_lock"));
|
|
758
|
+
return h.slice(0, 8);
|
|
759
|
+
}
|
|
760
|
+
|
|
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 };
|