@omegax/protocol-sdk 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/claims.js ADDED
@@ -0,0 +1,361 @@
1
+ import bs58 from 'bs58';
2
+ import nacl from 'tweetnacl';
3
+ import { PublicKey, SystemProgram, Transaction, TransactionInstruction, } from '@solana/web3.js';
4
+ import { anchorDiscriminator, encodeU64Le, fromHex, hashStringTo32, } from './utils.js';
5
+ import { deriveClaimDelegatePda, deriveClaimPda, deriveClaimV2Pda, deriveConfigV2Pda, deriveOutcomeAggregatePda, derivePoolOraclePolicyPda, derivePoolTermsPda, deriveCycleWindowPda, deriveCycleOutcomePda, deriveMembershipPda, } from './protocol_seeds.js';
6
+ const SUBMIT_CLAIM_DISCRIMINATOR = anchorDiscriminator('global', 'submit_claim');
7
+ const SUBMIT_REWARD_CLAIM_DISCRIMINATOR = anchorDiscriminator('global', 'submit_reward_claim');
8
+ function validateRewardClaimOptionalAccounts(params) {
9
+ const providedCount = [
10
+ params.poolAssetVault,
11
+ params.poolVaultTokenAccount,
12
+ params.recipientTokenAccount,
13
+ ].filter((value) => typeof value === 'string' && value.length > 0).length;
14
+ if (providedCount !== 0 && providedCount !== 3) {
15
+ throw new Error('poolAssetVault, poolVaultTokenAccount, and recipientTokenAccount must be provided together');
16
+ }
17
+ }
18
+ function serializeSubmitClaimPayload(params) {
19
+ const cycleHash = hashStringTo32(params.cycleId);
20
+ const intentHash = hashStringTo32(params.intentId);
21
+ return Buffer.concat([
22
+ SUBMIT_CLAIM_DISCRIMINATOR,
23
+ Buffer.from(cycleHash),
24
+ Buffer.from(intentHash),
25
+ ]);
26
+ }
27
+ function serializeSubmitRewardClaimPayload(params) {
28
+ const cycleHash = hashStringTo32(params.cycleId);
29
+ const ruleHash = fromHex(params.ruleHashHex, 32);
30
+ const intentHash = fromHex(params.intentHashHex, 32);
31
+ return Buffer.concat([
32
+ SUBMIT_REWARD_CLAIM_DISCRIMINATOR,
33
+ new PublicKey(params.member).toBuffer(),
34
+ Buffer.from(cycleHash),
35
+ Buffer.from(ruleHash),
36
+ Buffer.from(intentHash),
37
+ encodeU64Le(params.payoutAmount),
38
+ new PublicKey(params.recipient).toBuffer(),
39
+ ]);
40
+ }
41
+ export function buildUnsignedClaimTx(params) {
42
+ const claimant = new PublicKey(params.claimantWallet);
43
+ const programId = new PublicKey(params.programId);
44
+ const poolAddress = new PublicKey(params.poolAddress);
45
+ const cycleHash = hashStringTo32(params.cycleId);
46
+ const [membershipPda] = deriveMembershipPda({
47
+ programId,
48
+ poolAddress,
49
+ member: claimant,
50
+ });
51
+ const [cycleOutcomePda] = deriveCycleOutcomePda({
52
+ programId,
53
+ poolAddress,
54
+ member: claimant,
55
+ cycleHash,
56
+ });
57
+ const [claimPda] = deriveClaimPda({
58
+ programId,
59
+ poolAddress,
60
+ member: claimant,
61
+ cycleHash,
62
+ });
63
+ const [cycleWindowPda] = deriveCycleWindowPda({
64
+ programId,
65
+ poolAddress,
66
+ cycleHash,
67
+ });
68
+ const instruction = new TransactionInstruction({
69
+ keys: [
70
+ {
71
+ pubkey: claimant,
72
+ isSigner: true,
73
+ isWritable: true,
74
+ },
75
+ {
76
+ pubkey: poolAddress,
77
+ isSigner: false,
78
+ isWritable: true,
79
+ },
80
+ {
81
+ pubkey: membershipPda,
82
+ isSigner: false,
83
+ isWritable: false,
84
+ },
85
+ {
86
+ pubkey: cycleOutcomePda,
87
+ isSigner: false,
88
+ isWritable: true,
89
+ },
90
+ {
91
+ pubkey: cycleWindowPda,
92
+ isSigner: false,
93
+ isWritable: false,
94
+ },
95
+ {
96
+ pubkey: claimPda,
97
+ isSigner: false,
98
+ isWritable: true,
99
+ },
100
+ {
101
+ pubkey: SystemProgram.programId,
102
+ isSigner: false,
103
+ isWritable: false,
104
+ },
105
+ ],
106
+ programId,
107
+ data: serializeSubmitClaimPayload(params),
108
+ });
109
+ const tx = new Transaction({
110
+ recentBlockhash: params.recentBlockhash,
111
+ feePayer: claimant,
112
+ }).add(instruction);
113
+ const unsignedTxBase64 = tx
114
+ .serialize({ requireAllSignatures: false, verifySignatures: false })
115
+ .toString('base64');
116
+ return {
117
+ intentId: params.intentId,
118
+ unsignedTxBase64,
119
+ requiredSigner: claimant.toBase58(),
120
+ expiresAtIso: params.expiresAtIso,
121
+ attestationRefs: params.attestationRefs,
122
+ };
123
+ }
124
+ export function buildUnsignedRewardClaimTx(params) {
125
+ validateRewardClaimOptionalAccounts(params);
126
+ const claimant = new PublicKey(params.claimantWallet);
127
+ const member = new PublicKey(params.member);
128
+ const programId = new PublicKey(params.programId);
129
+ const poolAddress = new PublicKey(params.poolAddress);
130
+ const cycleHash = hashStringTo32(params.cycleId);
131
+ const ruleHash = fromHex(params.ruleHashHex, 32);
132
+ const [configV2Pda] = deriveConfigV2Pda(programId);
133
+ const [poolTermsPda] = derivePoolTermsPda({
134
+ programId,
135
+ poolAddress,
136
+ });
137
+ const [poolOraclePolicyPda] = derivePoolOraclePolicyPda({
138
+ programId,
139
+ poolAddress,
140
+ });
141
+ const [membershipPda] = deriveMembershipPda({
142
+ programId,
143
+ poolAddress,
144
+ member,
145
+ });
146
+ const [aggregatePda] = deriveOutcomeAggregatePda({
147
+ programId,
148
+ poolAddress,
149
+ member,
150
+ cycleHash,
151
+ ruleHash,
152
+ });
153
+ const [claimRecordV2Pda] = deriveClaimV2Pda({
154
+ programId,
155
+ poolAddress,
156
+ member,
157
+ cycleHash,
158
+ ruleHash,
159
+ });
160
+ const [claimDelegatePda] = deriveClaimDelegatePda({
161
+ programId,
162
+ poolAddress,
163
+ member,
164
+ });
165
+ const optionPlaceholder = programId;
166
+ const claimDelegateAccount = params.claimDelegate ? claimDelegatePda : optionPlaceholder;
167
+ const poolAssetVaultAccount = params.poolAssetVault
168
+ ? new PublicKey(params.poolAssetVault)
169
+ : optionPlaceholder;
170
+ const poolVaultTokenAccount = params.poolVaultTokenAccount
171
+ ? new PublicKey(params.poolVaultTokenAccount)
172
+ : optionPlaceholder;
173
+ const recipientTokenAccount = params.recipientTokenAccount
174
+ ? new PublicKey(params.recipientTokenAccount)
175
+ : optionPlaceholder;
176
+ const instruction = new TransactionInstruction({
177
+ keys: [
178
+ { pubkey: claimant, isSigner: true, isWritable: true },
179
+ { pubkey: configV2Pda, isSigner: false, isWritable: false },
180
+ { pubkey: poolAddress, isSigner: false, isWritable: true },
181
+ { pubkey: poolTermsPda, isSigner: false, isWritable: false },
182
+ { pubkey: poolOraclePolicyPda, isSigner: false, isWritable: false },
183
+ { pubkey: membershipPda, isSigner: false, isWritable: false },
184
+ { pubkey: aggregatePda, isSigner: false, isWritable: true },
185
+ { pubkey: new PublicKey(params.recipientSystemAccount), isSigner: false, isWritable: true },
186
+ { pubkey: claimDelegateAccount, isSigner: false, isWritable: false },
187
+ { pubkey: poolAssetVaultAccount, isSigner: false, isWritable: false },
188
+ { pubkey: poolVaultTokenAccount, isSigner: false, isWritable: params.poolVaultTokenAccount != null },
189
+ { pubkey: recipientTokenAccount, isSigner: false, isWritable: params.recipientTokenAccount != null },
190
+ { pubkey: claimRecordV2Pda, isSigner: false, isWritable: true },
191
+ { pubkey: new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), isSigner: false, isWritable: false },
192
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
193
+ ],
194
+ programId,
195
+ data: serializeSubmitRewardClaimPayload(params),
196
+ });
197
+ const tx = new Transaction({
198
+ recentBlockhash: params.recentBlockhash,
199
+ feePayer: claimant,
200
+ }).add(instruction);
201
+ const unsignedTxBase64 = tx
202
+ .serialize({ requireAllSignatures: false, verifySignatures: false })
203
+ .toString('base64');
204
+ return {
205
+ intentHashHex: Buffer.from(fromHex(params.intentHashHex, 32)).toString('hex'),
206
+ unsignedTxBase64,
207
+ requiredSigner: claimant.toBase58(),
208
+ };
209
+ }
210
+ function firstSignatureBase58(tx) {
211
+ if (tx.signatures.length === 0)
212
+ return null;
213
+ const first = tx.signatures[0];
214
+ if (!first?.signature)
215
+ return null;
216
+ return bs58.encode(first.signature);
217
+ }
218
+ export function validateSignedClaimTx(params) {
219
+ let tx;
220
+ try {
221
+ tx = Transaction.from(Buffer.from(params.signedTxBase64, 'base64'));
222
+ }
223
+ catch {
224
+ return {
225
+ valid: false,
226
+ txSignature: null,
227
+ reason: 'invalid_transaction_base64',
228
+ signer: null,
229
+ };
230
+ }
231
+ if (typeof params.expectedUnsignedTxBase64 === 'string' && params.expectedUnsignedTxBase64.length > 0) {
232
+ try {
233
+ const expectedUnsignedTx = Transaction.from(Buffer.from(params.expectedUnsignedTxBase64, 'base64'));
234
+ const signedMessageBytes = tx.serializeMessage();
235
+ const expectedMessageBytes = expectedUnsignedTx.serializeMessage();
236
+ if (signedMessageBytes.length !== expectedMessageBytes.length
237
+ || !signedMessageBytes.every((value, index) => value === expectedMessageBytes[index])) {
238
+ return {
239
+ valid: false,
240
+ txSignature: firstSignatureBase58(tx),
241
+ reason: 'intent_message_mismatch',
242
+ signer: tx.feePayer?.toBase58() ?? null,
243
+ };
244
+ }
245
+ }
246
+ catch {
247
+ return {
248
+ valid: false,
249
+ txSignature: firstSignatureBase58(tx),
250
+ reason: 'intent_message_mismatch',
251
+ signer: tx.feePayer?.toBase58() ?? null,
252
+ };
253
+ }
254
+ }
255
+ const expectedSigner = params.requiredSigner.trim();
256
+ const signer = tx.feePayer?.toBase58() ?? null;
257
+ if (!signer) {
258
+ return {
259
+ valid: false,
260
+ txSignature: null,
261
+ reason: 'missing_fee_payer',
262
+ signer: null,
263
+ };
264
+ }
265
+ if (signer !== expectedSigner) {
266
+ return {
267
+ valid: false,
268
+ txSignature: firstSignatureBase58(tx),
269
+ reason: 'required_signer_mismatch',
270
+ signer,
271
+ };
272
+ }
273
+ const expectedPubkey = tx.signatures.find((s) => s.publicKey.toBase58() === expectedSigner);
274
+ if (!expectedPubkey || !expectedPubkey.signature) {
275
+ return {
276
+ valid: false,
277
+ txSignature: firstSignatureBase58(tx),
278
+ reason: 'missing_required_signature',
279
+ signer,
280
+ };
281
+ }
282
+ const ok = nacl.sign.detached.verify(tx.serializeMessage(), expectedPubkey.signature, new PublicKey(expectedSigner).toBytes());
283
+ if (!ok) {
284
+ return {
285
+ valid: false,
286
+ txSignature: firstSignatureBase58(tx),
287
+ reason: 'invalid_required_signature',
288
+ signer,
289
+ };
290
+ }
291
+ return {
292
+ valid: true,
293
+ txSignature: firstSignatureBase58(tx),
294
+ reason: null,
295
+ signer,
296
+ };
297
+ }
298
+ function lower(value) {
299
+ return value.toLowerCase();
300
+ }
301
+ export function mapValidationReasonToClaimFailure(reason) {
302
+ if (!reason)
303
+ return null;
304
+ if (reason === 'intent_message_mismatch')
305
+ return 'intent_message_mismatch';
306
+ if (reason === 'required_signer_mismatch')
307
+ return 'required_signer_mismatch';
308
+ return 'unknown';
309
+ }
310
+ export function normalizeClaimSimulationFailure(params) {
311
+ const logs = params.logs ?? [];
312
+ const logsText = lower(logs.join('\n'));
313
+ const errText = lower(params.err == null
314
+ ? ''
315
+ : typeof params.err === 'string'
316
+ ? params.err
317
+ : JSON.stringify(params.err));
318
+ const haystack = `${logsText}\n${errText}`;
319
+ if (haystack.includes('insufficientpoolliquidity')
320
+ || haystack.includes('insufficient funds')
321
+ || haystack.includes('insufficient lamports')) {
322
+ return {
323
+ code: 'simulation_failed_insufficient_funds',
324
+ message: 'Pool does not have sufficient liquidity for this claim.',
325
+ };
326
+ }
327
+ if (haystack.includes('poolnotactive') || haystack.includes('pool is not active')) {
328
+ return {
329
+ code: 'simulation_failed_pool_paused',
330
+ message: 'Pool is paused or not active for claims.',
331
+ };
332
+ }
333
+ if (haystack.includes('membershipnotactive')
334
+ || haystack.includes('membership member mismatch')
335
+ || haystack.includes('membership')) {
336
+ return {
337
+ code: 'simulation_failed_membership_invalid',
338
+ message: 'Membership is not active for this pool.',
339
+ };
340
+ }
341
+ return {
342
+ code: 'simulation_failed_unknown',
343
+ message: 'Claim simulation failed before broadcast.',
344
+ };
345
+ }
346
+ export function normalizeClaimRpcFailure(error) {
347
+ const message = error instanceof Error ? error.message : String(error);
348
+ const normalized = lower(message);
349
+ if (normalized.includes('timeout')
350
+ || normalized.includes('timed out')
351
+ || normalized.includes('blockhash not found')) {
352
+ return {
353
+ code: 'rpc_timeout',
354
+ message: message || 'RPC timeout while submitting claim transaction.',
355
+ };
356
+ }
357
+ return {
358
+ code: 'rpc_rejected',
359
+ message: message || 'RPC rejected claim transaction.',
360
+ };
361
+ }
@@ -0,0 +1,7 @@
1
+ export * from './types.js';
2
+ export * from './utils.js';
3
+ export * from './oracle.js';
4
+ export * from './claims.js';
5
+ export * from './rpc.js';
6
+ export * from './protocol_seeds.js';
7
+ export * from './protocol.js';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from './types.js';
2
+ export * from './utils.js';
3
+ export * from './oracle.js';
4
+ export * from './claims.js';
5
+ export * from './rpc.js';
6
+ export * from './protocol_seeds.js';
7
+ export * from './protocol.js';
@@ -0,0 +1,22 @@
1
+ import type { OracleSigner, OracleKmsSignerAdapter, OutcomeAttestation } from './types.js';
2
+ export type AttestOutcomeParams = {
3
+ userId: string;
4
+ cycleId: string;
5
+ outcomeId: string;
6
+ asOfIso: string;
7
+ payload: Record<string, unknown>;
8
+ signer: OracleSigner;
9
+ submitAttestation?: (attestation: OutcomeAttestation) => Promise<{
10
+ txSignature?: string;
11
+ }>;
12
+ };
13
+ export type AttestOutcomeResult = {
14
+ attestation: OutcomeAttestation;
15
+ txSignature: string | null;
16
+ };
17
+ export declare function createOracleSignerFromEnv(params?: {
18
+ keyIdEnv?: string;
19
+ secretKeyBase58Env?: string;
20
+ }): OracleSigner;
21
+ export declare function createOracleSignerFromKmsAdapter(adapter: OracleKmsSignerAdapter): OracleSigner;
22
+ export declare function attestOutcome(params: AttestOutcomeParams): Promise<AttestOutcomeResult>;
package/dist/oracle.js ADDED
@@ -0,0 +1,82 @@
1
+ import bs58 from 'bs58';
2
+ import nacl from 'tweetnacl';
3
+ import { newId, nowIso, sha256Hex, stableStringify, toIsoString } from './utils.js';
4
+ export function createOracleSignerFromEnv(params) {
5
+ const keyIdEnv = params?.keyIdEnv ?? 'ORACLE_SIGNER_KEY_ID';
6
+ const secretKeyEnv = params?.secretKeyBase58Env ?? 'ORACLE_SIGNER_SECRET_KEY_BASE58';
7
+ const keyId = String(process.env[keyIdEnv] || '').trim();
8
+ const secretKeyBase58 = String(process.env[secretKeyEnv] || '').trim();
9
+ if (!keyId) {
10
+ throw new Error(`Missing ${keyIdEnv}`);
11
+ }
12
+ if (!secretKeyBase58) {
13
+ throw new Error(`Missing ${secretKeyEnv}`);
14
+ }
15
+ const secretKeyBytes = bs58.decode(secretKeyBase58);
16
+ if (secretKeyBytes.length !== nacl.sign.secretKeyLength) {
17
+ throw new Error(`Invalid secret key length for ${secretKeyEnv}`);
18
+ }
19
+ const publicKeyBytes = secretKeyBytes.slice(32);
20
+ return {
21
+ keyId,
22
+ publicKeyBase58: bs58.encode(publicKeyBytes),
23
+ sign: async (message) => {
24
+ return nacl.sign.detached(message, secretKeyBytes);
25
+ },
26
+ };
27
+ }
28
+ export function createOracleSignerFromKmsAdapter(adapter) {
29
+ return {
30
+ keyId: adapter.keyId,
31
+ publicKeyBase58: adapter.publicKeyBase58,
32
+ sign: (message) => adapter.signWithKms(message),
33
+ };
34
+ }
35
+ function canonicalAttestationBody(params) {
36
+ return {
37
+ id: params.id,
38
+ userId: params.userId,
39
+ cycleId: params.cycleId,
40
+ outcomeId: params.outcomeId,
41
+ asOfIso: toIsoString(params.asOfIso),
42
+ issuedAtIso: toIsoString(params.issuedAtIso),
43
+ payload: params.payload,
44
+ verifier: {
45
+ keyId: params.verifierKeyId,
46
+ publicKeyBase58: params.verifierPublicKeyBase58,
47
+ algorithm: 'ed25519',
48
+ },
49
+ };
50
+ }
51
+ export async function attestOutcome(params) {
52
+ const id = newId('att');
53
+ const issuedAtIso = nowIso();
54
+ const body = canonicalAttestationBody({
55
+ id,
56
+ userId: params.userId,
57
+ cycleId: params.cycleId,
58
+ outcomeId: params.outcomeId,
59
+ asOfIso: params.asOfIso,
60
+ issuedAtIso,
61
+ payload: params.payload,
62
+ verifierKeyId: params.signer.keyId,
63
+ verifierPublicKeyBase58: params.signer.publicKeyBase58,
64
+ });
65
+ const canonical = stableStringify(body);
66
+ const message = new TextEncoder().encode(canonical);
67
+ const signature = await params.signer.sign(message);
68
+ const attestation = {
69
+ ...body,
70
+ signatureBase64: Buffer.from(signature).toString('base64'),
71
+ digestHex: sha256Hex(message),
72
+ };
73
+ let txSignature = null;
74
+ if (params.submitAttestation) {
75
+ const result = await params.submitAttestation(attestation);
76
+ txSignature = typeof result?.txSignature === 'string' ? result.txSignature : null;
77
+ }
78
+ return {
79
+ attestation,
80
+ txSignature,
81
+ };
82
+ }
@@ -0,0 +1,9 @@
1
+ import { Connection } from '@solana/web3.js';
2
+ import type { ProtocolClient } from './types.js';
3
+ export declare const PROTOCOL_PROGRAM_ID = "Bn6eixac1QEEVErGBvBjxAd6pgB9e2q4XHvAkinQ5y1B";
4
+ export declare function createProtocolClient(connection: Connection, programIdInput: string): ProtocolClient;
5
+ export declare function derivePoolAddress(params: {
6
+ programId: string;
7
+ authority: string;
8
+ poolId: string;
9
+ }): string;