@pasosdejesus/mpdj 0.1.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.
Files changed (103) hide show
  1. package/LICENSE.md +16 -0
  2. package/SKILL.md +310 -0
  3. package/dist/SKILL.md +310 -0
  4. package/dist/abis/PasosDeJesusCredentials.json +1342 -0
  5. package/dist/blockchain/AddSlearnButton.d.ts +5 -0
  6. package/dist/blockchain/AddSlearnButton.js +58 -0
  7. package/dist/blockchain/SlearnInfo.d.ts +22 -0
  8. package/dist/blockchain/SlearnInfo.js +68 -0
  9. package/dist/blockchain/admin-credentials.d.ts +29 -0
  10. package/dist/blockchain/admin-credentials.js +91 -0
  11. package/dist/blockchain/credential-cache.d.ts +33 -0
  12. package/dist/blockchain/credential-cache.js +129 -0
  13. package/dist/blockchain/credential-images.d.ts +30 -0
  14. package/dist/blockchain/credential-images.js +199 -0
  15. package/dist/blockchain/credentials.d.ts +66 -0
  16. package/dist/blockchain/credentials.js +128 -0
  17. package/dist/blockchain/ecosystem-addresses.d.ts +9 -0
  18. package/dist/blockchain/ecosystem-addresses.js +23 -0
  19. package/dist/blockchain/index.d.ts +6 -0
  20. package/dist/blockchain/index.js +12 -0
  21. package/dist/ia/adJ.md +181 -0
  22. package/dist/ia/alineacion_bayes_general.md +60 -0
  23. package/dist/ia/alineacion_bayes_profecia_mayor20G.md +220 -0
  24. package/dist/ia/alineacion_servicio.md +49 -0
  25. package/dist/ia/alineacion_testimonios.md +268 -0
  26. package/dist/ia/construir_en_adJ_y_celo.md +239 -0
  27. package/dist/ia/entrenamiento-marcos.jsonl +23 -0
  28. package/dist/ia/entrenamiento-no-adivinar.jsonl +15 -0
  29. package/dist/ia/estilo_entrenamiento.jsonl +10 -0
  30. package/dist/ia/indice_entrenamiento.json +43 -0
  31. package/dist/ia/m.jsonl +3 -0
  32. package/dist/ia/manifiesto.md +45 -0
  33. package/dist/ia/piedra_fundamental/README.md +31 -0
  34. package/dist/ia/piedra_fundamental/marcos-01.usfm +68 -0
  35. package/dist/ia/piedra_fundamental/marcos-02.usfm +46 -0
  36. package/dist/ia/piedra_fundamental/marcos-03.usfm +48 -0
  37. package/dist/ia/piedra_fundamental/marcos-04.usfm +59 -0
  38. package/dist/ia/piedra_fundamental/marcos-05.usfm +65 -0
  39. package/dist/ia/piedra_fundamental/marcos-06.usfm +78 -0
  40. package/dist/ia/piedra_fundamental/marcos-07.usfm +53 -0
  41. package/dist/ia/piedra_fundamental/marcos-08.usfm +63 -0
  42. package/dist/ia/piedra_fundamental/marcos-09.usfm +82 -0
  43. package/dist/ia/piedra_fundamental/marcos-10.usfm +88 -0
  44. package/dist/ia/piedra_fundamental/marcos-11.usfm +52 -0
  45. package/dist/ia/piedra_fundamental/marcos-12.usfm +62 -0
  46. package/dist/ia/piedra_fundamental/marcos-13.usfm +48 -0
  47. package/dist/ia/piedra_fundamental/marcos-14.usfm +108 -0
  48. package/dist/ia/piedra_fundamental/marcos-15.usfm +70 -0
  49. package/dist/ia/piedra_fundamental/marcos-16.usfm +32 -0
  50. package/dist/ia/principles.md +35 -0
  51. package/dist/ia/referencia-marcos.md +152 -0
  52. package/dist/ia/verificacion_cristologica.json +128 -0
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.js +159 -0
  55. package/dist/tasks/adJ.d.ts +1 -0
  56. package/dist/tasks/adJ.js +37 -0
  57. package/dist/tasks/ai-verify-core.d.ts +27 -0
  58. package/dist/tasks/ai-verify-core.js +82 -0
  59. package/dist/tasks/ai.d.ts +3 -0
  60. package/dist/tasks/ai.js +96 -0
  61. package/dist/tasks/credentials.d.ts +4 -0
  62. package/dist/tasks/credentials.js +627 -0
  63. package/dist/tasks/principles.d.ts +1 -0
  64. package/dist/tasks/principles.js +37 -0
  65. package/dist/tasks/req.d.ts +9 -0
  66. package/dist/tasks/req.js +1164 -0
  67. package/dist/tasks/skill.d.ts +1 -0
  68. package/dist/tasks/skill.js +34 -0
  69. package/dist/test-utils/blockchain-mocks.d.ts +130 -0
  70. package/dist/test-utils/blockchain-mocks.js +112 -0
  71. package/ia/adJ.md +181 -0
  72. package/ia/alineacion_bayes_general.md +60 -0
  73. package/ia/alineacion_bayes_profecia_mayor20G.md +220 -0
  74. package/ia/alineacion_servicio.md +49 -0
  75. package/ia/alineacion_testimonios.md +268 -0
  76. package/ia/construir_en_adJ_y_celo.md +239 -0
  77. package/ia/entrenamiento-marcos.jsonl +23 -0
  78. package/ia/entrenamiento-no-adivinar.jsonl +15 -0
  79. package/ia/estilo_entrenamiento.jsonl +10 -0
  80. package/ia/indice_entrenamiento.json +43 -0
  81. package/ia/m.jsonl +3 -0
  82. package/ia/manifiesto.md +45 -0
  83. package/ia/piedra_fundamental/README.md +31 -0
  84. package/ia/piedra_fundamental/marcos-01.usfm +68 -0
  85. package/ia/piedra_fundamental/marcos-02.usfm +46 -0
  86. package/ia/piedra_fundamental/marcos-03.usfm +48 -0
  87. package/ia/piedra_fundamental/marcos-04.usfm +59 -0
  88. package/ia/piedra_fundamental/marcos-05.usfm +65 -0
  89. package/ia/piedra_fundamental/marcos-06.usfm +78 -0
  90. package/ia/piedra_fundamental/marcos-07.usfm +53 -0
  91. package/ia/piedra_fundamental/marcos-08.usfm +63 -0
  92. package/ia/piedra_fundamental/marcos-09.usfm +82 -0
  93. package/ia/piedra_fundamental/marcos-10.usfm +88 -0
  94. package/ia/piedra_fundamental/marcos-11.usfm +52 -0
  95. package/ia/piedra_fundamental/marcos-12.usfm +62 -0
  96. package/ia/piedra_fundamental/marcos-13.usfm +48 -0
  97. package/ia/piedra_fundamental/marcos-14.usfm +108 -0
  98. package/ia/piedra_fundamental/marcos-15.usfm +70 -0
  99. package/ia/piedra_fundamental/marcos-16.usfm +32 -0
  100. package/ia/principles.md +35 -0
  101. package/ia/referencia-marcos.md +152 -0
  102. package/ia/verificacion_cristologica.json +128 -0
  103. package/package.json +73 -0
@@ -0,0 +1,199 @@
1
+ // src/blockchain/credential-images.ts
2
+ // SVG composition and PNG generation for credential badges.
3
+ //
4
+ // composeCredentialImage reads user SVG + site logos from publicDir,
5
+ // composes layers (border, icon, logo, lock, star), writes SVG + renders PNG.
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { execSync } from 'child_process';
9
+ import { fileURLToPath } from 'url';
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ // ==================== SVG ASSETS (inline defaults) ====================
12
+ const LOCK_SVG_FALLBACK = (color) => `<g transform="translate(452,4) scale(1.8)">
13
+ <rect x="0" y="16" width="48" height="32" rx="4" fill="${color}22" stroke="${color}" stroke-width="2"/>
14
+ <path d="M14 16V10a10 10 0 0 1 20 0v6" fill="none" stroke="${color}" stroke-width="2"/>
15
+ </g>`;
16
+ const STAR_SVG = `<g transform="translate(429,220) scale(1.5)">
17
+ <polygon points="24,0 30,18 48,18 33,30 39,48 24,36 9,48 15,30 0,18 18,18"
18
+ fill="#fbbf24" stroke="#d97706" stroke-width="1"/>
19
+ </g>`;
20
+ const SITE_LOGOS = {
21
+ 'learn.tg': `<rect x="0" y="0" width="64" height="64" rx="12" fill="#7c3aed"/>
22
+ <text x="32" y="42" text-anchor="middle" font-size="28" fill="white" font-family="sans-serif" font-weight="bold">LT</text>`,
23
+ 'sivel.xyz': `<rect x="0" y="0" width="64" height="64" rx="12" fill="#0891b2"/>
24
+ <text x="32" y="42" text-anchor="middle" font-size="24" fill="white" font-family="sans-serif" font-weight="bold">S3</text>`,
25
+ 'stable-sl.pdJ.app': `<rect x="0" y="0" width="64" height="64" rx="12" fill="#059669"/>
26
+ <text x="32" y="42" text-anchor="middle" font-size="24" fill="white" font-family="sans-serif" font-weight="bold">SL</text>`,
27
+ };
28
+ export const TYPE_COLORS = {
29
+ course_completion: '#3b82f6',
30
+ role: '#f59e0b',
31
+ achievement: '#10b981',
32
+ nft: '#8b5cf6',
33
+ };
34
+ export const FONT_FAMILY = "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
35
+ const SHORT_NAMES = {
36
+ 'learn.tg': 'learntg',
37
+ 'sivel.xyz': 'sivel',
38
+ 'stable-sl.pdJ.app': 'stablesl',
39
+ };
40
+ // ==================== SVG HELPERS ====================
41
+ /**
42
+ * Escape text for safe inclusion in XML/SVG attributes and text content.
43
+ * Handles: & < > " '
44
+ */
45
+ function xmlEscape(text) {
46
+ return text
47
+ .replace(/&/g, '&amp;')
48
+ .replace(/</g, '&lt;')
49
+ .replace(/>/g, '&gt;')
50
+ .replace(/"/g, '&quot;')
51
+ .replace(/'/g, '&apos;');
52
+ }
53
+ /**
54
+ * Extract inner content of user's SVG (between <svg> tags).
55
+ */
56
+ export function extractSvgInner(svgText) {
57
+ const match = svgText.match(/<svg[^>]*>([\s\S]*)<\/svg>/i);
58
+ if (!match)
59
+ throw new Error('Invalid SVG: no <svg> root found');
60
+ return match[1].trim();
61
+ }
62
+ /**
63
+ * Validate user's SVG file before composition.
64
+ */
65
+ export function validateSvg(svgText) {
66
+ if (!/<svg[\s\S]*viewBox\s*=\s*["']0 0 512 512["']/i.test(svgText)) {
67
+ throw new Error('SVG must have viewBox="0 0 512 512"');
68
+ }
69
+ const forbidden = [
70
+ /<script/i,
71
+ /href\s*=\s*["']https?:\/\//i,
72
+ /xlink:href\s*=\s*["']https?:\/\//i,
73
+ /data:image\/svg\+xml/i,
74
+ /foreignObject/i,
75
+ ];
76
+ for (const pattern of forbidden) {
77
+ if (pattern.test(svgText)) {
78
+ throw new Error(`SVG contains forbidden content: ${pattern.source}`);
79
+ }
80
+ }
81
+ const inner = extractSvgInner(svgText);
82
+ if (inner.length < 50 || inner.length > 50000) {
83
+ throw new Error(`SVG content size out of range (${inner.length} chars)`);
84
+ }
85
+ }
86
+ // ==================== LOCK / PDJ / LOGO ====================
87
+ let lockSvgCache = null;
88
+ let pdjSvgCache = null;
89
+ function getLockSvg(color, publicDir) {
90
+ if (!lockSvgCache) {
91
+ const lockSbtPath = path.join(publicDir, 'img', 'lock-sbt.svg');
92
+ if (fs.existsSync(lockSbtPath)) {
93
+ lockSvgCache = extractSvgInner(fs.readFileSync(lockSbtPath, 'utf-8'));
94
+ }
95
+ }
96
+ if (lockSvgCache) {
97
+ const colored = lockSvgCache.replace(/#10b981/gi, color);
98
+ return `<g transform="translate(405,4) scale(2.0)">${colored}</g>`;
99
+ }
100
+ return LOCK_SVG_FALLBACK(color);
101
+ }
102
+ function getPdJLogo(color, publicDir) {
103
+ if (!pdjSvgCache) {
104
+ // 1. Try project's public/img/
105
+ const projectPath = path.join(publicDir, 'img', 'PdJ-badge.svg');
106
+ if (fs.existsSync(projectPath)) {
107
+ pdjSvgCache = extractSvgInner(fs.readFileSync(projectPath, 'utf-8'));
108
+ }
109
+ else {
110
+ // 2. Fallback to @pasosdejesus/m/blockchain/img/
111
+ const mPath = path.join(__dirname, 'img', 'PdJ-badge.svg');
112
+ if (fs.existsSync(mPath)) {
113
+ pdjSvgCache = extractSvgInner(fs.readFileSync(mPath, 'utf-8'));
114
+ }
115
+ }
116
+ }
117
+ if (pdjSvgCache) {
118
+ const colored = pdjSvgCache.replace(/#currentColor/gi, color);
119
+ return `<g transform="translate(11,11) scale(3.5)" opacity="0.85">${colored}</g>`;
120
+ }
121
+ return '';
122
+ }
123
+ function getLogoSvg(svgPath) {
124
+ const svg = fs.readFileSync(svgPath, 'utf-8');
125
+ let w = 256, h = 256;
126
+ const vb = svg.match(/viewBox\s*=\s*["']0\s+0\s+([\d.]+)\s+([\d.]+)/i);
127
+ if (vb) {
128
+ w = parseFloat(vb[1]);
129
+ h = parseFloat(vb[2]);
130
+ }
131
+ const maxDim = Math.max(w, h);
132
+ const scale = maxDim > 0 ? (96 / maxDim).toFixed(4) : '0.375';
133
+ return `<g transform="scale(${scale})">${extractSvgInner(svg)}</g>`;
134
+ }
135
+ function getSiteLogo(site, publicDir) {
136
+ const short = SHORT_NAMES[site] || site;
137
+ // 1. Try project's public/img/ directory
138
+ const projectPath = path.join(publicDir, 'img', `logo-${short}.svg`);
139
+ if (fs.existsSync(projectPath))
140
+ return getLogoSvg(projectPath);
141
+ // 2. Fallback to @pasosdejesus/m/blockchain/img/
142
+ const mPath = path.join(__dirname, 'img', `logo-${short}.svg`);
143
+ if (fs.existsSync(mPath))
144
+ return getLogoSvg(mPath);
145
+ // 3. Last resort: inline default
146
+ return SITE_LOGOS[site] || '';
147
+ }
148
+ /**
149
+ * Compose credential image by layering user's SVG icon with
150
+ * programmatic elements (border, site logo, lock/star overlays).
151
+ */
152
+ export async function composeCredentialImage(params) {
153
+ const { userSvgPath, tokenId, site, siteName, type, isSoulbound, isPremium, displayName, publicDir, pngCommand } = params;
154
+ // 1. Read and validate user SVG
155
+ if (!fs.existsSync(userSvgPath)) {
156
+ throw new Error(`SVG file not found: ${userSvgPath}`);
157
+ }
158
+ const rawSvg = fs.readFileSync(userSvgPath, 'utf-8');
159
+ validateSvg(rawSvg);
160
+ const userInner = extractSvgInner(rawSvg);
161
+ const color = TYPE_COLORS[type] || '#64748b';
162
+ const siteLogo = getSiteLogo(site, publicDir);
163
+ if (!siteLogo)
164
+ throw new Error(`Unknown site: ${site}`);
165
+ const generatedDir = path.join(publicDir, 'img', 'credential', 'generated');
166
+ const pngDir = path.join(publicDir, 'img', 'credential');
167
+ fs.mkdirSync(generatedDir, { recursive: true });
168
+ fs.mkdirSync(pngDir, { recursive: true });
169
+ // 2. Compose layers
170
+ const layers = [
171
+ `<rect x="0" y="0" width="512" height="512" rx="28" fill="#ffffff"/>`,
172
+ `<rect x="0" y="0" width="512" height="512" rx="28" fill="none" stroke="${color}" stroke-width="14"/>`,
173
+ getPdJLogo(color, publicDir),
174
+ `<g transform="translate(11,11) scale(0.95)">${userInner}</g>`,
175
+ `<rect x="0" y="464" width="512" height="48" rx="0" fill="${color}" opacity="0.15"/>`,
176
+ `<rect x="0" y="464" width="512" height="3" rx="0" fill="${color}" opacity="0.6"/>`,
177
+ `<g transform="translate(405,364)">${siteLogo}</g>`,
178
+ `<text x="501" y="498" font-family="${FONT_FAMILY}" font-weight="700" font-size="32" fill="${color}" text-anchor="end">${xmlEscape(siteName)}</text>`,
179
+ `<text x="8" y="498" font-family="${FONT_FAMILY}" font-weight="700" font-size="${displayName.length > 20 ? 20 : 36}" fill="${color}">${xmlEscape(displayName)}</text>`,
180
+ ];
181
+ if (isSoulbound)
182
+ layers.push(getLockSvg(color, publicDir));
183
+ if (isPremium)
184
+ layers.push(STAR_SVG);
185
+ const composedSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
186
+ ${layers.join('\n')}
187
+ </svg>
188
+ `;
189
+ // 3. Write composed SVG
190
+ const svgPath = path.join(generatedDir, `${tokenId}.svg`);
191
+ fs.writeFileSync(svgPath, composedSvg, 'utf-8');
192
+ // 4. Convert to PNG
193
+ const pngPath = path.join(pngDir, `${tokenId}.png`);
194
+ execSync(`${pngCommand} "${pngPath}" "${svgPath}"`, { stdio: 'pipe', timeout: 10000 });
195
+ if (!fs.existsSync(pngPath)) {
196
+ throw new Error('PNG conversion failed — check pngCommand');
197
+ }
198
+ return { svgPath: `img/credential/generated/${tokenId}.svg`, pngPath: `img/credential/${tokenId}.png` };
199
+ }
@@ -0,0 +1,66 @@
1
+ import type { PublicClient, WalletClient, Chain, Account } from 'viem';
2
+ /**
3
+ * Get tokenId from courseId (reads from contract).
4
+ */
5
+ export declare function getTokenIdByCourseId(publicClient: PublicClient, contractAddress: `0x${string}`, courseId: number): Promise<number>;
6
+ /**
7
+ * Check if an address already holds a credential (reads balanceOf from contract).
8
+ * For SBTs, balance > 0 means they have it (always amount=1).
9
+ * For NFTs, balance > 0 means they hold at least one.
10
+ */
11
+ export declare function hasCredentialOnChain(publicClient: PublicClient, contractAddress: `0x${string}`, account: `0x${string}`, tokenId: number): Promise<boolean>;
12
+ /**
13
+ * Revoke (burn) a credential from a user.
14
+ */
15
+ export declare function revokeCredential(walletClient: WalletClient, contractAddress: `0x${string}`, userAddress: `0x${string}`, tokenId: number, amount: number): Promise<string>;
16
+ /**
17
+ * Generic mint with Celo L2 nonce retry. Handles replacement/underpriced errors.
18
+ *
19
+ * Celo's OP Stack sequencer rejects txs that try to replace pending ones
20
+ * ("replacement transaction underpriced"). This function handles that by:
21
+ *
22
+ * 1. Using blockTag: 'pending' to skip stuck nonces
23
+ * 2. Passing explicit gasPrice from the network
24
+ * 3. Creating fresh clients each attempt (no http() caching)
25
+ * 4. Retrying up to 5 times with 2s delays
26
+ *
27
+ * @param functionName — 'mintCredential' or 'mintCourseCompletion'
28
+ * @param args — the writeContract args array
29
+ * @returns tx hash on success
30
+ * @throws after all retries exhausted or on non-retryable errors
31
+ */
32
+ export declare function mintWithRetry(params: {
33
+ account: Account;
34
+ rpcUrl: string;
35
+ chain: Chain;
36
+ contractAddress: `0x${string}`;
37
+ functionName: string;
38
+ args: any[];
39
+ maxAttempts?: number;
40
+ }): Promise<string>;
41
+ /**
42
+ * Mint a credential by tokenId with Celo L2 nonce handling.
43
+ * Calls mintCredential(address, tokenId, 1).
44
+ */
45
+ export declare function mintCredentialWithRetry(params: {
46
+ account: Account;
47
+ rpcUrl: string;
48
+ chain: Chain;
49
+ contractAddress: `0x${string}`;
50
+ userAddress: `0x${string}`;
51
+ tokenId: number;
52
+ maxAttempts?: number;
53
+ }): Promise<string>;
54
+ /**
55
+ * Mint a course completion SBT by courseId with Celo L2 nonce handling.
56
+ * Calls mintCourseCompletion(address, courseId).
57
+ */
58
+ export declare function mintCourseWithRetry(params: {
59
+ account: Account;
60
+ rpcUrl: string;
61
+ chain: Chain;
62
+ contractAddress: `0x${string}`;
63
+ userAddress: `0x${string}`;
64
+ courseId: number;
65
+ maxAttempts?: number;
66
+ }): Promise<string>;
@@ -0,0 +1,128 @@
1
+ // src/blockchain/credentials.ts
2
+ // Pure functions for PasosDeJesusCredentials contract interactions.
3
+ //
4
+ // All functions receive WalletClient/PublicClient and contractAddress
5
+ // as parameters. The caller wires its own keys, networks, and address.
6
+ import { createPublicClient, createWalletClient, http } from 'viem';
7
+ import pasosDeJesusCredentialsAbi from '../abis/PasosDeJesusCredentials.json' with { type: 'json' };
8
+ /**
9
+ * Get tokenId from courseId (reads from contract).
10
+ */
11
+ export async function getTokenIdByCourseId(publicClient, contractAddress, courseId) {
12
+ const tokenId = await publicClient.readContract({
13
+ address: contractAddress,
14
+ abi: pasosDeJesusCredentialsAbi,
15
+ functionName: 'courseIdToTokenId',
16
+ args: [BigInt(courseId)],
17
+ });
18
+ return Number(tokenId);
19
+ }
20
+ /**
21
+ * Check if an address already holds a credential (reads balanceOf from contract).
22
+ * For SBTs, balance > 0 means they have it (always amount=1).
23
+ * For NFTs, balance > 0 means they hold at least one.
24
+ */
25
+ export async function hasCredentialOnChain(publicClient, contractAddress, account, tokenId) {
26
+ try {
27
+ const balance = await publicClient.readContract({
28
+ address: contractAddress,
29
+ abi: pasosDeJesusCredentialsAbi,
30
+ functionName: 'balanceOf',
31
+ args: [account, BigInt(tokenId)],
32
+ });
33
+ return balance > 0n;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ /**
40
+ * Revoke (burn) a credential from a user.
41
+ */
42
+ export async function revokeCredential(walletClient, contractAddress, userAddress, tokenId, amount) {
43
+ return await walletClient.writeContract({
44
+ address: contractAddress,
45
+ abi: pasosDeJesusCredentialsAbi,
46
+ functionName: 'revokeCredential',
47
+ args: [userAddress, BigInt(tokenId), BigInt(amount)],
48
+ chain: walletClient.chain,
49
+ account: walletClient.account,
50
+ });
51
+ }
52
+ /**
53
+ * Generic mint with Celo L2 nonce retry. Handles replacement/underpriced errors.
54
+ *
55
+ * Celo's OP Stack sequencer rejects txs that try to replace pending ones
56
+ * ("replacement transaction underpriced"). This function handles that by:
57
+ *
58
+ * 1. Using blockTag: 'pending' to skip stuck nonces
59
+ * 2. Passing explicit gasPrice from the network
60
+ * 3. Creating fresh clients each attempt (no http() caching)
61
+ * 4. Retrying up to 5 times with 2s delays
62
+ *
63
+ * @param functionName — 'mintCredential' or 'mintCourseCompletion'
64
+ * @param args — the writeContract args array
65
+ * @returns tx hash on success
66
+ * @throws after all retries exhausted or on non-retryable errors
67
+ */
68
+ export async function mintWithRetry(params) {
69
+ const { account, rpcUrl, chain, contractAddress, functionName, args, maxAttempts = 5 } = params;
70
+ let lastError;
71
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
72
+ // Fresh clients each attempt — http() caches nonce, can't reuse
73
+ const pc = createPublicClient({ chain, transport: http(rpcUrl) });
74
+ const wc = createWalletClient({ chain, transport: http(rpcUrl), account });
75
+ const nonce = await pc.getTransactionCount({
76
+ address: account.address,
77
+ blockTag: attempt <= 3 ? 'pending' : 'latest',
78
+ });
79
+ const gasPrice = await pc.getGasPrice();
80
+ try {
81
+ return await wc.writeContract({
82
+ address: contractAddress,
83
+ abi: pasosDeJesusCredentialsAbi,
84
+ functionName,
85
+ args,
86
+ chain,
87
+ account,
88
+ nonce,
89
+ gasPrice,
90
+ });
91
+ }
92
+ catch (err) {
93
+ lastError = err;
94
+ const msg = err.message || '';
95
+ if (msg.includes('replacement') || msg.includes('underpriced') ||
96
+ msg.includes('Missing or invalid')) {
97
+ await new Promise(r => setTimeout(r, 2000));
98
+ continue;
99
+ }
100
+ throw err;
101
+ }
102
+ }
103
+ throw lastError;
104
+ }
105
+ /**
106
+ * Mint a credential by tokenId with Celo L2 nonce handling.
107
+ * Calls mintCredential(address, tokenId, 1).
108
+ */
109
+ export async function mintCredentialWithRetry(params) {
110
+ const { userAddress, tokenId, ...rest } = params;
111
+ return mintWithRetry({
112
+ ...rest,
113
+ functionName: 'mintCredential',
114
+ args: [userAddress, BigInt(tokenId), BigInt(1)],
115
+ });
116
+ }
117
+ /**
118
+ * Mint a course completion SBT by courseId with Celo L2 nonce handling.
119
+ * Calls mintCourseCompletion(address, courseId).
120
+ */
121
+ export async function mintCourseWithRetry(params) {
122
+ const { userAddress, courseId, ...rest } = params;
123
+ return mintWithRetry({
124
+ ...rest,
125
+ functionName: 'mintCourseCompletion',
126
+ args: [userAddress, BigInt(courseId)],
127
+ });
128
+ }
@@ -0,0 +1,9 @@
1
+ export type CeloNetwork = 'celo' | 'celoSepolia';
2
+ export declare function resolveNetwork(): CeloNetwork;
3
+ export declare const SLEARN_ADDRESSES: Record<CeloNetwork, `0x${string}`>;
4
+ export declare const USDT_ADDRESSES: Record<CeloNetwork, `0x${string}`>;
5
+ export declare const SIGNERS: {
6
+ readonly learnTg: `0x${string}`;
7
+ readonly sivel3: `0x${string}`;
8
+ readonly stableSl: `0x${string}`;
9
+ };
@@ -0,0 +1,23 @@
1
+ // src/blockchain/ecosystem-addresses.ts
2
+ // Shared addresses for contracts and EIP-191 signers across the Pasos de Jesús ecosystem.
3
+ // Used by learn.tg, sivel.xyz, stable-sl.pdJ.app.
4
+ export function resolveNetwork() {
5
+ return (process.env.NEXT_PUBLIC_NETWORK === 'celo' ? 'celo' : 'celoSepolia');
6
+ }
7
+ // ── Contracts ────────────────────────────────────────────
8
+ export const SLEARN_ADDRESSES = {
9
+ celo: '0x27fd41Bea85C39254f2B12789eB37a1543152CC1',
10
+ celoSepolia: process.env.NEXT_PUBLIC_SLEARN_ADDRESS,
11
+ };
12
+ export const USDT_ADDRESSES = {
13
+ celo: '0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e',
14
+ celoSepolia: process.env.NEXT_PUBLIC_USDT_ADDRESS,
15
+ };
16
+ // ── EIP-191 Signers ──────────────────────────────────────
17
+ // Addresses used by each project to sign cross-project messages.
18
+ // These are not contracts but EOA wallets whose signatures are verified via EIP-191.
19
+ export const SIGNERS = {
20
+ learnTg: '0x358643bAdcC77Cccb28A319abD439438A57339A7',
21
+ sivel3: '0x9F636E5653b649b44c9375E6E103600AE55aF979',
22
+ stableSl: '0x6B3BC1b55b28380193733a2FD27f2639d92f14be',
23
+ };
@@ -0,0 +1,6 @@
1
+ export * from './credentials.js';
2
+ export * from './credential-cache.js';
3
+ export { verifyCredentialMetadataSchema } from './credential-cache.js';
4
+ export * from './ecosystem-addresses.js';
5
+ export { SlearnInfo } from './SlearnInfo.js';
6
+ export { AddSlearnButton } from './AddSlearnButton.js';
@@ -0,0 +1,12 @@
1
+ // src/blockchain/index.ts
2
+ // Server-only modules (credential-images, admin-credentials, deployments)
3
+ // are NOT exported from the barrel to avoid bundling Node.js 'fs' into client builds.
4
+ // Import them directly: import { ... } from '@pasosdejesus/m/blockchain/credential-images'
5
+ // import { ... } from '@pasosdejesus/m/blockchain/admin-credentials'
6
+ // import { ... } from '@pasosdejesus/m/blockchain/deployments'
7
+ export * from './credentials.js';
8
+ export * from './credential-cache.js';
9
+ export { verifyCredentialMetadataSchema } from './credential-cache.js';
10
+ export * from './ecosystem-addresses.js';
11
+ export { SlearnInfo } from './SlearnInfo.js';
12
+ export { AddSlearnButton } from './AddSlearnButton.js';
package/dist/ia/adJ.md ADDED
@@ -0,0 +1,181 @@
1
+ # OpenBSD/adJ Tips for AI Agents
2
+
3
+ > *"By wisdom a house is built, and by understanding it is established"* (Proverbs 24:3)
4
+
5
+ This document catalogs platform-specific quirks that cause recurring errors
6
+ when AI agents assume GNU/Linux conventions. Add new entries as they surface.
7
+
8
+ ---
9
+
10
+ ## Shell & CLI
11
+
12
+ ### grep — BSD grep, not GNU
13
+
14
+ ```sh
15
+ # ❌ GNU-only flags
16
+ grep -P 'pattern' # -P (Perl regex) does not exist
17
+ grep -oP 'pattern' # same
18
+ grep --exclude-dir=... # use --exclude instead
19
+
20
+ # ✅ BSD-compatible
21
+ grep -E 'pattern' # extended regex
22
+ grep -c 'a' file # OK for single file
23
+ # Multi-file: prefer separate invocations or handle exit code 1
24
+ grep -c 'a' file1 && grep -c 'a' file2
25
+ ```
26
+
27
+ ### sed — BSD sed requires backup extension
28
+
29
+ ```sh
30
+ # ❌ GNU
31
+ sed -i 's/foo/bar/' file
32
+
33
+ # ✅ BSD — empty string for no backup
34
+ sed -i '' 's/foo/bar/' file
35
+ ```
36
+
37
+ ### bash — not at /bin/bash
38
+
39
+ ```sh
40
+ # ❌
41
+ #!/bin/bash
42
+
43
+ # ✅
44
+ #!/usr/bin/env bash
45
+ # Or: /usr/local/bin/bash
46
+ ```
47
+
48
+ ### Shell defaults
49
+
50
+ OpenBSD's default shell is `ksh`, not `bash`. Scripts without shebangs run
51
+ under `ksh`. Use explicit `#!/usr/bin/env bash` for bash features.
52
+
53
+ ### Pipe exit codes
54
+
55
+ BSD utilities can produce exit code 1 in pipelines where GNU returns 0.
56
+ Example: `grep -c 'x' file1 file2 | head` — exit 1 if any file has 0 matches.
57
+
58
+ ```sh
59
+ # ❌ May silently fail
60
+ grep -c 'pattern' f1 f2 | head
61
+
62
+ # ✅ Separate invocations or ignore exit code
63
+ grep -c 'pattern' f1; grep -c 'pattern' f2
64
+ ```
65
+
66
+ ---
67
+
68
+ ## System Administration
69
+
70
+ ### Privilege escalation: doas, not sudo
71
+
72
+ ```sh
73
+ # ❌
74
+ sudo command
75
+
76
+ # ✅
77
+ doas command
78
+ ```
79
+
80
+ ### Package management: pkg_add, not apt/yum
81
+
82
+ ```sh
83
+ # ❌
84
+ apt install pkg
85
+ yum install pkg
86
+
87
+ # ✅
88
+ doas pkg_add pkgname
89
+ ```
90
+
91
+ ### Service management: rcctl, not systemctl
92
+
93
+ ```sh
94
+ # ❌
95
+ systemctl start postgresql
96
+
97
+ # ✅
98
+ doas rcctl start postgresql
99
+ ```
100
+
101
+ ### /proc — minimal on OpenBSD
102
+
103
+ `/proc` exists but only exposes PID and basic info. No `/proc/cpuinfo`,
104
+ `/proc/meminfo`, etc. Use `sysctl` instead:
105
+
106
+ ```sh
107
+ # ❌
108
+ cat /proc/cpuinfo
109
+
110
+ # ✅
111
+ sysctl hw.model hw.ncpu
112
+ ```
113
+
114
+ ---
115
+
116
+ ## PostgreSQL
117
+
118
+ ### System user: _postgresql
119
+
120
+ ```sh
121
+ # ❌ — assumes Linux
122
+ PGUSER=postgres
123
+
124
+ # ✅ — OpenBSD
125
+ PGUSER=_postgresql
126
+ ```
127
+
128
+ ### Unix sockets by default
129
+
130
+ PostgreSQL on OpenBSD uses Unix sockets by default. Omit `PGHOST` or set it
131
+ to the socket directory:
132
+
133
+ ```sh
134
+ PGHOST=/var/www/var/run/postgresql
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Development
140
+
141
+ ### Package manager: pnpm
142
+
143
+ `pnpm` has better performance than `npm`/`yarn` on OpenBSD. Use it consistently.
144
+
145
+ ```sh
146
+ pnpm install
147
+ pnpm run build
148
+ pnpm test
149
+ ```
150
+
151
+ ### Native binaries (napi-rs)
152
+
153
+ Many npm packages with native Rust binaries (via napi-rs) lack prebuilt
154
+ OpenBSD binaries. Fallbacks:
155
+ - `@nomicfoundation/edr` → `@pasosdejesus/edr` (@ethereumjs/vm)
156
+ - General pattern: look for `@pasosdejesus/*` fallback packages
157
+
158
+ ### make — BSD make, not GNU
159
+
160
+ ```sh
161
+ # ❌ GNU-specific
162
+ make -j$(nproc)
163
+
164
+ # ✅ BSD
165
+ make -j$(sysctl -n hw.ncpuonline)
166
+ ```
167
+
168
+ ---
169
+
170
+ ## File Paths
171
+
172
+ ### No /bin/bash
173
+
174
+ Use `/usr/bin/env bash` or `/usr/local/bin/bash`.
175
+
176
+ ### Ports tree
177
+
178
+ OpenBSD packages installed via `pkg_add` go to `/usr/local/`. This means:
179
+ - Config in `/etc/` for base, `/usr/local/etc/` for packages
180
+ - Binaries in `/usr/local/bin/` for packages
181
+ - Libraries in `/usr/local/lib/` for packages