@tangle-network/tcloud 0.1.4 → 0.2.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.
@@ -1,8 +1,10 @@
1
1
  import {
2
- TCloudClient,
3
2
  createShieldedClient,
4
3
  generateWallet
5
- } from "./chunk-KVXWEAK3.js";
4
+ } from "./chunk-VD4RNZOC.js";
5
+ import {
6
+ TCloudClient
7
+ } from "./chunk-HL4CXKET.js";
6
8
 
7
9
  // src/index.ts
8
10
  var TCloud = class _TCloud extends TCloudClient {
@@ -0,0 +1,263 @@
1
+ import {
2
+ TCloudClient
3
+ } from "./chunk-HL4CXKET.js";
4
+
5
+ // src/shielded.ts
6
+ import { privateKeyToAccount } from "viem/accounts";
7
+ import { keccak256, encodeAbiParameters, parseAbiParameters, concat, toBytes } from "viem";
8
+ var SPEND_TYPEHASH = keccak256(
9
+ toBytes(
10
+ "SpendAuthorization(bytes32 commitment,uint64 serviceId,uint8 jobIndex,uint256 amount,address operator,uint256 nonce,uint64 expiry)"
11
+ )
12
+ );
13
+ var DEFAULT_DOMAIN = {
14
+ name: "ShieldedCredits",
15
+ version: "1"
16
+ };
17
+ function generateWallet() {
18
+ const privateKeyBytes = crypto.getRandomValues(new Uint8Array(32));
19
+ const privateKey = "0x" + Array.from(privateKeyBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
20
+ const saltBytes = crypto.getRandomValues(new Uint8Array(32));
21
+ const salt = "0x" + Array.from(saltBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
22
+ const account = privateKeyToAccount(privateKey);
23
+ const commitment = keccak256(
24
+ encodeAbiParameters(
25
+ parseAbiParameters("address, bytes32"),
26
+ [account.address, salt]
27
+ )
28
+ );
29
+ return { privateKey, address: account.address, commitment, salt };
30
+ }
31
+ async function signSpendAuth(wallet, params) {
32
+ const account = privateKeyToAccount(wallet.privateKey);
33
+ const domainSeparator = keccak256(
34
+ encodeAbiParameters(
35
+ parseAbiParameters("bytes32, bytes32, bytes32, uint256, address"),
36
+ [
37
+ keccak256(toBytes("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")),
38
+ keccak256(toBytes(DEFAULT_DOMAIN.name)),
39
+ keccak256(toBytes(DEFAULT_DOMAIN.version)),
40
+ BigInt(params.chainId),
41
+ params.creditsAddress
42
+ ]
43
+ )
44
+ );
45
+ const structHash = keccak256(
46
+ encodeAbiParameters(
47
+ parseAbiParameters("bytes32, bytes32, uint64, uint8, uint256, address, uint256, uint64"),
48
+ [
49
+ SPEND_TYPEHASH,
50
+ wallet.commitment,
51
+ params.serviceId,
52
+ params.jobIndex,
53
+ params.amount,
54
+ params.operator,
55
+ params.nonce,
56
+ params.expiry
57
+ ]
58
+ )
59
+ );
60
+ const digest = keccak256(
61
+ concat([toBytes("0x1901"), toBytes(domainSeparator), toBytes(structHash)])
62
+ );
63
+ const signature = await account.sign({ hash: digest });
64
+ return {
65
+ commitment: wallet.commitment,
66
+ serviceId: params.serviceId.toString(),
67
+ jobIndex: params.jobIndex,
68
+ amount: params.amount.toString(),
69
+ operator: params.operator,
70
+ nonce: params.nonce.toString(),
71
+ expiry: params.expiry.toString(),
72
+ signature
73
+ };
74
+ }
75
+ function estimateCost(inputTokens, maxOutputTokens, inputPricePerM = 0.15, outputPricePerM = 0.6) {
76
+ const cost = inputTokens / 1e6 * inputPricePerM + maxOutputTokens / 1e6 * outputPricePerM;
77
+ return BigInt(Math.ceil(cost * 1e6));
78
+ }
79
+ function createShieldedClient(config = {}) {
80
+ const wallet = config.wallet || generateWallet();
81
+ const chainId = config.chainId || 3799;
82
+ const creditsAddress = config.creditsAddress || "0x0000000000000000000000000000000000000000";
83
+ const operatorAddress = config.operatorAddress || "0x0000000000000000000000000000000000000000";
84
+ const serviceId = config.serviceId || 1n;
85
+ let nonce = 0n;
86
+ const client = new TCloudClient({
87
+ ...config,
88
+ apiKey: void 0
89
+ // no API key in private mode
90
+ });
91
+ client.setSpendAuthSigner(async () => {
92
+ const currentNonce = nonce++;
93
+ const amount = estimateCost(500, 4096);
94
+ const buffered = amount + amount / 5n;
95
+ return signSpendAuth(wallet, {
96
+ serviceId,
97
+ jobIndex: 0,
98
+ amount: buffered,
99
+ operator: operatorAddress,
100
+ nonce: currentNonce,
101
+ expiry: BigInt(Math.floor(Date.now() / 1e3) + 300),
102
+ chainId,
103
+ creditsAddress
104
+ });
105
+ });
106
+ const monitor = { lastBalance: 0n, timer: null, replenishing: false };
107
+ if (config.autoReplenish) {
108
+ const ar = config.autoReplenish;
109
+ const intervalMs = ar.checkIntervalMs ?? 3e4;
110
+ const check = async () => {
111
+ try {
112
+ const balance = await fetchBalance(wallet.commitment, creditsAddress, chainId);
113
+ monitor.lastBalance = balance;
114
+ if (balance < ar.minBalance && !monitor.replenishing) {
115
+ monitor.replenishing = true;
116
+ try {
117
+ if (ar.fundingSource === "relayer") {
118
+ await replenishViaRelayer(ar.relayerUrl, wallet.commitment, wallet.privateKey);
119
+ } else {
120
+ await replenishDirect(
121
+ ar.fundingWalletKey,
122
+ ar.tokenAddress,
123
+ ar.replenishAmount,
124
+ wallet.commitment,
125
+ wallet.address,
126
+ creditsAddress,
127
+ chainId
128
+ );
129
+ }
130
+ monitor.lastBalance = await fetchBalance(wallet.commitment, creditsAddress, chainId);
131
+ console.log(`[tcloud/shielded] replenished. balance=${monitor.lastBalance}`);
132
+ } finally {
133
+ monitor.replenishing = false;
134
+ }
135
+ }
136
+ } catch (err) {
137
+ console.error("[tcloud/shielded] auto-replenish error:", err instanceof Error ? err.message : String(err));
138
+ }
139
+ };
140
+ void check();
141
+ monitor.timer = setInterval(() => void check(), intervalMs);
142
+ }
143
+ function stopAutoReplenish() {
144
+ if (monitor.timer) {
145
+ clearInterval(monitor.timer);
146
+ monitor.timer = null;
147
+ }
148
+ }
149
+ return Object.assign(client, { wallet, stopAutoReplenish });
150
+ }
151
+ var GET_ACCOUNT_ABI = [{
152
+ type: "function",
153
+ name: "getAccount",
154
+ inputs: [{ name: "commitment", type: "bytes32" }],
155
+ outputs: [{
156
+ name: "",
157
+ type: "tuple",
158
+ components: [
159
+ { name: "spendingKey", type: "address" },
160
+ { name: "token", type: "address" },
161
+ { name: "balance", type: "uint256" },
162
+ { name: "totalFunded", type: "uint256" },
163
+ { name: "totalSpent", type: "uint256" },
164
+ { name: "nonce", type: "uint256" }
165
+ ]
166
+ }],
167
+ stateMutability: "view"
168
+ }];
169
+ var FUND_CREDITS_ABI = [{
170
+ type: "function",
171
+ name: "fundCredits",
172
+ inputs: [
173
+ { name: "token", type: "address" },
174
+ { name: "amount", type: "uint256" },
175
+ { name: "commitment", type: "bytes32" },
176
+ { name: "spendingKey", type: "address" }
177
+ ],
178
+ outputs: [],
179
+ stateMutability: "nonpayable"
180
+ }];
181
+ var ERC20_APPROVE_ABI = [{
182
+ type: "function",
183
+ name: "approve",
184
+ inputs: [
185
+ { name: "spender", type: "address" },
186
+ { name: "amount", type: "uint256" }
187
+ ],
188
+ outputs: [{ name: "", type: "bool" }],
189
+ stateMutability: "nonpayable"
190
+ }];
191
+ function makeChain(chainId, rpcUrl) {
192
+ return {
193
+ id: chainId,
194
+ name: `chain-${chainId}`,
195
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
196
+ rpcUrls: { default: { http: [rpcUrl] } }
197
+ };
198
+ }
199
+ function getRpcUrl(chainId) {
200
+ if (chainId === 3799) return "https://testnet-rpc.tangle.tools";
201
+ if (chainId === 5845) return "https://rpc.tangle.tools";
202
+ return "http://localhost:8545";
203
+ }
204
+ async function fetchBalance(commitment, creditsAddress, chainId) {
205
+ const { createPublicClient, http } = await import("viem");
206
+ const rpcUrl = getRpcUrl(chainId);
207
+ const client = createPublicClient({ chain: makeChain(chainId, rpcUrl), transport: http(rpcUrl) });
208
+ const result = await client.readContract({
209
+ address: creditsAddress,
210
+ abi: GET_ACCOUNT_ABI,
211
+ functionName: "getAccount",
212
+ args: [commitment]
213
+ });
214
+ return result.balance;
215
+ }
216
+ async function replenishViaRelayer(relayerUrl, commitment, spendingKey) {
217
+ const res = await fetch(`${relayerUrl.replace(/\/$/, "")}/relay/fund-credits`, {
218
+ method: "POST",
219
+ headers: { "Content-Type": "application/json" },
220
+ body: JSON.stringify({
221
+ anchorProof: { proof: "0x", auxPublicInputs: "0x", externalData: "0x", publicInputs: "0x", encryptions: "0x" },
222
+ commitment,
223
+ spendingKey
224
+ })
225
+ });
226
+ if (!res.ok) {
227
+ const body = await res.text();
228
+ throw new Error(`relayer fund-credits failed (${res.status}): ${body}`);
229
+ }
230
+ }
231
+ async function replenishDirect(fundingKey, tokenAddress, amount, commitment, spendingKeyAddress, creditsAddress, chainId) {
232
+ const { createPublicClient, createWalletClient, http } = await import("viem");
233
+ const { privateKeyToAccount: toAccount } = await import("viem/accounts");
234
+ const rpcUrl = getRpcUrl(chainId);
235
+ const chain = makeChain(chainId, rpcUrl);
236
+ const account = toAccount(fundingKey);
237
+ const pub = createPublicClient({ chain, transport: http(rpcUrl) });
238
+ const wal = createWalletClient({ account, chain, transport: http(rpcUrl) });
239
+ const approveHash = await wal.writeContract({
240
+ address: tokenAddress,
241
+ abi: ERC20_APPROVE_ABI,
242
+ functionName: "approve",
243
+ args: [creditsAddress, amount]
244
+ });
245
+ await pub.waitForTransactionReceipt({ hash: approveHash });
246
+ const fundHash = await wal.writeContract({
247
+ address: creditsAddress,
248
+ abi: FUND_CREDITS_ABI,
249
+ functionName: "fundCredits",
250
+ args: [tokenAddress, amount, commitment, spendingKeyAddress]
251
+ });
252
+ const receipt = await pub.waitForTransactionReceipt({ hash: fundHash });
253
+ if (receipt.status !== "success") {
254
+ throw new Error(`fundCredits reverted (tx: ${fundHash})`);
255
+ }
256
+ }
257
+
258
+ export {
259
+ generateWallet,
260
+ signSpendAuth,
261
+ estimateCost,
262
+ createShieldedClient
263
+ };