@attocash/n8n-nodes-atto 0.2.2 → 0.3.1
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 +4 -4
- package/THIRD_PARTY_NOTICES.md +224 -0
- package/dist/credentials/AttoApi.credentials.js +3 -3
- package/dist/nodes/Atto/Atto.node.js +649 -111101
- package/dist/nodes/Atto/Atto.node.json +1 -1
- package/dist/nodes/Atto/operations.js +434 -516
- package/dist/nodes/Atto/protocol.js +2069 -0
- package/dist/nodes/AttoTrigger/AttoTrigger.node.js +301 -110737
- package/dist/nodes/AttoTrigger/AttoTrigger.node.json +1 -1
- package/dist/package.json +6 -10
- package/package.json +6 -10
- package/dist/credentials/AttoApi.credentials.d.ts +0 -9
- package/dist/credentials/AttoApi.credentials.js.map +0 -1
- package/dist/nodes/Atto/Atto.node.d.ts +0 -5
- package/dist/nodes/Atto/Atto.node.js.map +0 -1
- package/dist/nodes/Atto/operations.d.ts +0 -38
- package/dist/nodes/Atto/operations.js.map +0 -1
- package/dist/nodes/AttoTrigger/AttoTrigger.node.d.ts +0 -5
- package/dist/nodes/AttoTrigger/AttoTrigger.node.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -1,225 +1,200 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.deriveAccountFromSecret = void 0;
|
|
4
|
-
exports.deriveAddressFromSecret = deriveAddressFromSecret;
|
|
5
|
-
exports.createNodeClient = createNodeClient;
|
|
6
|
-
exports.createWorkerClient = createWorkerClient;
|
|
7
|
-
exports.clearWorkerClientCache = clearWorkerClientCache;
|
|
8
3
|
exports.executeAttoOperation = executeAttoOperation;
|
|
9
|
-
exports.
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const commons_node_1 = require("@attocash/commons-node");
|
|
13
|
-
const commons_node_remote_1 = require("@attocash/commons-node-remote");
|
|
14
|
-
const commons_wallet_1 = require("@attocash/commons-wallet");
|
|
15
|
-
const commons_worker_remote_1 = require("@attocash/commons-worker-remote");
|
|
4
|
+
exports.pollAttoEvent = pollAttoEvent;
|
|
5
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
6
|
+
const protocol_1 = require("./protocol");
|
|
16
7
|
const DEFAULT_STREAM_TIMEOUT_MS = 5000;
|
|
17
8
|
const DEFAULT_PUBLISH_TIMEOUT_MS = 60000;
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
const POLL_MAX_ITEMS = 100;
|
|
10
|
+
const POLL_TIMEOUT_MS = 2000;
|
|
11
|
+
const MAX_SEEN_POLL_ITEMS = 500;
|
|
12
|
+
function credentials(value) {
|
|
13
|
+
return (value ?? {});
|
|
22
14
|
}
|
|
23
15
|
function text(value) {
|
|
24
16
|
return typeof value === 'string' ? value.trim() : '';
|
|
25
17
|
}
|
|
26
18
|
function optionalText(value) {
|
|
27
|
-
const
|
|
28
|
-
return
|
|
19
|
+
const valueText = text(value);
|
|
20
|
+
return valueText || undefined;
|
|
29
21
|
}
|
|
30
|
-
function
|
|
22
|
+
function nonNegativeInteger(value, fieldName) {
|
|
31
23
|
const numberValue = typeof value === 'number' ? value : Number(value);
|
|
32
|
-
if (!Number.
|
|
24
|
+
if (!Number.isSafeInteger(numberValue) || numberValue < 0)
|
|
33
25
|
throw new Error(`${fieldName} must be a non-negative integer`);
|
|
34
|
-
}
|
|
35
26
|
return numberValue;
|
|
36
27
|
}
|
|
37
|
-
function
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
28
|
+
function positiveInteger(value, fieldName) {
|
|
29
|
+
const numberValue = nonNegativeInteger(value, fieldName);
|
|
30
|
+
if (numberValue < 1)
|
|
40
31
|
throw new Error(`${fieldName} must be greater than zero`);
|
|
41
|
-
return
|
|
32
|
+
return numberValue;
|
|
42
33
|
}
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (source === 'node') {
|
|
51
|
-
const walletSecret = text(parameters.walletSecret);
|
|
52
|
-
if (!walletSecret)
|
|
53
|
-
throw new Error('Wallet Secret is required');
|
|
54
|
-
return {
|
|
55
|
-
secretType: parseSecretType(parameters.walletSecretType ?? 'mnemonic', 'Wallet Secret Type'),
|
|
56
|
-
walletSecret,
|
|
57
|
-
keyIndex: positiveInteger(parameters.keyIndex ?? 0, 'Key Index'),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
const walletSecret = text(credentials.walletSecret);
|
|
61
|
-
if (!walletSecret)
|
|
62
|
-
throw new Error('Atto credentials must include a Wallet Secret');
|
|
63
|
-
return {
|
|
64
|
-
secretType: parseSecretType(credentials.walletMaterialType ?? 'mnemonic', 'Credential Wallet Secret Type'),
|
|
65
|
-
walletSecret,
|
|
66
|
-
keyIndex: positiveInteger(credentials.keyIndex ?? 0, 'Credential Key Index'),
|
|
67
|
-
};
|
|
34
|
+
function positiveHeight(value, fieldName) {
|
|
35
|
+
const valueText = optionalText(value);
|
|
36
|
+
if (!valueText)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (!/^\d+$/.test(valueText) || BigInt(valueText) < 1n)
|
|
39
|
+
throw new Error(`${fieldName} must be a positive integer`);
|
|
40
|
+
return valueText;
|
|
68
41
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const index = (0, commons_core_1.toAttoIndex)(input.keyIndex);
|
|
72
|
-
if (input.secretType === 'mnemonic') {
|
|
73
|
-
const mnemonic = commons_core_1.AttoMnemonic.fromPhrase(input.walletSecret);
|
|
74
|
-
const seed = await (0, commons_core_1.toSeedAsync)(mnemonic);
|
|
75
|
-
const privateKey = (0, commons_core_1.toPrivateKey)(seed, index);
|
|
76
|
-
const publicKey = (0, commons_core_1.toPublicKey)(privateKey);
|
|
77
|
-
const address = new commons_core_1.AttoAddress(commons_core_1.AttoAlgorithm.V1, publicKey);
|
|
78
|
-
return {
|
|
79
|
-
secretType: input.secretType,
|
|
80
|
-
keyIndex: input.keyIndex,
|
|
81
|
-
privateKey,
|
|
82
|
-
publicKey,
|
|
83
|
-
address,
|
|
84
|
-
seed,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
const privateKey = commons_core_1.AttoPrivateKey.Companion.parse(input.walletSecret);
|
|
88
|
-
const publicKey = (0, commons_core_1.toPublicKey)(privateKey);
|
|
89
|
-
const address = new commons_core_1.AttoAddress(commons_core_1.AttoAlgorithm.V1, publicKey);
|
|
90
|
-
return {
|
|
91
|
-
secretType: input.secretType,
|
|
92
|
-
keyIndex: input.keyIndex,
|
|
93
|
-
privateKey,
|
|
94
|
-
publicKey,
|
|
95
|
-
address,
|
|
96
|
-
};
|
|
42
|
+
function simplifyOutput(parameters) {
|
|
43
|
+
return parameters.simplify !== false;
|
|
97
44
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (!nodeUrl)
|
|
45
|
+
function requireNodeUrl(value) {
|
|
46
|
+
const url = text(value.nodeUrl);
|
|
47
|
+
if (!url)
|
|
102
48
|
throw new Error('Atto credentials must include a Node Base URL');
|
|
103
|
-
return
|
|
49
|
+
return url.replace(/\/+$/, '');
|
|
104
50
|
}
|
|
105
|
-
function requireWorkerUrl(
|
|
106
|
-
const
|
|
107
|
-
if (!
|
|
51
|
+
function requireWorkerUrl(value) {
|
|
52
|
+
const url = text(value.workerUrl);
|
|
53
|
+
if (!url)
|
|
108
54
|
throw new Error('Atto credentials must include a Worker Base URL');
|
|
109
|
-
return
|
|
55
|
+
return url.replace(/\/+$/, '');
|
|
110
56
|
}
|
|
111
|
-
function
|
|
112
|
-
const
|
|
57
|
+
function requestHeaders(value, accept = 'application/json') {
|
|
58
|
+
const headers = { Accept: accept };
|
|
59
|
+
const apiKey = text(value.apiKey);
|
|
113
60
|
if (!apiKey)
|
|
114
|
-
return
|
|
115
|
-
const
|
|
116
|
-
if (!
|
|
61
|
+
return headers;
|
|
62
|
+
const headerName = text(value.authHeaderName || 'Authorization');
|
|
63
|
+
if (!headerName)
|
|
117
64
|
throw new Error('API Key Header is required when API Key is set');
|
|
118
|
-
const prefix = text(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
function sha256(value) {
|
|
122
|
-
return (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
|
|
123
|
-
}
|
|
124
|
-
function workerClientCacheKey(credentials) {
|
|
125
|
-
const workerUrl = requireWorkerUrl(credentials);
|
|
126
|
-
const apiKey = text(credentials.apiKey);
|
|
127
|
-
if (!apiKey) {
|
|
128
|
-
return JSON.stringify({
|
|
129
|
-
workerUrl,
|
|
130
|
-
auth: 'none',
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
const header = text(credentials.authHeaderName || 'Authorization');
|
|
134
|
-
if (!header)
|
|
135
|
-
throw new Error('API Key Header is required when API Key is set');
|
|
136
|
-
const prefix = text(credentials.authHeaderPrefix);
|
|
137
|
-
const headerValue = prefix ? `${prefix} ${apiKey}` : apiKey;
|
|
138
|
-
return JSON.stringify({
|
|
139
|
-
workerUrl,
|
|
140
|
-
header,
|
|
141
|
-
headerValueHash: sha256(headerValue),
|
|
142
|
-
});
|
|
65
|
+
const prefix = text(value.authHeaderPrefix);
|
|
66
|
+
headers[headerName] = prefix ? `${prefix} ${apiKey}` : apiKey;
|
|
67
|
+
return headers;
|
|
143
68
|
}
|
|
144
|
-
function
|
|
145
|
-
const
|
|
146
|
-
return
|
|
147
|
-
}
|
|
148
|
-
function createWorkerClient(credentials) {
|
|
149
|
-
const attoCredentials = normalizeCredentials(credentials);
|
|
150
|
-
const cacheKey = workerClientCacheKey(attoCredentials);
|
|
151
|
-
const cachedWorker = workerClientCache.get(cacheKey);
|
|
152
|
-
if (cachedWorker)
|
|
153
|
-
return cachedWorker;
|
|
154
|
-
const worker = applyHeaders(new commons_worker_remote_1.AttoWorkerAsyncBuilder(requireWorkerUrl(attoCredentials)), attoCredentials)
|
|
155
|
-
.cached(true)
|
|
156
|
-
.build();
|
|
157
|
-
if (workerClientCache.size >= MAX_WORKER_CLIENT_CACHE_SIZE) {
|
|
158
|
-
const oldestKey = workerClientCache.keys().next().value;
|
|
159
|
-
if (typeof oldestKey === 'string')
|
|
160
|
-
workerClientCache.delete(oldestKey);
|
|
161
|
-
}
|
|
162
|
-
workerClientCache.set(cacheKey, worker);
|
|
163
|
-
return worker;
|
|
69
|
+
function apiError(context, error, message) {
|
|
70
|
+
const cause = error && typeof error === 'object' ? error : { message: String(error) };
|
|
71
|
+
return new n8n_workflow_1.NodeApiError(context.getNode(), cause, { message });
|
|
164
72
|
}
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
73
|
+
function responseStatus(response) {
|
|
74
|
+
if (!response || typeof response !== 'object')
|
|
75
|
+
return undefined;
|
|
76
|
+
const value = response;
|
|
77
|
+
const status = Number(value.statusCode ?? value.status);
|
|
78
|
+
return Number.isFinite(status) ? status : undefined;
|
|
79
|
+
}
|
|
80
|
+
function responseBody(response) {
|
|
81
|
+
if (!response || typeof response !== 'object' || !('body' in response))
|
|
82
|
+
return response;
|
|
83
|
+
return response.body;
|
|
84
|
+
}
|
|
85
|
+
async function requestText(context, value, baseUrl, method, path, options = {}) {
|
|
86
|
+
const request = {
|
|
87
|
+
url: `${baseUrl}/${path}`,
|
|
88
|
+
method,
|
|
89
|
+
headers: {
|
|
90
|
+
...requestHeaders(value),
|
|
91
|
+
...(options.body ? { 'Content-Type': 'application/json' } : {}),
|
|
92
|
+
},
|
|
93
|
+
encoding: 'text',
|
|
94
|
+
json: false,
|
|
95
|
+
timeout: options.timeoutMs ?? 10_000,
|
|
96
|
+
returnFullResponse: options.allowNotFound,
|
|
97
|
+
ignoreHttpStatusErrors: options.allowNotFound,
|
|
98
|
+
...(options.body ? { body: (0, protocol_1.stringifyAttoJson)(options.body) } : {}),
|
|
99
|
+
};
|
|
100
|
+
try {
|
|
101
|
+
const response = await context.helpers.httpRequest(request);
|
|
102
|
+
if (options.allowNotFound && responseStatus(response) === 404)
|
|
103
|
+
return undefined;
|
|
104
|
+
const status = responseStatus(response);
|
|
105
|
+
if (status !== undefined && status >= 400)
|
|
106
|
+
throw new Error(`Atto API returned HTTP ${status}`);
|
|
107
|
+
const body = responseBody(response);
|
|
108
|
+
return typeof body === 'string' ? body : JSON.stringify(body);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
throw apiError(context, error, `Atto API request failed: ${method} /${path}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function requestJson(context, value, baseUrl, method, path, options = {}) {
|
|
115
|
+
const body = await requestText(context, value, baseUrl, method, path, options);
|
|
116
|
+
if (body === undefined || !body.trim())
|
|
117
|
+
return undefined;
|
|
118
|
+
try {
|
|
119
|
+
return (0, protocol_1.parseAttoJson)(body);
|
|
182
120
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
get: async () => (0, commons_core_1.privateKeyToSigner)(derived.privateKey),
|
|
186
|
-
});
|
|
121
|
+
catch (error) {
|
|
122
|
+
throw apiError(context, error, `Atto API returned invalid JSON for ${method} /${path}`);
|
|
187
123
|
}
|
|
188
|
-
const wallet = builder.build();
|
|
189
|
-
await wallet.openAccount((0, commons_core_1.toAttoIndex)(derived.keyIndex));
|
|
190
|
-
return { node, derived, wallet };
|
|
191
124
|
}
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
125
|
+
function streamChunk(value) {
|
|
126
|
+
if (typeof value === 'string')
|
|
127
|
+
return value;
|
|
128
|
+
if (Buffer.isBuffer(value))
|
|
129
|
+
return value.toString('utf8');
|
|
130
|
+
if (value instanceof Uint8Array)
|
|
131
|
+
return Buffer.from(value).toString('utf8');
|
|
132
|
+
return String(value);
|
|
133
|
+
}
|
|
134
|
+
function isExpectedStreamEnd(error) {
|
|
135
|
+
if (!error || typeof error !== 'object')
|
|
136
|
+
return false;
|
|
137
|
+
const value = error;
|
|
138
|
+
const message = String(value.message ?? '').toLowerCase();
|
|
139
|
+
return (value.code === 'ECONNABORTED' ||
|
|
140
|
+
value.name === 'AbortError' ||
|
|
141
|
+
message.includes('aborted') ||
|
|
142
|
+
message.includes('timeout') ||
|
|
143
|
+
message.includes('timed out'));
|
|
144
|
+
}
|
|
145
|
+
async function requestStream(context, value, request) {
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
const options = {
|
|
148
|
+
url: `${requireNodeUrl(value)}/${request.path}`,
|
|
149
|
+
method: request.method,
|
|
150
|
+
headers: {
|
|
151
|
+
...requestHeaders(value, 'application/x-ndjson'),
|
|
152
|
+
...(request.body ? { 'Content-Type': 'application/json' } : {}),
|
|
153
|
+
},
|
|
154
|
+
encoding: 'stream',
|
|
155
|
+
json: false,
|
|
156
|
+
timeout: request.timeoutMs,
|
|
157
|
+
abortSignal: controller.signal,
|
|
158
|
+
...(request.body ? { body: (0, protocol_1.stringifyAttoJson)(request.body) } : {}),
|
|
159
|
+
};
|
|
160
|
+
const items = [];
|
|
161
|
+
let pending = '';
|
|
196
162
|
try {
|
|
197
|
-
|
|
163
|
+
const response = await context.helpers.httpRequest(options);
|
|
164
|
+
if (!response || typeof response !== 'object' || !(Symbol.asyncIterator in response)) {
|
|
165
|
+
throw new Error('Atto stream response is not readable');
|
|
166
|
+
}
|
|
167
|
+
for await (const chunk of response) {
|
|
168
|
+
pending += streamChunk(chunk);
|
|
169
|
+
const lines = pending.split('\n');
|
|
170
|
+
pending = lines.pop() ?? '';
|
|
171
|
+
for (const line of lines) {
|
|
172
|
+
if (!line.trim())
|
|
173
|
+
continue;
|
|
174
|
+
const parsed = (0, protocol_1.parseAttoJson)(line);
|
|
175
|
+
items.push(parsed);
|
|
176
|
+
if (items.length >= request.maxItems) {
|
|
177
|
+
controller.abort();
|
|
178
|
+
return items;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (pending.trim() && items.length < request.maxItems)
|
|
183
|
+
items.push((0, protocol_1.parseAttoJson)(pending));
|
|
184
|
+
return items;
|
|
198
185
|
}
|
|
199
|
-
catch {
|
|
200
|
-
|
|
186
|
+
catch (error) {
|
|
187
|
+
if (controller.signal.aborted || isExpectedStreamEnd(error))
|
|
188
|
+
return items;
|
|
189
|
+
throw apiError(context, error, `Atto API stream failed: ${request.method} /${request.path}`);
|
|
201
190
|
}
|
|
202
191
|
}
|
|
203
|
-
function
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
function parseAddressList(value, fieldName) {
|
|
208
|
-
const raw = typeof value === 'string'
|
|
209
|
-
? value
|
|
210
|
-
: Array.isArray(value)
|
|
211
|
-
? value.join('\n')
|
|
212
|
-
: '';
|
|
213
|
-
const addresses = raw
|
|
214
|
-
.split(/[\n,]+/)
|
|
215
|
-
.map((address) => address.trim())
|
|
216
|
-
.filter(Boolean)
|
|
217
|
-
.map((address) => parseAddress(address, fieldName));
|
|
218
|
-
if (addresses.length === 0)
|
|
219
|
-
throw new Error(`${fieldName} is required`);
|
|
220
|
-
return addresses;
|
|
192
|
+
function contextRequired(context) {
|
|
193
|
+
if (!context)
|
|
194
|
+
throw new Error('This Atto operation requires an n8n execution context');
|
|
195
|
+
return context;
|
|
221
196
|
}
|
|
222
|
-
function
|
|
197
|
+
function addressSource(value, allowAll) {
|
|
223
198
|
const source = text(value || 'credentials');
|
|
224
199
|
if (source === 'credentials' || source === 'manual')
|
|
225
200
|
return source;
|
|
@@ -227,398 +202,341 @@ function parseAddressSource(value, allowAll) {
|
|
|
227
202
|
return source;
|
|
228
203
|
throw new Error('Address Source must be credentials, manual, or all');
|
|
229
204
|
}
|
|
230
|
-
function
|
|
205
|
+
function queryMode(value) {
|
|
231
206
|
const mode = text(value || 'credentials');
|
|
232
207
|
if (mode === 'credentials' || mode === 'manual' || mode === 'all' || mode === 'hash')
|
|
233
208
|
return mode;
|
|
234
209
|
throw new Error('Query Mode must be credentials, manual, all, or hash');
|
|
235
210
|
}
|
|
236
|
-
|
|
237
|
-
const source =
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
async function addressForCredentials(credentials) {
|
|
246
|
-
const derived = await deriveAddressFromSecret({ secretSource: 'credentials' }, credentials);
|
|
247
|
-
return derived.address;
|
|
248
|
-
}
|
|
249
|
-
function parseAmount(amount, unit, fieldName) {
|
|
250
|
-
const raw = text(amount);
|
|
251
|
-
if (!raw)
|
|
211
|
+
function parseAddresses(value, fieldName) {
|
|
212
|
+
const source = typeof value === 'string' ? value : Array.isArray(value) ? value.join('\n') : '';
|
|
213
|
+
const result = source
|
|
214
|
+
.split(/[\n,]+/)
|
|
215
|
+
.map((address) => address.trim())
|
|
216
|
+
.filter(Boolean)
|
|
217
|
+
.map((address) => (0, protocol_1.parseAddress)(address, fieldName));
|
|
218
|
+
if (result.length === 0)
|
|
252
219
|
throw new Error(`${fieldName} is required`);
|
|
253
|
-
|
|
254
|
-
const amountValue = text(unit).toUpperCase() === 'RAW'
|
|
255
|
-
? commons_core_1.AttoAmount.from(commons_core_1.AttoUnit.RAW, raw)
|
|
256
|
-
: commons_core_1.AttoAmount.from(commons_core_1.AttoUnit.ATTO, raw);
|
|
257
|
-
if (amountValue.toString() === '0') {
|
|
258
|
-
throw new Error('zero');
|
|
259
|
-
}
|
|
260
|
-
return amountValue;
|
|
261
|
-
}
|
|
262
|
-
catch {
|
|
263
|
-
throw new Error(`${fieldName} must be a positive Atto amount`);
|
|
264
|
-
}
|
|
220
|
+
return result;
|
|
265
221
|
}
|
|
266
|
-
function
|
|
267
|
-
|
|
268
|
-
return raw ? parseAmount(raw, unit, fieldName) : undefined;
|
|
222
|
+
async function derivedAddress(value) {
|
|
223
|
+
return await (0, protocol_1.deriveAddressFromSecret)({ secretSource: 'credentials' }, value);
|
|
269
224
|
}
|
|
270
|
-
function
|
|
271
|
-
const
|
|
272
|
-
if (
|
|
273
|
-
throw new Error(`${fieldName} is required`);
|
|
274
|
-
try {
|
|
275
|
-
return commons_core_1.AttoHash.Companion.parse(raw);
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
throw new Error(`${fieldName} must be a valid Atto hash`);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
function parseOptionalHeight(value, fieldName) {
|
|
282
|
-
const raw = optionalText(value);
|
|
283
|
-
if (!raw)
|
|
225
|
+
async function addressesForSource(parameters, value, allowAll = false) {
|
|
226
|
+
const source = addressSource(parameters.addressSource, allowAll);
|
|
227
|
+
if (source === 'all')
|
|
284
228
|
return undefined;
|
|
285
|
-
|
|
286
|
-
return (
|
|
287
|
-
|
|
288
|
-
catch {
|
|
289
|
-
throw new Error(`${fieldName} must be a valid Atto height`);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
function parseRequiredHeight(value, fieldName) {
|
|
293
|
-
return parseOptionalHeight(value, fieldName) ?? (0, commons_core_1.toAttoHeight)('1');
|
|
294
|
-
}
|
|
295
|
-
function assertSameAddress(expected, actual, fieldName) {
|
|
296
|
-
if (!expected.equals(actual)) {
|
|
297
|
-
throw new Error(`${fieldName} must match the address derived from the wallet secret`);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
function assertOptionalSameAddress(expected, actual, fieldName) {
|
|
301
|
-
if (actual)
|
|
302
|
-
assertSameAddress(expected, actual, fieldName);
|
|
303
|
-
}
|
|
304
|
-
function parseJsonObject(value) {
|
|
305
|
-
return JSON.parse(value);
|
|
306
|
-
}
|
|
307
|
-
function amountOutput(amount) {
|
|
308
|
-
return {
|
|
309
|
-
raw: amount.toString(),
|
|
310
|
-
atto: amount.toFormattedString(commons_core_1.AttoUnit.ATTO),
|
|
311
|
-
};
|
|
229
|
+
if (source === 'manual')
|
|
230
|
+
return parseAddresses(parameters.addresses ?? parameters.address, 'Address');
|
|
231
|
+
return [await derivedAddress(value)];
|
|
312
232
|
}
|
|
313
|
-
function
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
height: account.height.toString(),
|
|
321
|
-
frontier: account.lastTransactionHash.toString(),
|
|
322
|
-
account: parseJsonObject((0, commons_node_1.accountToJson)(account)),
|
|
323
|
-
};
|
|
233
|
+
async function addressesForQuery(parameters, value) {
|
|
234
|
+
const mode = queryMode(parameters.queryMode);
|
|
235
|
+
if (mode === 'all' || mode === 'hash')
|
|
236
|
+
return undefined;
|
|
237
|
+
return mode === 'manual'
|
|
238
|
+
? parseAddresses(parameters.addresses ?? parameters.address, 'Address')
|
|
239
|
+
: [await derivedAddress(value)];
|
|
324
240
|
}
|
|
325
|
-
function
|
|
241
|
+
function streamLimits(parameters) {
|
|
326
242
|
return {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
fromAddress: receivable.address.value,
|
|
330
|
-
amount: amountOutput(receivable.amount),
|
|
331
|
-
receivable: parseJsonObject((0, commons_node_1.receivableToJson)(receivable)),
|
|
243
|
+
maxItems: positiveInteger(parameters.maxItems ?? 25, 'Max Items'),
|
|
244
|
+
timeoutMs: positiveInteger(parameters.timeoutMs ?? DEFAULT_STREAM_TIMEOUT_MS, 'Timeout'),
|
|
332
245
|
};
|
|
333
246
|
}
|
|
334
|
-
function
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
address: transaction.address.value,
|
|
339
|
-
height: transaction.height.toString(),
|
|
340
|
-
transaction: parseJsonObject((0, commons_node_1.transactionToJson)(transaction)),
|
|
341
|
-
};
|
|
247
|
+
function heightQuery(parameters) {
|
|
248
|
+
const fromHeight = positiveHeight(parameters.fromHeight, 'From Height') ?? '1';
|
|
249
|
+
const toHeight = positiveHeight(parameters.toHeight, 'To Height');
|
|
250
|
+
return `fromHeight=${encodeURIComponent(fromHeight)}${toHeight ? `&toHeight=${encodeURIComponent(toHeight)}` : ''}`;
|
|
342
251
|
}
|
|
343
|
-
function
|
|
252
|
+
function heightSearch(addresses, parameters) {
|
|
253
|
+
const fromHeight = positiveHeight(parameters.fromHeight, 'From Height') ?? '1';
|
|
254
|
+
const toHeight = positiveHeight(parameters.toHeight, 'To Height');
|
|
344
255
|
return {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
previousBalance: amountOutput(accountEntry.previousBalance),
|
|
351
|
-
balance: amountOutput(accountEntry.balance),
|
|
352
|
-
accountEntry: parseJsonObject((0, commons_node_1.accountEntryToJson)(accountEntry)),
|
|
256
|
+
search: addresses.map((address) => ({
|
|
257
|
+
address: address.value,
|
|
258
|
+
fromHeight,
|
|
259
|
+
...(toHeight ? { toHeight } : {}),
|
|
260
|
+
})),
|
|
353
261
|
};
|
|
354
262
|
}
|
|
355
|
-
function
|
|
263
|
+
async function receivableStreamRequest(parameters, value, maxItems, timeoutMs) {
|
|
264
|
+
const addresses = (await addressesForSource(parameters, value)) ?? [];
|
|
265
|
+
const minimum = optionalText(parameters.minAmount)
|
|
266
|
+
? (0, protocol_1.parseAmount)(parameters.minAmount, parameters.minAmountUnit ?? 'RAW', 'Minimum Amount').toString()
|
|
267
|
+
: '0';
|
|
268
|
+
const limits = streamLimits(parameters);
|
|
356
269
|
return {
|
|
357
|
-
|
|
358
|
-
|
|
270
|
+
method: 'POST',
|
|
271
|
+
path: `accounts/receivables/stream?minAmount=${minimum}`,
|
|
272
|
+
body: { addresses: addresses.map((address) => address.value) },
|
|
273
|
+
maxItems: maxItems ?? limits.maxItems,
|
|
274
|
+
timeoutMs: timeoutMs ?? limits.timeoutMs,
|
|
359
275
|
};
|
|
360
276
|
}
|
|
361
|
-
function
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (!error)
|
|
371
|
-
return new Error(fallback);
|
|
372
|
-
if (error instanceof Error)
|
|
373
|
-
return error;
|
|
374
|
-
return new Error(String(error));
|
|
375
|
-
}
|
|
376
|
-
async function collectStream(start, options) {
|
|
377
|
-
return await new Promise((resolve, reject) => {
|
|
378
|
-
const items = [];
|
|
379
|
-
let settled = false;
|
|
380
|
-
const state = {};
|
|
381
|
-
const finish = (error) => {
|
|
382
|
-
if (settled)
|
|
383
|
-
return;
|
|
384
|
-
settled = true;
|
|
385
|
-
clearTimeout(timer);
|
|
386
|
-
cancelJob(state.job);
|
|
387
|
-
if (error) {
|
|
388
|
-
reject(error);
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
resolve(items);
|
|
277
|
+
async function transactionStreamRequest(parameters, value, maxItems, timeoutMs) {
|
|
278
|
+
const mode = queryMode(parameters.queryMode);
|
|
279
|
+
const limits = streamLimits(parameters);
|
|
280
|
+
if (mode === 'hash') {
|
|
281
|
+
return {
|
|
282
|
+
method: 'GET',
|
|
283
|
+
path: `transactions/${(0, protocol_1.parseHash)(parameters.hash, 'Hash')}/stream`,
|
|
284
|
+
maxItems: maxItems ?? 1,
|
|
285
|
+
timeoutMs: timeoutMs ?? limits.timeoutMs,
|
|
392
286
|
};
|
|
393
|
-
const timer = setTimeout(() => finish(), options.timeoutMs);
|
|
394
|
-
state.job = start((item) => {
|
|
395
|
-
if (settled)
|
|
396
|
-
return;
|
|
397
|
-
items.push(item);
|
|
398
|
-
if (items.length >= options.maxItems)
|
|
399
|
-
finish();
|
|
400
|
-
}, (error) => {
|
|
401
|
-
if (settled)
|
|
402
|
-
return;
|
|
403
|
-
finish(error ? errorFromUnknown(error, 'Atto stream stopped') : undefined);
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
function inputItem(parameters) {
|
|
408
|
-
const item = parameters.inputItem;
|
|
409
|
-
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
410
|
-
throw new Error('Input item must contain a receivable object from Atto Trigger or Get Receivables');
|
|
411
|
-
}
|
|
412
|
-
return item;
|
|
413
|
-
}
|
|
414
|
-
function parseInputReceivable(parameters) {
|
|
415
|
-
const item = inputItem(parameters);
|
|
416
|
-
const value = item.receivable ?? item;
|
|
417
|
-
const json = typeof value === 'string' ? value : JSON.stringify(value);
|
|
418
|
-
try {
|
|
419
|
-
return (0, commons_node_1.receivableFromJson)(json);
|
|
420
287
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
async function withTimeout(promise, timeoutMs, operationName) {
|
|
426
|
-
let timer;
|
|
427
|
-
try {
|
|
428
|
-
return await Promise.race([
|
|
429
|
-
promise,
|
|
430
|
-
new Promise((_resolve, reject) => {
|
|
431
|
-
timer = setTimeout(() => reject(new Error(`${operationName} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
432
|
-
}),
|
|
433
|
-
]);
|
|
288
|
+
const addresses = await addressesForQuery(parameters, value);
|
|
289
|
+
if (!addresses) {
|
|
290
|
+
return { method: 'GET', path: 'transactions/stream', maxItems: maxItems ?? limits.maxItems, timeoutMs: timeoutMs ?? limits.timeoutMs };
|
|
434
291
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
292
|
+
if (addresses.length === 1) {
|
|
293
|
+
return {
|
|
294
|
+
method: 'GET',
|
|
295
|
+
path: `accounts/${addresses[0].publicKey}/transactions/stream?${heightQuery(parameters)}`,
|
|
296
|
+
maxItems: maxItems ?? limits.maxItems,
|
|
297
|
+
timeoutMs: timeoutMs ?? limits.timeoutMs,
|
|
298
|
+
};
|
|
438
299
|
}
|
|
300
|
+
return {
|
|
301
|
+
method: 'POST',
|
|
302
|
+
path: 'accounts/transactions/stream',
|
|
303
|
+
body: heightSearch(addresses, parameters),
|
|
304
|
+
maxItems: maxItems ?? limits.maxItems,
|
|
305
|
+
timeoutMs: timeoutMs ?? limits.timeoutMs,
|
|
306
|
+
};
|
|
439
307
|
}
|
|
440
|
-
function
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
const searches = addresses.map((address) => new commons_node_1.AccountHeightSearch(address, fromHeight, toHeight));
|
|
444
|
-
return commons_node_1.HeightSearch.Companion.fromArray(searches);
|
|
445
|
-
}
|
|
446
|
-
async function collectReceivables(node, parameters, credentials) {
|
|
447
|
-
const addresses = await addressesFromSource(parameters, credentials);
|
|
448
|
-
const minAmount = parseOptionalAmount(parameters.minAmount, parameters.minAmountUnit ?? 'RAW', 'Minimum Amount');
|
|
449
|
-
const options = streamOptions(parameters);
|
|
450
|
-
const receivables = await collectStream((onItem, onCancel) => node.onReceivableByAddresses(addresses, minAmount, onItem, onCancel), options);
|
|
451
|
-
return receivables.map(receivableOutput);
|
|
452
|
-
}
|
|
453
|
-
async function collectTransactionsForMode(node, parameters, credentials) {
|
|
454
|
-
const mode = parseQueryMode(parameters.queryMode);
|
|
308
|
+
async function accountEntryStreamRequest(parameters, value, maxItems, timeoutMs) {
|
|
309
|
+
const mode = queryMode(parameters.queryMode);
|
|
310
|
+
const limits = streamLimits(parameters);
|
|
455
311
|
if (mode === 'hash') {
|
|
456
|
-
|
|
457
|
-
|
|
312
|
+
return {
|
|
313
|
+
method: 'GET',
|
|
314
|
+
path: `accounts/entries/${(0, protocol_1.parseHash)(parameters.hash, 'Hash')}/stream`,
|
|
315
|
+
maxItems: maxItems ?? 1,
|
|
316
|
+
timeoutMs: timeoutMs ?? limits.timeoutMs,
|
|
317
|
+
};
|
|
458
318
|
}
|
|
459
|
-
const addresses =
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const options = streamOptions(parameters);
|
|
463
|
-
const transactions = await collectStream((onItem, onCancel) => subscribeToTransactionAddresses(node, addresses, fromHeight, toHeight, parameters, onItem, onCancel), options);
|
|
464
|
-
return transactions.map((transaction) => transactionOutput(transaction));
|
|
465
|
-
}
|
|
466
|
-
async function collectAccountEntries(node, parameters, credentials) {
|
|
467
|
-
const mode = parseQueryMode(parameters.queryMode);
|
|
468
|
-
if (mode === 'hash') {
|
|
469
|
-
const accountEntry = await node.accountEntry(parseHash(parameters.hash, 'Hash'));
|
|
470
|
-
return [accountEntryOutput(accountEntry)];
|
|
319
|
+
const addresses = await addressesForQuery(parameters, value);
|
|
320
|
+
if (!addresses) {
|
|
321
|
+
return { method: 'GET', path: 'accounts/entries/stream', maxItems: maxItems ?? limits.maxItems, timeoutMs: timeoutMs ?? limits.timeoutMs };
|
|
471
322
|
}
|
|
472
|
-
const addresses = mode === 'credentials' ? [await addressForCredentials(credentials)] : mode === 'manual' ? parseAddressList(parameters.addresses ?? parameters.address, 'Address') : undefined;
|
|
473
|
-
const fromHeight = parseOptionalHeight(parameters.fromHeight, 'From Height');
|
|
474
|
-
const toHeight = parseOptionalHeight(parameters.toHeight, 'To Height');
|
|
475
|
-
const options = streamOptions(parameters);
|
|
476
|
-
const accountEntries = await collectStream((onItem, onCancel) => subscribeToAccountEntryAddresses(node, addresses, fromHeight, toHeight, parameters, onItem, onCancel), options);
|
|
477
|
-
return accountEntries.map((accountEntry) => accountEntryOutput(accountEntry));
|
|
478
|
-
}
|
|
479
|
-
function subscribeToTransactionAddresses(node, addresses, fromHeight, toHeight, parameters, onItem, onCancel) {
|
|
480
|
-
if (!addresses)
|
|
481
|
-
return node.onTransactionAll(onItem, onCancel);
|
|
482
323
|
if (addresses.length === 1) {
|
|
483
|
-
return
|
|
324
|
+
return {
|
|
325
|
+
method: 'GET',
|
|
326
|
+
path: `accounts/${addresses[0].publicKey}/entries/stream?${heightQuery(parameters)}`,
|
|
327
|
+
maxItems: maxItems ?? limits.maxItems,
|
|
328
|
+
timeoutMs: timeoutMs ?? limits.timeoutMs,
|
|
329
|
+
};
|
|
484
330
|
}
|
|
485
|
-
return
|
|
331
|
+
return {
|
|
332
|
+
method: 'POST',
|
|
333
|
+
path: 'accounts/entries/stream',
|
|
334
|
+
body: heightSearch(addresses, parameters),
|
|
335
|
+
maxItems: maxItems ?? limits.maxItems,
|
|
336
|
+
timeoutMs: timeoutMs ?? limits.timeoutMs,
|
|
337
|
+
};
|
|
486
338
|
}
|
|
487
|
-
function
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
339
|
+
async function accountByAddress(context, value, address) {
|
|
340
|
+
return (await requestJson(context, value, requireNodeUrl(value), 'GET', `accounts/${address.publicKey}`, {
|
|
341
|
+
allowNotFound: true,
|
|
342
|
+
timeoutMs: 3000,
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
async function nodeTimestamp(context, value) {
|
|
346
|
+
const localTimestamp = Date.now();
|
|
347
|
+
const response = (await requestJson(context, value, requireNodeUrl(value), 'GET', `instants/${encodeURIComponent(new Date(localTimestamp).toISOString())}`));
|
|
348
|
+
const difference = Number(response?.differenceMillis ?? 0);
|
|
349
|
+
if (!Number.isSafeInteger(difference))
|
|
350
|
+
throw new Error('Atto node returned an invalid clock difference');
|
|
351
|
+
return Date.now() + difference;
|
|
352
|
+
}
|
|
353
|
+
async function workForBlock(context, value, block, timeoutMs) {
|
|
354
|
+
const response = (await requestJson(context, value, requireWorkerUrl(value), 'POST', 'works', {
|
|
355
|
+
body: {
|
|
356
|
+
network: block.network.name,
|
|
357
|
+
timestamp: Number(block.timestamp.toEpochMilliseconds()),
|
|
358
|
+
target: (0, protocol_1.workTarget)(block),
|
|
359
|
+
},
|
|
360
|
+
timeoutMs,
|
|
361
|
+
}));
|
|
362
|
+
const work = text(response?.work);
|
|
363
|
+
if (!work)
|
|
364
|
+
throw new Error('Atto worker response is missing work');
|
|
365
|
+
return work;
|
|
366
|
+
}
|
|
367
|
+
async function publishBlock(context, value, block, signer, timeoutMs) {
|
|
368
|
+
const transaction = await (0, protocol_1.signedTransaction)(block, signer, await workForBlock(context, value, block, timeoutMs));
|
|
369
|
+
const accepted = await requestStream(context, value, {
|
|
370
|
+
method: 'POST',
|
|
371
|
+
path: 'transactions/stream',
|
|
372
|
+
body: transaction.raw,
|
|
373
|
+
maxItems: 1,
|
|
374
|
+
timeoutMs,
|
|
375
|
+
});
|
|
376
|
+
if (accepted.length === 0)
|
|
377
|
+
throw new Error('Atto node did not acknowledge the transaction before timeout');
|
|
378
|
+
if ((0, protocol_1.blockHash)((accepted[0].block ?? {})) !== transaction.model.hash.toString()) {
|
|
379
|
+
throw new Error('Atto node acknowledged a different transaction');
|
|
492
380
|
}
|
|
493
|
-
return
|
|
381
|
+
return transaction.raw;
|
|
494
382
|
}
|
|
495
|
-
async function
|
|
496
|
-
const
|
|
383
|
+
async function receivableFromInput(context, value, parameters) {
|
|
384
|
+
const item = parameters.inputItem;
|
|
385
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
386
|
+
throw new Error('Input item must contain a receivable from Atto Trigger or Get Receivables');
|
|
387
|
+
}
|
|
388
|
+
const input = item;
|
|
389
|
+
const nested = input.receivable;
|
|
390
|
+
if (nested && typeof nested === 'object' && !Array.isArray(nested))
|
|
391
|
+
return nested;
|
|
392
|
+
if (input.network && input.receiverPublicKey && input.amount)
|
|
393
|
+
return input;
|
|
394
|
+
const hash = (0, protocol_1.parseHash)(input.hash, 'Input receivable hash');
|
|
395
|
+
const transaction = (await requestJson(context, value, requireNodeUrl(value), 'GET', `transactions/${hash}`));
|
|
396
|
+
const block = transaction.block;
|
|
397
|
+
if (!block || block.type !== 'SEND')
|
|
398
|
+
throw new Error('Input receivable hash does not identify an Atto send transaction');
|
|
399
|
+
return {
|
|
400
|
+
network: block.network,
|
|
401
|
+
hash,
|
|
402
|
+
version: block.version,
|
|
403
|
+
algorithm: block.algorithm,
|
|
404
|
+
publicKey: block.publicKey,
|
|
405
|
+
timestamp: block.timestamp,
|
|
406
|
+
receiverAlgorithm: block.receiverAlgorithm,
|
|
407
|
+
receiverPublicKey: block.receiverPublicKey,
|
|
408
|
+
amount: block.amount,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
async function executeAttoOperation(context, operation, parameters, credentialData) {
|
|
412
|
+
const value = credentials(credentialData);
|
|
413
|
+
const simplify = simplifyOutput(parameters);
|
|
497
414
|
if (operation === 'deriveAddress' || operation === 'deriveAccount') {
|
|
498
|
-
const derived = await deriveAddressFromSecret(parameters,
|
|
499
|
-
return {
|
|
500
|
-
address: derived.address.value,
|
|
501
|
-
publicKey: derived.publicKey.toString(),
|
|
502
|
-
keyIndex: derived.keyIndex,
|
|
503
|
-
secretType: derived.secretType,
|
|
504
|
-
};
|
|
415
|
+
const derived = await (0, protocol_1.deriveAddressFromSecret)(parameters, value);
|
|
416
|
+
return { address: derived.value, publicKey: derived.publicKey, keyIndex: derived.keyIndex, secretType: derived.secretType };
|
|
505
417
|
}
|
|
418
|
+
const execution = contextRequired(context);
|
|
506
419
|
if (operation === 'getAccount') {
|
|
507
|
-
const
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
if (!account) {
|
|
511
|
-
return {
|
|
512
|
-
found: false,
|
|
513
|
-
address: address.value,
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
return accountOutput(account);
|
|
420
|
+
const address = (0, protocol_1.parseAddress)(parameters.address ?? parameters.lookupAddress, 'Address');
|
|
421
|
+
const account = await accountByAddress(execution, value, address);
|
|
422
|
+
return account ? (0, protocol_1.accountOutput)(account, simplify) : { found: false, address: address.value };
|
|
517
423
|
}
|
|
518
424
|
if (operation === 'getReceivables') {
|
|
519
|
-
|
|
520
|
-
return await collectReceivables(node, parameters, attoCredentials);
|
|
425
|
+
return (await requestStream(execution, value, await receivableStreamRequest(parameters, value))).map((item) => (0, protocol_1.receivableOutput)(item, simplify));
|
|
521
426
|
}
|
|
522
427
|
if (operation === 'getTransactions') {
|
|
523
|
-
|
|
524
|
-
|
|
428
|
+
if (queryMode(parameters.queryMode) === 'hash') {
|
|
429
|
+
const hash = (0, protocol_1.parseHash)(parameters.hash, 'Hash');
|
|
430
|
+
const transaction = (await requestJson(execution, value, requireNodeUrl(value), 'GET', `transactions/${hash}`));
|
|
431
|
+
return [(0, protocol_1.transactionOutput)(transaction, simplify)];
|
|
432
|
+
}
|
|
433
|
+
return (await requestStream(execution, value, await transactionStreamRequest(parameters, value))).map((item) => (0, protocol_1.transactionOutput)(item, simplify));
|
|
525
434
|
}
|
|
526
435
|
if (operation === 'getAccountEntries') {
|
|
527
|
-
|
|
528
|
-
return await collectAccountEntries(node, parameters, attoCredentials);
|
|
436
|
+
return (await requestStream(execution, value, await accountEntryStreamRequest(parameters, value))).map((item) => (0, protocol_1.accountEntryOutput)(item, simplify));
|
|
529
437
|
}
|
|
438
|
+
const derived = await (0, protocol_1.deriveAddressFromSecret)(parameters, value);
|
|
439
|
+
const account = await accountByAddress(execution, value, derived);
|
|
440
|
+
const timestamp = await nodeTimestamp(execution, value);
|
|
441
|
+
const timeoutMs = positiveInteger(parameters.timeoutMs ?? DEFAULT_PUBLISH_TIMEOUT_MS, 'Timeout');
|
|
530
442
|
if (operation === 'sendTransaction') {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
const transaction = await
|
|
443
|
+
if (!account)
|
|
444
|
+
throw new Error('The wallet account is not open yet');
|
|
445
|
+
const destination = (0, protocol_1.parseAddress)(parameters.destinationAddress, 'Destination Address');
|
|
446
|
+
const amount = (0, protocol_1.parseAmount)(parameters.amount, parameters.amountUnit, 'Amount');
|
|
447
|
+
const block = (0, protocol_1.createSendBlock)(account, destination, amount, timestamp);
|
|
448
|
+
const transaction = await publishBlock(execution, value, block, derived.signer, timeoutMs);
|
|
537
449
|
return {
|
|
538
|
-
...transactionOutput(transaction, 'published'),
|
|
539
|
-
fromAddress:
|
|
540
|
-
destinationAddress:
|
|
541
|
-
amount: amountOutput(amount),
|
|
450
|
+
...(0, protocol_1.transactionOutput)(transaction, true, 'published'),
|
|
451
|
+
fromAddress: derived.value,
|
|
452
|
+
destinationAddress: destination.value,
|
|
453
|
+
amount: (0, protocol_1.amountOutput)(amount),
|
|
542
454
|
};
|
|
543
455
|
}
|
|
544
456
|
if (operation === 'receivePending') {
|
|
545
|
-
const
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const
|
|
457
|
+
const receivable = await receivableFromInput(execution, value, parameters);
|
|
458
|
+
const receiver = (0, protocol_1.addressFromPublicKey)(text(receivable.receiverPublicKey));
|
|
459
|
+
if (receiver.value !== derived.value)
|
|
460
|
+
throw new Error('Receivable Address must match the address derived from the wallet secret');
|
|
461
|
+
const representative = optionalText(parameters.representativeAddress)
|
|
462
|
+
? (0, protocol_1.parseAddress)(parameters.representativeAddress, 'Representative Address')
|
|
463
|
+
: derived;
|
|
464
|
+
const block = (0, protocol_1.createReceiveBlock)(account, receivable, representative, timestamp);
|
|
465
|
+
const transaction = await publishBlock(execution, value, block, derived.signer, timeoutMs);
|
|
553
466
|
return {
|
|
554
|
-
...transactionOutput(transaction, 'received'),
|
|
555
|
-
address:
|
|
467
|
+
...(0, protocol_1.transactionOutput)(transaction, true, 'received'),
|
|
468
|
+
address: derived.value,
|
|
556
469
|
representativeAddress: representative.value,
|
|
557
|
-
amount: amountOutput(receivable.amount),
|
|
558
|
-
receivable: parseJsonObject((0, commons_node_1.receivableToJson)(receivable)),
|
|
470
|
+
amount: (0, protocol_1.amountOutput)(receivable.amount),
|
|
559
471
|
};
|
|
560
472
|
}
|
|
561
473
|
if (operation === 'changeRepresentative') {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
const
|
|
474
|
+
if (!account)
|
|
475
|
+
throw new Error('The wallet account is not open yet');
|
|
476
|
+
const representative = (0, protocol_1.parseAddress)(parameters.representativeAddress, 'Representative Address');
|
|
477
|
+
const block = (0, protocol_1.createChangeBlock)(account, representative, timestamp);
|
|
478
|
+
const transaction = await publishBlock(execution, value, block, derived.signer, timeoutMs);
|
|
566
479
|
return {
|
|
567
|
-
...transactionOutput(transaction, 'representative_changed'),
|
|
568
|
-
address:
|
|
569
|
-
representativeAddress:
|
|
480
|
+
...(0, protocol_1.transactionOutput)(transaction, true, 'representative_changed'),
|
|
481
|
+
address: derived.value,
|
|
482
|
+
representativeAddress: representative.value,
|
|
570
483
|
};
|
|
571
484
|
}
|
|
572
485
|
throw new Error(`Unsupported Atto operation: ${operation}`);
|
|
573
486
|
}
|
|
574
|
-
async function
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
};
|
|
487
|
+
async function pollAccounts(context, parameters, value) {
|
|
488
|
+
const addresses = await addressesForSource(parameters, value, true);
|
|
489
|
+
if (addresses?.length === 1) {
|
|
490
|
+
const account = await accountByAddress(context, value, addresses[0]);
|
|
491
|
+
return account ? [(0, protocol_1.accountOutput)(account, true)] : [];
|
|
492
|
+
}
|
|
493
|
+
return (await requestStream(context, value, {
|
|
494
|
+
method: addresses ? 'POST' : 'GET',
|
|
495
|
+
path: 'accounts/stream',
|
|
496
|
+
...(addresses ? { body: { addresses: addresses.map((address) => address.value) } } : {}),
|
|
497
|
+
maxItems: POLL_MAX_ITEMS,
|
|
498
|
+
timeoutMs: POLL_TIMEOUT_MS,
|
|
499
|
+
})).map((item) => (0, protocol_1.accountOutput)(item, true));
|
|
500
|
+
}
|
|
501
|
+
function pollItemKey(event, item) {
|
|
502
|
+
if (event === 'account')
|
|
503
|
+
return `${String(item.address)}:${String(item.frontier)}:${String(item.balance?.raw ?? '')}`;
|
|
504
|
+
return String(item.hash ?? JSON.stringify(item));
|
|
505
|
+
}
|
|
506
|
+
function newPollItems(context, event, items) {
|
|
507
|
+
const staticData = context.getWorkflowStaticData('node');
|
|
508
|
+
const stateKey = `seen_${event}`;
|
|
509
|
+
const existing = Array.isArray(staticData[stateKey]) ? staticData[stateKey].map(String) : [];
|
|
510
|
+
const seen = new Set(existing);
|
|
511
|
+
const fresh = items.filter((item) => !seen.has(pollItemKey(event, item)));
|
|
512
|
+
const updated = [...existing, ...items.map((item) => pollItemKey(event, item))].slice(-MAX_SEEN_POLL_ITEMS);
|
|
513
|
+
staticData[stateKey] = updated;
|
|
514
|
+
if (existing.length === 0 && context.getMode() !== 'manual')
|
|
515
|
+
return [];
|
|
516
|
+
return fresh;
|
|
517
|
+
}
|
|
518
|
+
async function pollAttoEvent(context, event, parameters, credentialData) {
|
|
519
|
+
const value = credentials(credentialData);
|
|
520
|
+
let items;
|
|
587
521
|
if (event === 'receivable') {
|
|
588
|
-
|
|
589
|
-
const minAmount = parseOptionalAmount(parameters.minAmount, parameters.minAmountUnit ?? 'RAW', 'Minimum Amount');
|
|
590
|
-
const job = node.onReceivableByAddresses(addresses, minAmount, (receivable) => emit(receivableOutput(receivable)), onCancel);
|
|
591
|
-
return { close: () => closeJob(job) };
|
|
522
|
+
items = (await requestStream(context, value, await receivableStreamRequest(parameters, value, POLL_MAX_ITEMS, POLL_TIMEOUT_MS))).map((item) => (0, protocol_1.receivableOutput)(item, true));
|
|
592
523
|
}
|
|
593
|
-
if (event === 'account') {
|
|
594
|
-
|
|
595
|
-
const job = addresses
|
|
596
|
-
? node.onAccountByAddresses(addresses, (account) => emit(accountOutput(account)), onCancel)
|
|
597
|
-
: node.onAccountAll((account) => emit(accountOutput(account)), onCancel);
|
|
598
|
-
return { close: () => closeJob(job) };
|
|
524
|
+
else if (event === 'account') {
|
|
525
|
+
items = await pollAccounts(context, parameters, value);
|
|
599
526
|
}
|
|
600
|
-
if (event === 'transaction') {
|
|
601
|
-
const mode =
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
return { close: () => closeJob(job) };
|
|
527
|
+
else if (event === 'transaction') {
|
|
528
|
+
const mode = queryMode(parameters.queryMode);
|
|
529
|
+
if (mode === 'hash') {
|
|
530
|
+
const transaction = (await requestJson(context, value, requireNodeUrl(value), 'GET', `transactions/${(0, protocol_1.parseHash)(parameters.hash, 'Hash')}`));
|
|
531
|
+
items = [(0, protocol_1.transactionOutput)(transaction, true)];
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
items = (await requestStream(context, value, await transactionStreamRequest(parameters, value, POLL_MAX_ITEMS, POLL_TIMEOUT_MS))).map((item) => (0, protocol_1.transactionOutput)(item, true));
|
|
535
|
+
}
|
|
610
536
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const hash = mode === 'hash' ? parseHash(parameters.hash, 'Hash') : undefined;
|
|
614
|
-
const addresses = mode === 'credentials' ? [await addressForCredentials(attoCredentials)] : mode === 'manual' ? parseAddressList(parameters.addresses ?? parameters.address, 'Address') : undefined;
|
|
615
|
-
const fromHeight = parseOptionalHeight(parameters.fromHeight, 'From Height');
|
|
616
|
-
const toHeight = parseOptionalHeight(parameters.toHeight, 'To Height');
|
|
617
|
-
const job = hash
|
|
618
|
-
? node.onAccountEntryByHash(hash, (accountEntry) => emit(accountEntryOutput(accountEntry)), onCancel)
|
|
619
|
-
: subscribeToAccountEntryAddresses(node, addresses, fromHeight, toHeight, parameters, (accountEntry) => emit(accountEntryOutput(accountEntry)), onCancel);
|
|
620
|
-
return { close: () => closeJob(job) };
|
|
537
|
+
else {
|
|
538
|
+
items = (await requestStream(context, value, await accountEntryStreamRequest(parameters, value, POLL_MAX_ITEMS, POLL_TIMEOUT_MS))).map((item) => (0, protocol_1.accountEntryOutput)(item, true));
|
|
621
539
|
}
|
|
622
|
-
|
|
540
|
+
return newPollItems(context, event, items);
|
|
623
541
|
}
|
|
624
542
|
//# sourceMappingURL=operations.js.map
|