Skip to content

Commit 6431ea0

Browse files
committed
Add Node.js and React templates for credits.aleo functions
1 parent 84d4205 commit 6431ea0

File tree

29 files changed

+1973
-2
lines changed

29 files changed

+1973
-2
lines changed

.github/workflows/sdk.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ jobs:
102102
- extension
103103
- nextjs-ts
104104
- offline-public-transaction-ts
105+
- react-credits-aleo-functions-ts
105106
- react-leo
106107
#- react-managed-worker
107108
- react-ts
@@ -125,8 +126,8 @@ jobs:
125126
matrix:
126127
template:
127128
- node
129+
- node-credits-aleo-functions-ts
128130
- node-ts
129-
- private-transaction-ts
130131
steps:
131132
- uses: actions/checkout@v4
132133
- uses: ./.github/actions/setup-yarn

create-leo-app/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"template-build-and-execute-authorization-ts",
1616
"template-node",
1717
"template-offline-public-transaction-ts",
18-
"template-private-transaction-ts",
18+
"template-node-credits-aleo-functions-ts",
19+
"template-react-credits-aleo-functions-ts",
1920
"template-react-leo",
2021
"template-react-managed-worker",
2122
"template-react-ts",
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Node.js Credits.aleo Functions Example
2+
3+
This example builds execution transactions for all 6 credits.aleo functions using
4+
the ProgramManager: `transfer_public`, `transfer_public_to_private`,
5+
`transfer_private`, `transfer_private_to_public`, `join`, and `split`.
6+
7+
Run all 6 functions:
8+
9+
```bash
10+
yarn start
11+
# or
12+
npm run start
13+
```
14+
15+
Run a single function:
16+
17+
```bash
18+
yarn start transfer_public
19+
# or
20+
npm run start -- transfer_public
21+
```
22+
23+
Available functions: `transfer_public`, `transfer_public_to_private`,
24+
`transfer_private`, `transfer_private_to_public`, `join`, `split`.
25+
26+
Requires network connectivity to fetch inclusion proofs from the explorer API.
27+
Recommend Node.js 20+ for best performance.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "template-node-credits-aleo-functions-ts",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"build": "rimraf dist/js && rollup --config",
8+
"start": "npm run build && node dist/index.js"
9+
},
10+
"dependencies": {
11+
"@provablehq/sdk": "^0.9.15"
12+
},
13+
"devDependencies": {
14+
"rimraf": "^6.0.1",
15+
"rollup": "^4.32.0",
16+
"rollup-plugin-typescript2": "^0.36.0",
17+
"typescript": "^5.7.3"
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import typescript from "rollup-plugin-typescript2";
2+
3+
export default {
4+
input: {
5+
index: "./src/index.ts",
6+
},
7+
output: {
8+
dir: `dist`,
9+
format: "es",
10+
sourcemap: true,
11+
},
12+
external: ["@provablehq/sdk"],
13+
plugins: [
14+
typescript({
15+
tsconfig: "tsconfig.json",
16+
clean: true,
17+
}),
18+
],
19+
};
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import {
2+
Account,
3+
initThreadPool,
4+
ProgramManager,
5+
AleoKeyProvider,
6+
AleoKeyProviderParams,
7+
TransactionObject,
8+
} from "@provablehq/sdk/testnet.js";
9+
import { CREDITS_PROGRAM_KEYS } from "@provablehq/sdk/testnet.js";
10+
11+
// Initialize the threadpool to speed up proving.
12+
await initThreadPool();
13+
14+
/**
15+
* A class that wraps the credits.aleo program functionality.
16+
*
17+
* Provides methods for all credits.aleo transfer functions:
18+
* - transferPublic: Public to public transfer
19+
* - transferPublicToPrivate: Public to private transfer
20+
* - transferPrivate: Private to private transfer
21+
* - transferPrivateToPublic: Private to public transfer
22+
* - join: Combine two private records
23+
* - split: Split a private record into two
24+
*
25+
* @example
26+
* const account = Account.fromCiphertext(ciphertext, password);
27+
* const credits = new Credits(account);
28+
*
29+
* await credits.transferPublic(recipient, 50000);
30+
* await credits.transferPrivate(record, recipient, 100000);
31+
*/
32+
class Credits {
33+
private programManager: ProgramManager;
34+
private keyProvider: AleoKeyProvider;
35+
private creditsProgram: string;
36+
private _account: Account;
37+
38+
/**
39+
* Create a new Credits instance.
40+
*
41+
* @param account - The Aleo account to use for transactions
42+
* @param apiUrl - The API endpoint (defaults to https://api.provable.com/v2)
43+
*/
44+
constructor(account: Account, apiUrl: string = "https://api.provable.com/v2") {
45+
this._account = account;
46+
this.programManager = new ProgramManager(apiUrl);
47+
this.programManager.setAccount(account);
48+
49+
this.keyProvider = new AleoKeyProvider();
50+
this.keyProvider.useCache(true);
51+
this.programManager.setKeyProvider(this.keyProvider);
52+
53+
this.creditsProgram = this.programManager.creditsProgram().toString();
54+
}
55+
56+
/**
57+
* Get the account associated with this Credits instance.
58+
*/
59+
get account(): Account {
60+
return this._account;
61+
}
62+
63+
/**
64+
* Execute a credits.aleo function.
65+
*/
66+
private async execute(functionName: string, inputs: string[]): Promise<string[]> {
67+
const start = Date.now();
68+
console.log(`Starting ${functionName} execution`);
69+
70+
const keyParams = new AleoKeyProviderParams({
71+
cacheKey: CREDITS_PROGRAM_KEYS.getKey(functionName).locator,
72+
});
73+
74+
const tx = await this.programManager.buildExecutionTransaction({
75+
programName: "credits.aleo",
76+
functionName,
77+
inputs,
78+
priorityFee: 0,
79+
privateFee: false,
80+
keySearchParams: keyParams,
81+
program: this.creditsProgram,
82+
});
83+
84+
const summary = tx.summary(true) as TransactionObject;
85+
const outputs = this.extractOutputs(summary);
86+
87+
console.log("Transaction ID:", tx.id());
88+
console.log("Outputs:", outputs);
89+
console.log(`${functionName} finished in ${Date.now() - start}ms`);
90+
91+
return outputs;
92+
}
93+
94+
/**
95+
* Extract outputs from a built transaction using its summary.
96+
* Gets outputs from the execution transition (excludes fee transition).
97+
*/
98+
private extractOutputs(summary: TransactionObject): string[] {
99+
if (!summary.execution) return [];
100+
101+
const execTransition = summary.execution.transitions.find(
102+
(t) => t.function !== "fee_public" && t.function !== "fee_private",
103+
);
104+
if (!execTransition?.outputs) return [];
105+
106+
return execTransition.outputs.map((o) => {
107+
if (o.type === "record" && o.value) {
108+
return String(o.value);
109+
} else if (o.type === "future") {
110+
const args = Array.isArray(o.arguments)
111+
? o.arguments.map(String).join(", ")
112+
: "";
113+
return `Future { program: ${o.program}, function: ${o.function}, args: [${args}] }`;
114+
}
115+
return o.value ? String(o.value) : "";
116+
});
117+
}
118+
119+
/**
120+
* Transfer credits from the caller's public balance to a recipient's public balance.
121+
*
122+
* @param recipient - The Aleo address to receive the credits
123+
* @param amount - The amount to transfer in microcredits
124+
* @returns The outputs from the execution
125+
*
126+
* @example
127+
* await credits.transferPublic("aleo1abc...xyz", 1000000);
128+
*/
129+
async transferPublic(recipient: string, amount: number): Promise<string[]> {
130+
return this.execute("transfer_public", [recipient, `${amount}u64`]);
131+
}
132+
133+
/**
134+
* Transfer credits from the caller's public balance to a recipient as a private record.
135+
*
136+
* @param recipient - The Aleo address to receive the private record
137+
* @param amount - The amount to transfer in microcredits
138+
* @returns The outputs containing the new private record
139+
*
140+
* @example
141+
* await credits.transferPublicToPrivate("aleo1abc...xyz", 500000);
142+
*/
143+
async transferPublicToPrivate(recipient: string, amount: number): Promise<string[]> {
144+
return this.execute("transfer_public_to_private", [recipient, `${amount}u64`]);
145+
}
146+
147+
/**
148+
* Transfer credits privately from one record to another address.
149+
*
150+
* @param record - The sender's credits record (plaintext format)
151+
* @param recipient - The Aleo address to receive the credits
152+
* @param amount - The amount to transfer in microcredits
153+
* @returns The outputs containing recipient record + change record
154+
*
155+
* @example
156+
* await credits.transferPrivate(myRecord, "aleo1abc...xyz", 100000);
157+
*/
158+
async transferPrivate(record: string, recipient: string, amount: number): Promise<string[]> {
159+
return this.execute("transfer_private", [record, recipient, `${amount}u64`]);
160+
}
161+
162+
/**
163+
* Transfer credits from a private record to a recipient's public balance.
164+
*
165+
* @param record - The sender's credits record (plaintext format)
166+
* @param recipient - The Aleo address to receive the public credits
167+
* @param amount - The amount to transfer in microcredits
168+
* @returns The outputs containing the change record
169+
*
170+
* @example
171+
* await credits.transferPrivateToPublic(myRecord, "aleo1abc...xyz", 50000);
172+
*/
173+
async transferPrivateToPublic(record: string, recipient: string, amount: number): Promise<string[]> {
174+
return this.execute("transfer_private_to_public", [record, recipient, `${amount}u64`]);
175+
}
176+
177+
/**
178+
* Combine two private credit records into a single record.
179+
*
180+
* @param record1 - The first credits record to combine
181+
* @param record2 - The second credits record to combine
182+
* @returns The outputs containing the combined record
183+
*
184+
* @example
185+
* await credits.join(recordA, recordB);
186+
*/
187+
async join(record1: string, record2: string): Promise<string[]> {
188+
return this.execute("join", [record1, record2]);
189+
}
190+
191+
/**
192+
* Split a private credit record into two separate records.
193+
*
194+
* @param record - The credits record to split
195+
* @param amount - The amount for the first output record
196+
* @returns The outputs containing two records
197+
*
198+
* @example
199+
* await credits.split(myRecord, 200000);
200+
*/
201+
async split(record: string, amount: number): Promise<string[]> {
202+
return this.execute("split", [record, `${amount}u64`]);
203+
}
204+
}
205+
206+
// ============================================================================
207+
// Demo: Using the Credits class
208+
// ============================================================================
209+
210+
// Import the account
211+
const accountCiphertext =
212+
"ciphertext1qvq283j7ujnhz59d4rnu772rfmvf94039x9ekhk2lzuutteqzlghsr3g9824qgw97a79mmdymqdt0ulqdkahq39vnerw2tl7thvvnnunq386jzjnw29e0ghnq7unphgdzw637q3fgvvlkrcywsc5jukkdhss5qq3njp";
213+
const account = Account.fromCiphertext(accountCiphertext, "provablealeo1");
214+
215+
// Create the Credits instance
216+
const credits = new Credits(account);
217+
218+
// Specify the recipient
219+
const recipient = "aleo1vskzxa2qqgnhznxsqh6tgq93c30sfkj6xqwe7sr85lgjkexjlcxs3lxhy3";
220+
221+
// NOTE: These records exist on testnet and are used to build transactions.
222+
// Since we only build (not broadcast) the transactions, they remain unspent.
223+
224+
const sendRecord = `{
225+
owner: aleo1vskzxa2qqgnhznxsqh6tgq93c30sfkj6xqwe7sr85lgjkexjlcxs3lxhy3.private,
226+
microcredits: 500000u64.private,
227+
_nonce: 2128807984625485873765840993868794284062894954530194503954279385341936659546group.public,
228+
_version: 1u8.public
229+
}`;
230+
231+
const joinRecord = `{
232+
owner: aleo1vskzxa2qqgnhznxsqh6tgq93c30sfkj6xqwe7sr85lgjkexjlcxs3lxhy3.private,
233+
microcredits: 1000000u64.private,
234+
_nonce: 3679642728562651942188038004588605401119210243204186196628122783406618717891group.public,
235+
_version: 1u8.public
236+
}`;
237+
238+
const functions: Record<string, () => Promise<string[]>> = {
239+
transfer_public: () => credits.transferPublic(recipient, 50000),
240+
transfer_public_to_private: () => credits.transferPublicToPrivate(recipient, 50000),
241+
transfer_private: () => credits.transferPrivate(sendRecord, recipient, 100000),
242+
transfer_private_to_public: () => credits.transferPrivateToPublic(sendRecord, recipient, 50000),
243+
join: () => credits.join(sendRecord, joinRecord),
244+
split: () => credits.split(sendRecord, 250000),
245+
};
246+
247+
async function main() {
248+
const selected = process.argv[2];
249+
250+
if (selected && !functions[selected]) {
251+
console.error(`Unknown function: ${selected}`);
252+
console.error(`Available: ${Object.keys(functions).join(", ")}`);
253+
process.exit(1);
254+
}
255+
256+
const toRun = selected ? { [selected]: functions[selected] } : functions;
257+
258+
for (const fn of Object.values(toRun)) {
259+
await fn();
260+
}
261+
262+
console.log("\nDone!");
263+
}
264+
265+
main().catch(console.error);

0 commit comments

Comments
 (0)