@radix-effects/tx-tool 0.0.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/dist/chunk-Bp6m_JJh.js +13 -0
- package/dist/index.d.ts +593 -0
- package/dist/index.js +900 -0
- package/package.json +42 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
|
+
import { Amount, Base64String, Epoch, FungibleResourceAddress, HexString, NetworkId, Nonce, TransactionId, TransactionManifestString, TransactionMessageString } from "@radix-effects/shared";
|
|
3
|
+
import { Convert, PrivateKey, PublicKey, RadixEngineToolkit, Signature, SignatureWithPublicKey, TransactionBuilder, generateRandomNonce } from "@radixdlt/radix-engine-toolkit";
|
|
4
|
+
import { Array, Cause, Config, Context, Data, Duration, Effect, Fiber, Layer, Option, ParseResult, Record, Redacted, Schedule, Schema, String, TestClock, flow, pipe } from "effect";
|
|
5
|
+
import { FileSystem, HttpBody, HttpClient } from "@effect/platform";
|
|
6
|
+
import { NodeFileSystem, NodeHttpClient } from "@effect/platform-node";
|
|
7
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
8
|
+
import { hexToBytes } from "@noble/hashes/utils.js";
|
|
9
|
+
import { AccountAddress as AccountAddress$1 } from "shared/brandedTypes";
|
|
10
|
+
import { GatewayApiClient, GetFungibleBalance, GetLedgerStateService } from "@radix-effects/gateway";
|
|
11
|
+
|
|
12
|
+
//#region src/manifests/addFeePayer.ts
|
|
13
|
+
const addFeePayer = (input) => {
|
|
14
|
+
if (input.account.type === "unsecurifiedAccount") return TransactionManifestString.make(`
|
|
15
|
+
CALL_METHOD
|
|
16
|
+
Address("${input.account.address}")
|
|
17
|
+
"lock_fee"
|
|
18
|
+
Decimal("${input.amount}")
|
|
19
|
+
;
|
|
20
|
+
`);
|
|
21
|
+
return TransactionManifestString.make(`
|
|
22
|
+
CALL_METHOD
|
|
23
|
+
Address("${input.account.accessControllerAddress}")
|
|
24
|
+
"create_proof"
|
|
25
|
+
;
|
|
26
|
+
|
|
27
|
+
CALL_METHOD
|
|
28
|
+
Address("${input.account.address}")
|
|
29
|
+
"lock_fee"
|
|
30
|
+
Decimal("${input.amount}")
|
|
31
|
+
;
|
|
32
|
+
`);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/manifests/createBadge.ts
|
|
37
|
+
const createBadge = (account, initialSupply = 1) => TransactionManifestString.make(`
|
|
38
|
+
CREATE_FUNGIBLE_RESOURCE_WITH_INITIAL_SUPPLY
|
|
39
|
+
Enum<0u8>()
|
|
40
|
+
true
|
|
41
|
+
0u8
|
|
42
|
+
Decimal("${initialSupply}")
|
|
43
|
+
Tuple(
|
|
44
|
+
Enum<1u8>(
|
|
45
|
+
Tuple(
|
|
46
|
+
Enum<1u8>(
|
|
47
|
+
Enum<1u8>()
|
|
48
|
+
),
|
|
49
|
+
Enum<1u8>(
|
|
50
|
+
Enum<1u8>()
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
),
|
|
54
|
+
Enum<1u8>(
|
|
55
|
+
Tuple(
|
|
56
|
+
Enum<1u8>(
|
|
57
|
+
Enum<1u8>()
|
|
58
|
+
),
|
|
59
|
+
Enum<1u8>(
|
|
60
|
+
Enum<1u8>()
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
),
|
|
64
|
+
Enum<1u8>(
|
|
65
|
+
Tuple(
|
|
66
|
+
Enum<1u8>(
|
|
67
|
+
Enum<1u8>()
|
|
68
|
+
),
|
|
69
|
+
Enum<1u8>(
|
|
70
|
+
Enum<1u8>()
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
),
|
|
74
|
+
Enum<1u8>(
|
|
75
|
+
Tuple(
|
|
76
|
+
Enum<1u8>(
|
|
77
|
+
Enum<1u8>()
|
|
78
|
+
),
|
|
79
|
+
Enum<1u8>(
|
|
80
|
+
Enum<1u8>()
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
),
|
|
84
|
+
Enum<1u8>(
|
|
85
|
+
Tuple(
|
|
86
|
+
Enum<1u8>(
|
|
87
|
+
Enum<0u8>()
|
|
88
|
+
),
|
|
89
|
+
Enum<1u8>(
|
|
90
|
+
Enum<1u8>()
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
),
|
|
94
|
+
Enum<1u8>(
|
|
95
|
+
Tuple(
|
|
96
|
+
Enum<1u8>(
|
|
97
|
+
Enum<0u8>()
|
|
98
|
+
),
|
|
99
|
+
Enum<1u8>(
|
|
100
|
+
Enum<1u8>()
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
Tuple(
|
|
106
|
+
Map<String, Tuple>(
|
|
107
|
+
"name" => Tuple(
|
|
108
|
+
Enum<1u8>(
|
|
109
|
+
Enum<0u8>(
|
|
110
|
+
"Badge"
|
|
111
|
+
)
|
|
112
|
+
),
|
|
113
|
+
false
|
|
114
|
+
)
|
|
115
|
+
),
|
|
116
|
+
Map<String, Enum>(
|
|
117
|
+
"metadata_setter" => Enum<0u8>(),
|
|
118
|
+
"metadata_setter_updater" => Enum<0u8>(),
|
|
119
|
+
"metadata_locker" => Enum<0u8>(),
|
|
120
|
+
"metadata_locker_updater" => Enum<0u8>()
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
Enum<0u8>()
|
|
124
|
+
;
|
|
125
|
+
|
|
126
|
+
CALL_METHOD
|
|
127
|
+
Address("${account.address}")
|
|
128
|
+
"try_deposit_batch_or_abort"
|
|
129
|
+
Expression("ENTIRE_WORKTOP")
|
|
130
|
+
Enum<0u8>()
|
|
131
|
+
;`);
|
|
132
|
+
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/manifests/createFungibleToken.ts
|
|
135
|
+
const createFungibleTokenManifest = (input) => TransactionManifestString.make(`CREATE_FUNGIBLE_RESOURCE_WITH_INITIAL_SUPPLY
|
|
136
|
+
None
|
|
137
|
+
true
|
|
138
|
+
0u8
|
|
139
|
+
Decimal("${input.initialSupply}")
|
|
140
|
+
Tuple(
|
|
141
|
+
Some(
|
|
142
|
+
Tuple(
|
|
143
|
+
Some(Enum<AccessRule::DenyAll>()),
|
|
144
|
+
Some(Enum<AccessRule::DenyAll>())
|
|
145
|
+
)
|
|
146
|
+
),
|
|
147
|
+
Some(
|
|
148
|
+
Tuple(
|
|
149
|
+
Some(Enum<AccessRule::DenyAll>()),
|
|
150
|
+
Some(Enum<AccessRule::DenyAll>())
|
|
151
|
+
)
|
|
152
|
+
),
|
|
153
|
+
Some(
|
|
154
|
+
Tuple(
|
|
155
|
+
Some(Enum<AccessRule::DenyAll>()),
|
|
156
|
+
Some(Enum<AccessRule::DenyAll>())
|
|
157
|
+
)
|
|
158
|
+
),
|
|
159
|
+
Some(
|
|
160
|
+
Tuple(
|
|
161
|
+
Some(Enum<AccessRule::DenyAll>()),
|
|
162
|
+
Some(Enum<AccessRule::DenyAll>())
|
|
163
|
+
)
|
|
164
|
+
),
|
|
165
|
+
Some(
|
|
166
|
+
Tuple(
|
|
167
|
+
Some(Enum<AccessRule::AllowAll>()),
|
|
168
|
+
Some(Enum<AccessRule::DenyAll>())
|
|
169
|
+
)
|
|
170
|
+
),
|
|
171
|
+
Some(
|
|
172
|
+
Tuple(
|
|
173
|
+
Some(Enum<AccessRule::AllowAll>()),
|
|
174
|
+
Some(Enum<AccessRule::DenyAll>())
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
Tuple(
|
|
179
|
+
Map<String, Tuple>(
|
|
180
|
+
"name" => Tuple(
|
|
181
|
+
Some(Enum<Metadata::String>("${input.name}")),
|
|
182
|
+
false
|
|
183
|
+
),"symbol" => Tuple(
|
|
184
|
+
Some(Enum<Metadata::String>("${input.symbol}")),
|
|
185
|
+
false
|
|
186
|
+
)
|
|
187
|
+
),
|
|
188
|
+
Map<String, Enum>(
|
|
189
|
+
"metadata_setter" => None,
|
|
190
|
+
"metadata_setter_updater" => None,
|
|
191
|
+
"metadata_locker" => None,
|
|
192
|
+
"metadata_locker_updater" => None
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
None
|
|
196
|
+
;
|
|
197
|
+
|
|
198
|
+
CALL_METHOD
|
|
199
|
+
Address("${input.account.address}")
|
|
200
|
+
"try_deposit_batch_or_abort"
|
|
201
|
+
Expression("ENTIRE_WORKTOP")
|
|
202
|
+
Enum<0u8>()
|
|
203
|
+
;`);
|
|
204
|
+
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/manifests/faucet.ts
|
|
207
|
+
const faucet = (accountAddress) => Effect.gen(function* () {
|
|
208
|
+
const knownAddresses = yield* Effect.tryPromise(() => RadixEngineToolkit.Utils.knownAddresses(2));
|
|
209
|
+
return TransactionManifestString.make(`
|
|
210
|
+
CALL_METHOD
|
|
211
|
+
Address("${knownAddresses.componentAddresses.faucet}")
|
|
212
|
+
"lock_fee"
|
|
213
|
+
Decimal("10")
|
|
214
|
+
;
|
|
215
|
+
|
|
216
|
+
CALL_METHOD
|
|
217
|
+
Address("${knownAddresses.componentAddresses.faucet}")
|
|
218
|
+
"free"
|
|
219
|
+
;
|
|
220
|
+
|
|
221
|
+
CALL_METHOD
|
|
222
|
+
Address("${accountAddress}")
|
|
223
|
+
"try_deposit_batch_or_abort"
|
|
224
|
+
Expression("ENTIRE_WORKTOP")
|
|
225
|
+
Enum<0u8>()
|
|
226
|
+
;`);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region src/manifests/manifestHelper.ts
|
|
231
|
+
var ManifestHelper = class extends Effect.Service()("ManifestHelper", { effect: Effect.gen(function* () {
|
|
232
|
+
return { addFeePayer: (input) => Effect.gen(function* () {
|
|
233
|
+
if (input.account.type === "unsecurifiedAccount") return TransactionManifestString.make(`
|
|
234
|
+
CALL_METHOD
|
|
235
|
+
Address("${input.account.address}")
|
|
236
|
+
"lock_fee"
|
|
237
|
+
Decimal("${input.amount}")
|
|
238
|
+
;
|
|
239
|
+
`);
|
|
240
|
+
return TransactionManifestString.make(`
|
|
241
|
+
CALL_METHOD
|
|
242
|
+
Address("${input.account.accessControllerAddress}")
|
|
243
|
+
"create_proof"
|
|
244
|
+
;
|
|
245
|
+
|
|
246
|
+
CALL_METHOD
|
|
247
|
+
Address("${input.account.address}")
|
|
248
|
+
"lock_fee"
|
|
249
|
+
Decimal("${input.amount}")
|
|
250
|
+
;
|
|
251
|
+
`);
|
|
252
|
+
}) };
|
|
253
|
+
}) }) {};
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/schemas.ts
|
|
257
|
+
var schemas_exports = /* @__PURE__ */ __export({
|
|
258
|
+
BadgeDecodedSchema: () => BadgeDecodedSchema,
|
|
259
|
+
BadgeSchema: () => BadgeSchema,
|
|
260
|
+
Base64FromHexSchema: () => Base64FromHexSchema,
|
|
261
|
+
Ed25519PrivateKeySchema: () => Ed25519PrivateKeySchema,
|
|
262
|
+
Ed25519PublicKeySchema: () => Ed25519PublicKeySchema,
|
|
263
|
+
Ed25519SignatureWithPublicKeySchema: () => Ed25519SignatureWithPublicKeySchema,
|
|
264
|
+
HexFromBase64Schema: () => HexFromBase64Schema,
|
|
265
|
+
ManifestSchema: () => ManifestSchema,
|
|
266
|
+
TransactionHeaderSchema: () => TransactionHeaderSchema,
|
|
267
|
+
TransactionIntentSchema: () => TransactionIntentSchema,
|
|
268
|
+
TransactionMessageSchema: () => TransactionMessageSchema
|
|
269
|
+
});
|
|
270
|
+
const Base64FromHexSchema = Schema.asSchema(Schema.transformOrFail(HexString, Base64String, {
|
|
271
|
+
decode: (hex) => Effect.succeed(Base64String.make(Buffer.from(hex, "hex").toString("base64"))),
|
|
272
|
+
encode: (base64) => Effect.succeed(HexString.make(Buffer.from(base64, "base64").toString("hex")))
|
|
273
|
+
}));
|
|
274
|
+
const HexFromBase64Schema = Schema.asSchema(Schema.transformOrFail(Base64String, HexString, {
|
|
275
|
+
decode: (base64) => Effect.succeed(HexString.make(Buffer.from(base64, "base64").toString("hex"))),
|
|
276
|
+
encode: (hex) => Effect.succeed(Base64String.make(Buffer.from(hex, "hex").toString("base64")))
|
|
277
|
+
}));
|
|
278
|
+
const Ed25519PublicKeySchema = Schema.asSchema(Schema.transformOrFail(HexString, Schema.instanceOf(PublicKey.Ed25519), {
|
|
279
|
+
decode: (hex) => Effect.succeed(new PublicKey.Ed25519(hex)),
|
|
280
|
+
encode: (publicKey) => Effect.succeed(HexString.make(publicKey.hex()))
|
|
281
|
+
}));
|
|
282
|
+
const Ed25519PrivateKeySchema = Schema.asSchema(Schema.transformOrFail(HexString, Schema.instanceOf(PrivateKey.Ed25519), {
|
|
283
|
+
decode: (hex) => Effect.succeed(new PrivateKey.Ed25519(hex)),
|
|
284
|
+
encode: (publicKey) => Effect.succeed(HexString.make(Convert.Uint8Array.toHexString(publicKey.bytes)))
|
|
285
|
+
}));
|
|
286
|
+
const ManifestSchema = Schema.asSchema(Schema.transformOrFail(TransactionManifestString, Schema.Struct({
|
|
287
|
+
instructions: Schema.Struct({
|
|
288
|
+
kind: Schema.Literal("String"),
|
|
289
|
+
value: TransactionManifestString
|
|
290
|
+
}),
|
|
291
|
+
blobs: Schema.mutable(Schema.Array(Schema.Uint8Array))
|
|
292
|
+
}), {
|
|
293
|
+
decode: (value) => Effect.succeed({
|
|
294
|
+
instructions: {
|
|
295
|
+
kind: "String",
|
|
296
|
+
value
|
|
297
|
+
},
|
|
298
|
+
blobs: []
|
|
299
|
+
}),
|
|
300
|
+
encode: (input) => Effect.succeed(TransactionManifestString.make(input.instructions.value))
|
|
301
|
+
}));
|
|
302
|
+
const PlainTextMessageSchema = Schema.Struct({
|
|
303
|
+
kind: Schema.Literal("PlainText"),
|
|
304
|
+
value: Schema.Struct({
|
|
305
|
+
message: Schema.Struct({
|
|
306
|
+
kind: Schema.Literal("String"),
|
|
307
|
+
value: TransactionMessageString
|
|
308
|
+
}),
|
|
309
|
+
mimeType: Schema.Literal("text/plain")
|
|
310
|
+
})
|
|
311
|
+
});
|
|
312
|
+
const EmptyMessageSchema = Schema.Struct({ kind: Schema.Literal("None") });
|
|
313
|
+
const TransactionMessageSchema = Schema.asSchema(Schema.transformOrFail(Schema.OptionFromUndefinedOr(TransactionMessageString), Schema.Union(PlainTextMessageSchema, EmptyMessageSchema), {
|
|
314
|
+
decode: (value) => Option.match(value, {
|
|
315
|
+
onNone: () => Effect.succeed({ kind: "None" }),
|
|
316
|
+
onSome: (value$1) => Effect.succeed({
|
|
317
|
+
kind: "PlainText",
|
|
318
|
+
value: {
|
|
319
|
+
message: {
|
|
320
|
+
kind: "String",
|
|
321
|
+
value: value$1
|
|
322
|
+
},
|
|
323
|
+
mimeType: "text/plain"
|
|
324
|
+
}
|
|
325
|
+
})
|
|
326
|
+
}),
|
|
327
|
+
encode: (input) => Effect.succeed(input.kind === "None" ? Option.none() : Option.some(TransactionMessageString.make(input.value.message.value)))
|
|
328
|
+
}));
|
|
329
|
+
const TransactionHeaderSchema = Schema.Struct({
|
|
330
|
+
networkId: NetworkId,
|
|
331
|
+
startEpochInclusive: Epoch,
|
|
332
|
+
endEpochExclusive: Epoch,
|
|
333
|
+
notaryPublicKey: Ed25519PublicKeySchema,
|
|
334
|
+
nonce: Nonce,
|
|
335
|
+
notaryIsSignatory: Schema.Boolean,
|
|
336
|
+
tipPercentage: Schema.Number
|
|
337
|
+
});
|
|
338
|
+
const TransactionIntentSchema = Schema.Struct({
|
|
339
|
+
header: TransactionHeaderSchema,
|
|
340
|
+
message: TransactionMessageSchema,
|
|
341
|
+
manifest: ManifestSchema
|
|
342
|
+
});
|
|
343
|
+
const Ed25519SignatureWithPublicKeySchema = Schema.asSchema(Schema.transformOrFail(Schema.Struct({
|
|
344
|
+
signature: HexString,
|
|
345
|
+
signerPublicKey: HexString,
|
|
346
|
+
curve: Schema.Literal("Ed25519")
|
|
347
|
+
}), Schema.instanceOf(SignatureWithPublicKey.Ed25519), {
|
|
348
|
+
strict: false,
|
|
349
|
+
decode: (value) => Effect.succeed(new SignatureWithPublicKey.Ed25519(value.signature, value.signerPublicKey)),
|
|
350
|
+
encode: (input) => Effect.succeed({
|
|
351
|
+
signature: HexString.make(Convert.Uint8Array.toHexString(input.signature)),
|
|
352
|
+
signerPublicKey: HexString.make(Convert.Uint8Array.toHexString(input.publicKey))
|
|
353
|
+
})
|
|
354
|
+
}));
|
|
355
|
+
const BadgeDecodedSchema = Schema.Struct({
|
|
356
|
+
type: Schema.Literal("fungibleResource"),
|
|
357
|
+
resourceAddress: FungibleResourceAddress
|
|
358
|
+
});
|
|
359
|
+
const BadgeSchema = Schema.transformOrFail(Schema.Struct({
|
|
360
|
+
type: Schema.String,
|
|
361
|
+
resourceAddress: Schema.String
|
|
362
|
+
}), BadgeDecodedSchema, {
|
|
363
|
+
strict: false,
|
|
364
|
+
decode: (value) => Effect.succeed(Schema.Struct({
|
|
365
|
+
type: Schema.Literal("fungibleResource"),
|
|
366
|
+
resourceAddress: FungibleResourceAddress
|
|
367
|
+
}).make({
|
|
368
|
+
type: "fungibleResource",
|
|
369
|
+
resourceAddress: FungibleResourceAddress.make(value.resourceAddress)
|
|
370
|
+
})),
|
|
371
|
+
encode: (value) => Effect.succeed(value)
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region src/signer/vault.ts
|
|
376
|
+
const SignResponseSchema = Schema.asSchema(Schema.transformOrFail(Schema.Struct({ data: Schema.Struct({ signature: Schema.String }) }), HexString, {
|
|
377
|
+
strict: true,
|
|
378
|
+
decode: (input) => pipe(input.data.signature, String.split(":"), Array.last, Option.getOrThrow, Base64String.make, Schema.decode(HexFromBase64Schema), Effect.catchTag("ParseError", (error) => ParseResult.fail(error.issue))),
|
|
379
|
+
encode: (value, _, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, value, "Encoding signatures back to response format is forbidden."))
|
|
380
|
+
}));
|
|
381
|
+
const PublicKeyResponseSchema = Schema.asSchema(Schema.transformOrFail(Schema.Struct({ data: Schema.Struct({ keys: Schema.Record({
|
|
382
|
+
key: Schema.String,
|
|
383
|
+
value: Schema.Struct({ public_key: Base64String })
|
|
384
|
+
}) }) }), HexString, {
|
|
385
|
+
strict: true,
|
|
386
|
+
decode: (input) => pipe(input.data.keys, (keys) => Record.values(keys), Array.head, Option.map((item) => item.public_key), Option.getOrThrow, Schema.decode(HexFromBase64Schema), Effect.catchTag("ParseError", (error) => ParseResult.fail(error.issue))),
|
|
387
|
+
encode: (value, _, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, value, "Encoding public keys back to response format is forbidden."))
|
|
388
|
+
}));
|
|
389
|
+
/**
|
|
390
|
+
* Reads the Vault token from a file or falls back to environment variable.
|
|
391
|
+
* In production, Vault Agent writes the token to a file and handles renewal.
|
|
392
|
+
* For local development, you can use either VAULT_TOKEN_FILE or VAULT_TOKEN.
|
|
393
|
+
*/
|
|
394
|
+
const getVaultToken = (fs) => Effect.gen(function* () {
|
|
395
|
+
const tokenFilePath = yield* Config.string("VAULT_TOKEN_FILE").pipe(Config.option, Effect.map(Option.getOrUndefined));
|
|
396
|
+
if (tokenFilePath) return (yield* fs.readFileString(tokenFilePath)).trim();
|
|
397
|
+
return yield* Config.string("VAULT_TOKEN");
|
|
398
|
+
});
|
|
399
|
+
const NodeHttpClientLive = NodeHttpClient.layerWithoutAgent.pipe(Layer.provide(NodeHttpClient.makeAgentLayer({ rejectUnauthorized: false })));
|
|
400
|
+
var Vault = class extends Effect.Service()("Vault", {
|
|
401
|
+
dependencies: [NodeHttpClientLive, NodeFileSystem.layer],
|
|
402
|
+
effect: Effect.gen(function* () {
|
|
403
|
+
const keyName = yield* Config.string("VAULT_KEY_NAME").pipe(Config.withDefault("xrd-distribution"));
|
|
404
|
+
const baseUrl = yield* Config.string("VAULT_BASE_URL").pipe(Config.withDefault("http://localhost:8200"), Effect.orDie);
|
|
405
|
+
const httpClient = (yield* HttpClient.HttpClient).pipe(HttpClient.retryTransient({
|
|
406
|
+
schedule: Schedule.exponential(Duration.millis(100)),
|
|
407
|
+
times: 3
|
|
408
|
+
}), HttpClient.tapError((error) => Effect.logError("Vault request error", { error })), HttpClient.tap((response) => response.status >= 200 && response.status < 300 ? Effect.void : response.json.pipe(Effect.tap((body) => Effect.logError("Vault request failed", {
|
|
409
|
+
status: response.status,
|
|
410
|
+
url: response.request.url,
|
|
411
|
+
body
|
|
412
|
+
})), Effect.ignore)), HttpClient.filterStatusOk);
|
|
413
|
+
const fs = yield* FileSystem.FileSystem;
|
|
414
|
+
const getPublicKey = () => Effect.gen(function* () {
|
|
415
|
+
const token = yield* getVaultToken(fs);
|
|
416
|
+
return yield* httpClient.get(`${baseUrl}/v1/transit/keys/${keyName}`, { headers: { "X-Vault-Token": token } }).pipe(Effect.tapError(Effect.logError), Effect.flatMap((response) => response.json), Effect.flatMap(Schema.decodeUnknown(PublicKeyResponseSchema)));
|
|
417
|
+
}).pipe(Effect.orDie);
|
|
418
|
+
const toSignatureWithPublicKey = (hash) => Effect.gen(function* () {
|
|
419
|
+
const token = yield* getVaultToken(fs);
|
|
420
|
+
const encoded = {
|
|
421
|
+
signature: yield* httpClient.post(`${baseUrl}/v1/transit/sign/${keyName}`, {
|
|
422
|
+
headers: {
|
|
423
|
+
"X-Vault-Token": token,
|
|
424
|
+
"Content-Type": "application/json"
|
|
425
|
+
},
|
|
426
|
+
body: yield* Schema.decode(Base64FromHexSchema)(hash).pipe(Effect.flatMap((base64) => HttpBody.json({ input: base64 })))
|
|
427
|
+
}).pipe(Effect.flatMap((response) => response.json), Effect.tap((response) => Effect.log(response)), Effect.flatMap(Schema.decodeUnknown(SignResponseSchema))),
|
|
428
|
+
signerPublicKey: yield* getPublicKey(),
|
|
429
|
+
curve: "Ed25519"
|
|
430
|
+
};
|
|
431
|
+
return yield* Schema.decode(Ed25519SignatureWithPublicKeySchema)(encoded);
|
|
432
|
+
}).pipe(Effect.annotateLogs({
|
|
433
|
+
signer: "Vault",
|
|
434
|
+
keyName
|
|
435
|
+
}));
|
|
436
|
+
return {
|
|
437
|
+
getPublicKey,
|
|
438
|
+
toSignatureWithPublicKey
|
|
439
|
+
};
|
|
440
|
+
})
|
|
441
|
+
}) {};
|
|
442
|
+
|
|
443
|
+
//#endregion
|
|
444
|
+
//#region src/signer/signer.ts
|
|
445
|
+
var FailedToSignTransactionError = class extends Data.TaggedError("FailedToSignTransactionError") {};
|
|
446
|
+
var Signer = class Signer extends Context.Tag("Signer")() {
|
|
447
|
+
static VaultLive = Layer.effect(Signer, Effect.gen(function* () {
|
|
448
|
+
const vault = yield* Vault;
|
|
449
|
+
return {
|
|
450
|
+
signToSignatureWithPublicKey: (hash) => vault.toSignatureWithPublicKey(hash).pipe(Effect.map((signatureWithPublicKey) => [signatureWithPublicKey]), Effect.catchAll(Effect.die)),
|
|
451
|
+
publicKey: () => vault.getPublicKey().pipe(Effect.map((publicKey) => new PublicKey.Ed25519(publicKey)))
|
|
452
|
+
};
|
|
453
|
+
})).pipe(Layer.provide(Vault.Default));
|
|
454
|
+
static makePrivateKeySigner = (privateKey) => Layer.effect(Signer, Effect.gen(function* () {
|
|
455
|
+
return {
|
|
456
|
+
signToSignatureWithPublicKey: (hash) => Effect.gen(function* () {
|
|
457
|
+
const value = Redacted.value(privateKey);
|
|
458
|
+
const Ed25519PrivateKey = yield* Schema.decode(Ed25519PrivateKeySchema)(value);
|
|
459
|
+
return [{
|
|
460
|
+
curve: "Ed25519",
|
|
461
|
+
signature: Ed25519PrivateKey.signToSignatureWithPublicKey(Convert.HexString.toUint8Array(hash)).signature,
|
|
462
|
+
publicKey: Ed25519PrivateKey.publicKey().bytes
|
|
463
|
+
}];
|
|
464
|
+
}).pipe(Effect.orDie, Effect.annotateLogs({ signer: "PrivateKey" })),
|
|
465
|
+
publicKey: () => Effect.gen(function* () {
|
|
466
|
+
const value = Redacted.value(privateKey);
|
|
467
|
+
return (yield* Schema.decode(Ed25519PrivateKeySchema)(value)).publicKey();
|
|
468
|
+
}).pipe(Effect.orDie)
|
|
469
|
+
};
|
|
470
|
+
}));
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region src/test-helpers/createAccount.ts
|
|
475
|
+
const createAccount = (input) => Effect.gen(function* () {
|
|
476
|
+
const privateKey = input?.privateKey ? input.privateKey : ed25519.keygen().secretKey;
|
|
477
|
+
const publicKey = ed25519.getPublicKey(privateKey);
|
|
478
|
+
const publicKeyHex = Buffer.from(publicKey).toString("hex");
|
|
479
|
+
const keypair = new PrivateKey.Ed25519(privateKey);
|
|
480
|
+
const address = yield* Effect.tryPromise(() => RadixEngineToolkit.Derive.virtualAccountAddressFromPublicKey(keypair.publicKey(), input?.networkId ?? 1));
|
|
481
|
+
return {
|
|
482
|
+
address: AccountAddress$1.make(address),
|
|
483
|
+
sign: (hash) => {
|
|
484
|
+
const signature = ed25519.sign(hexToBytes(hash), privateKey);
|
|
485
|
+
return Buffer.from(signature).toString("hex");
|
|
486
|
+
},
|
|
487
|
+
publicKeyHex,
|
|
488
|
+
privateKeyHex: Buffer.from(privateKey).toString("hex")
|
|
489
|
+
};
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region src/test-helpers/disableTestClock.ts
|
|
494
|
+
const resolveFiber = (fiber) => Effect.gen(function* () {
|
|
495
|
+
while (Option.isNone(yield* Fiber.poll(fiber))) {
|
|
496
|
+
yield* Effect.promise(async () => new Promise((resolve) => setTimeout(resolve, 1e3)));
|
|
497
|
+
yield* TestClock.adjust(Duration.seconds(1));
|
|
498
|
+
}
|
|
499
|
+
return yield* Fiber.join(fiber);
|
|
500
|
+
});
|
|
501
|
+
const DisableTestClock = (effect) => Effect.gen(function* () {
|
|
502
|
+
return yield* resolveFiber(yield* effect.pipe(Effect.fork));
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region src/notaryKeyPair.ts
|
|
507
|
+
var NotaryKeyPair = class extends Effect.Service()("NotaryKeyPair", { effect: Effect.gen(function* () {
|
|
508
|
+
const signer = yield* Signer;
|
|
509
|
+
return {
|
|
510
|
+
publicKey: () => signer.publicKey(),
|
|
511
|
+
signToSignature: (hash) => signer.signToSignatureWithPublicKey(hash).pipe(Effect.map(flow(Array.head, Option.map((signature) => new Signature.Ed25519(signature.signature)), Option.getOrThrow)))
|
|
512
|
+
};
|
|
513
|
+
}) }) {};
|
|
514
|
+
|
|
515
|
+
//#endregion
|
|
516
|
+
//#region src/compileTransaction.ts
|
|
517
|
+
var FailedToCompileTransactionError = class extends Data.TaggedError("FailedToCompileTransactionError") {};
|
|
518
|
+
var FailedToNotarizeTransactionError = class extends Data.TaggedError("FailedToNotarizeTransactionError") {};
|
|
519
|
+
Schema.Struct({
|
|
520
|
+
intent: TransactionIntentSchema,
|
|
521
|
+
signatures: Schema.Array(Ed25519SignatureWithPublicKeySchema)
|
|
522
|
+
});
|
|
523
|
+
var CompileTransaction = class extends Effect.Service()("CompileTransaction", {
|
|
524
|
+
dependencies: [NotaryKeyPair.Default],
|
|
525
|
+
effect: Effect.gen(function* () {
|
|
526
|
+
const notaryKeyPair = yield* NotaryKeyPair;
|
|
527
|
+
const TransactionBuilder$1 = Effect.tryPromise(() => TransactionBuilder.new()).pipe(Effect.catchAll(Effect.orDie));
|
|
528
|
+
const notarySignToSignature = (hash) => pipe(Convert.Uint8Array.toHexString(hash), HexString.make, notaryKeyPair.signToSignature).pipe(Effect.runPromise);
|
|
529
|
+
const notarizeTransaction = (input) => Effect.gen(function* () {
|
|
530
|
+
const builder = yield* TransactionBuilder$1;
|
|
531
|
+
return yield* Effect.tryPromise({
|
|
532
|
+
try: () => pipe(builder, (builder$1) => builder$1.header(input.intent.header), (builder$1) => builder$1.message(input.intent.message), (builder$1) => builder$1.manifest(input.intent.manifest), (builder$1) => input.signatures.reduce((builder$2, signature) => builder$2.sign(signature), builder$1), (builder$1) => builder$1.notarizeAsync(notarySignToSignature)),
|
|
533
|
+
catch: (error) => new FailedToNotarizeTransactionError({ error })
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
const compileNotarizedTransaction = (input) => Effect.tryPromise({
|
|
537
|
+
try: () => RadixEngineToolkit.NotarizedTransaction.compile(input),
|
|
538
|
+
catch: (error) => new FailedToCompileTransactionError({ error })
|
|
539
|
+
});
|
|
540
|
+
return (input) => notarizeTransaction(input).pipe(Effect.flatMap(compileNotarizedTransaction));
|
|
541
|
+
})
|
|
542
|
+
}) {};
|
|
543
|
+
|
|
544
|
+
//#endregion
|
|
545
|
+
//#region src/staticallyValidateManifest.ts
|
|
546
|
+
var FailedToStaticallyValidateManifestError = class extends Data.TaggedError("FailedToStaticallyValidateManifestError") {};
|
|
547
|
+
var InvalidManifestError = class extends Data.TaggedError("InvalidManifestError") {};
|
|
548
|
+
var StaticallyValidateManifest = class extends Effect.Service()("StaticallyValidateManifest", { effect: Effect.gen(function* () {
|
|
549
|
+
return (input) => Effect.gen(function* () {
|
|
550
|
+
const result = yield* Effect.tryPromise({
|
|
551
|
+
try: () => RadixEngineToolkit.TransactionManifest.staticallyValidate(input.manifest, input.networkId),
|
|
552
|
+
catch: (error) => {
|
|
553
|
+
console.error(error);
|
|
554
|
+
return new FailedToStaticallyValidateManifestError({ error });
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
if (result.kind === "Invalid") return yield* Effect.fail(new InvalidManifestError({ message: result.error }));
|
|
558
|
+
});
|
|
559
|
+
}) }) {};
|
|
560
|
+
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/epoch.ts
|
|
563
|
+
var InvalidEndEpochError = class extends Data.TaggedError("InvalidEndEpochError") {};
|
|
564
|
+
var InvalidStartEpochError = class extends Data.TaggedError("InvalidStartEpochError") {};
|
|
565
|
+
var EpochService = class extends Effect.Service()("EpochService", {
|
|
566
|
+
dependencies: [GetLedgerStateService.Default],
|
|
567
|
+
effect: Effect.gen(function* () {
|
|
568
|
+
const getLedgerStateService = yield* GetLedgerStateService;
|
|
569
|
+
const getCurrentEpoch = () => Effect.gen(function* () {
|
|
570
|
+
return yield* getLedgerStateService({ at_ledger_state: { timestamp: /* @__PURE__ */ new Date() } }).pipe(Effect.map((ledgerState) => Epoch.make(ledgerState.epoch)));
|
|
571
|
+
});
|
|
572
|
+
return {
|
|
573
|
+
getCurrentEpoch,
|
|
574
|
+
verifyEpochBounds: (input) => Effect.gen(function* () {
|
|
575
|
+
const currentEpoch = yield* getCurrentEpoch();
|
|
576
|
+
if (currentEpoch < input.transactionIntent.header.startEpochInclusive) return yield* new InvalidStartEpochError({
|
|
577
|
+
message: `Current epoch ${currentEpoch} is less than start epoch ${input.transactionIntent.header.startEpochInclusive}`,
|
|
578
|
+
transactionId: input.transactionId
|
|
579
|
+
});
|
|
580
|
+
if (currentEpoch >= input.transactionIntent.header.endEpochExclusive) return yield* new InvalidEndEpochError({
|
|
581
|
+
message: `Current epoch ${currentEpoch} is greater than or equal to end epoch ${input.transactionIntent.header.endEpochExclusive}`,
|
|
582
|
+
transactionId: input.transactionId
|
|
583
|
+
});
|
|
584
|
+
})
|
|
585
|
+
};
|
|
586
|
+
})
|
|
587
|
+
}) {};
|
|
588
|
+
|
|
589
|
+
//#endregion
|
|
590
|
+
//#region src/transactionHeader.ts
|
|
591
|
+
var InvalidEpochError = class extends Data.TaggedError("InvalidEpochError") {};
|
|
592
|
+
var TransactionHeader = class extends Effect.Service()("TransactionHeader", {
|
|
593
|
+
dependencies: [
|
|
594
|
+
GetLedgerStateService.Default,
|
|
595
|
+
NotaryKeyPair.Default,
|
|
596
|
+
EpochService.Default
|
|
597
|
+
],
|
|
598
|
+
effect: Effect.gen(function* () {
|
|
599
|
+
const notaryKeyPair = yield* NotaryKeyPair;
|
|
600
|
+
const epochService = yield* EpochService;
|
|
601
|
+
const generateNonce = () => pipe(generateRandomNonce(), Nonce.make);
|
|
602
|
+
return (input) => Effect.gen(function* () {
|
|
603
|
+
const { tipPercentage = 0, nonce = generateNonce(), notaryIsSignatory = false } = input;
|
|
604
|
+
const currentEpoch = yield* epochService.getCurrentEpoch();
|
|
605
|
+
const startEpochInclusive = Option.match(input.startEpochInclusive, {
|
|
606
|
+
onSome: (epoch) => epoch,
|
|
607
|
+
onNone: () => currentEpoch
|
|
608
|
+
});
|
|
609
|
+
if (currentEpoch < startEpochInclusive) return yield* new InvalidEpochError({ message: `Current epoch ${currentEpoch} is less than start epoch ${input.startEpochInclusive}` });
|
|
610
|
+
const endEpochExclusive = Option.match(input.endEpochExclusive, {
|
|
611
|
+
onSome: (epoch) => epoch,
|
|
612
|
+
onNone: () => Epoch.make(currentEpoch + 2)
|
|
613
|
+
});
|
|
614
|
+
if (currentEpoch >= endEpochExclusive) return yield* new InvalidEpochError({ message: `Current epoch ${currentEpoch} is greater than or equal to end epoch ${input.endEpochExclusive}` });
|
|
615
|
+
const notaryPublicKey = yield* notaryKeyPair.publicKey();
|
|
616
|
+
return TransactionHeaderSchema.make({
|
|
617
|
+
networkId: input.networkId,
|
|
618
|
+
startEpochInclusive,
|
|
619
|
+
endEpochExclusive,
|
|
620
|
+
notaryPublicKey,
|
|
621
|
+
nonce,
|
|
622
|
+
notaryIsSignatory,
|
|
623
|
+
tipPercentage
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
})
|
|
627
|
+
}) {};
|
|
628
|
+
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region src/createTransactionIntent.ts
|
|
631
|
+
const CreateTransactionIntentInputSchema = Schema.Struct({
|
|
632
|
+
startEpochInclusive: Schema.optional(Epoch),
|
|
633
|
+
endEpochExclusive: Schema.optional(Epoch),
|
|
634
|
+
manifest: TransactionManifestString,
|
|
635
|
+
message: Schema.optional(TransactionMessageString),
|
|
636
|
+
tipPercentage: Schema.optional(Schema.Number)
|
|
637
|
+
});
|
|
638
|
+
var CreateTransactionIntent = class extends Effect.Service()("CreateTransactionIntent", {
|
|
639
|
+
dependencies: [StaticallyValidateManifest.Default, TransactionHeader.Default],
|
|
640
|
+
effect: Effect.gen(function* () {
|
|
641
|
+
const staticallyValidateManifest = yield* StaticallyValidateManifest;
|
|
642
|
+
const createTransactionHeader = yield* TransactionHeader;
|
|
643
|
+
const gatewayApiClient = yield* GatewayApiClient;
|
|
644
|
+
const networkId = NetworkId.make(gatewayApiClient.networkId);
|
|
645
|
+
return (input) => Effect.gen(function* () {
|
|
646
|
+
const parsedInput = yield* Schema.decodeUnknown(CreateTransactionIntentInputSchema)(input);
|
|
647
|
+
const header = yield* createTransactionHeader({
|
|
648
|
+
networkId,
|
|
649
|
+
startEpochInclusive: Option.fromNullable(parsedInput.startEpochInclusive),
|
|
650
|
+
endEpochExclusive: Option.fromNullable(parsedInput.endEpochExclusive)
|
|
651
|
+
});
|
|
652
|
+
const message = yield* Schema.decodeUnknown(TransactionMessageSchema)(parsedInput.message);
|
|
653
|
+
const manifest = yield* Schema.decodeUnknown(ManifestSchema)(parsedInput.manifest);
|
|
654
|
+
const transactionIntent = {
|
|
655
|
+
header,
|
|
656
|
+
message,
|
|
657
|
+
manifest
|
|
658
|
+
};
|
|
659
|
+
yield* staticallyValidateManifest({
|
|
660
|
+
manifest,
|
|
661
|
+
networkId
|
|
662
|
+
});
|
|
663
|
+
return TransactionIntentSchema.make(transactionIntent);
|
|
664
|
+
});
|
|
665
|
+
})
|
|
666
|
+
}) {};
|
|
667
|
+
|
|
668
|
+
//#endregion
|
|
669
|
+
//#region src/intentHash.ts
|
|
670
|
+
var FailedToCreateIntentHashError = class extends Data.TaggedError("FailedToCreateIntentHashError") {};
|
|
671
|
+
var IntentHashService = class extends Effect.Service()("IntentHashService", { effect: Effect.gen(function* () {
|
|
672
|
+
const createIntentHash = (input) => Effect.tryPromise({
|
|
673
|
+
try: () => RadixEngineToolkit.Intent.hash(input),
|
|
674
|
+
catch: (error) => new FailedToCreateIntentHashError({ error })
|
|
675
|
+
}).pipe(Effect.map((hash) => ({
|
|
676
|
+
id: TransactionId.make(hash.id),
|
|
677
|
+
hash: pipe(Convert.Uint8Array.toHexString(hash.hash), HexString.make)
|
|
678
|
+
})));
|
|
679
|
+
return { create: createIntentHash };
|
|
680
|
+
}) }) {};
|
|
681
|
+
|
|
682
|
+
//#endregion
|
|
683
|
+
//#region src/previewTransaction.ts
|
|
684
|
+
var TransactionPreviewError = class extends Data.TaggedError("TransactionPreviewError") {};
|
|
685
|
+
var PreviewTransaction = class extends Effect.Service()("PreviewTransaction", { effect: Effect.gen(function* () {
|
|
686
|
+
const gatewayApiClient = yield* GatewayApiClient;
|
|
687
|
+
return (input) => Effect.gen(function* () {
|
|
688
|
+
const result = yield* gatewayApiClient.transaction.innerClient.transactionPreview({ transactionPreviewRequest: input.payload });
|
|
689
|
+
const receipt = result.receipt;
|
|
690
|
+
if (receipt.status !== "Succeeded") return yield* new TransactionPreviewError({ message: receipt.error_message });
|
|
691
|
+
return result;
|
|
692
|
+
});
|
|
693
|
+
}) }) {};
|
|
694
|
+
|
|
695
|
+
//#endregion
|
|
696
|
+
//#region src/submitTransaction.ts
|
|
697
|
+
var SubmitTransaction = class extends Effect.Service()("SubmitTransaction", { effect: Effect.gen(function* () {
|
|
698
|
+
const gatewayApiClient = yield* GatewayApiClient;
|
|
699
|
+
return (input) => Effect.gen(function* () {
|
|
700
|
+
const notarizedTransactionHex = pipe(input.compiledTransaction, Convert.Uint8Array.toHexString, HexString.make);
|
|
701
|
+
return yield* gatewayApiClient.transaction.innerClient.transactionSubmit({ transactionSubmitRequest: { notarized_transaction_hex: notarizedTransactionHex } });
|
|
702
|
+
});
|
|
703
|
+
}) }) {};
|
|
704
|
+
|
|
705
|
+
//#endregion
|
|
706
|
+
//#region src/transactionStatus.ts
|
|
707
|
+
var TransactionNotResolvedError = class extends Data.TaggedError("TransactionNotResolvedError") {};
|
|
708
|
+
var TransactionFailedError = class extends Data.TaggedError("TransactionFailedError") {};
|
|
709
|
+
var TimeoutError = class extends Data.TaggedError("TimeoutError") {};
|
|
710
|
+
var TransactionStatus = class extends Effect.Service()("TransactionStatus", { effect: Effect.gen(function* () {
|
|
711
|
+
const pollTimeoutDuration = yield* Config.duration("TRANSACTION_STATUS_POLL_TIMEOUT").pipe(Config.withDefault(Duration.minutes(1)), Effect.orDie);
|
|
712
|
+
const maxPollAttempts = yield* Config.number("TRANSACTION_STATUS_MAX_POLL_ATTEMPTS_COUNT").pipe(Config.withDefault(10), Effect.orDie);
|
|
713
|
+
const pollDelay = yield* Config.duration("TRANSACTION_STATUS_POLL_DELAY").pipe(Config.withDefault(Duration.millis(100)), Effect.orDie);
|
|
714
|
+
const retryPolicy = Schedule.exponential(pollDelay).pipe(Schedule.compose(Schedule.recurs(maxPollAttempts)));
|
|
715
|
+
const gatewayApiClient = yield* GatewayApiClient;
|
|
716
|
+
const getTransactionStatus = (id) => Effect.gen(function* () {
|
|
717
|
+
const result = yield* gatewayApiClient.transaction.innerClient.transactionStatus({ transactionStatusRequest: { intent_hash: id } });
|
|
718
|
+
const { intent_status } = result;
|
|
719
|
+
if (intent_status === "CommittedSuccess") return result;
|
|
720
|
+
if (intent_status === "CommittedFailure" || intent_status === "PermanentlyRejected") return yield* new TransactionFailedError({
|
|
721
|
+
status: intent_status,
|
|
722
|
+
statusDescription: result.intent_status_description,
|
|
723
|
+
message: result.error_message,
|
|
724
|
+
transactionId: id
|
|
725
|
+
});
|
|
726
|
+
return yield* new TransactionNotResolvedError({
|
|
727
|
+
status: intent_status,
|
|
728
|
+
statusDescription: result.intent_status_description,
|
|
729
|
+
message: result.error_message,
|
|
730
|
+
transactionId: id
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
return { poll: (input) => getTransactionStatus(input.id).pipe(Effect.tapErrorTag("TransactionFailedError", Effect.logError), Effect.retry({
|
|
734
|
+
schedule: input.retryPolicy ?? retryPolicy,
|
|
735
|
+
while: (error) => error._tag === "TransactionNotResolvedError"
|
|
736
|
+
}), Effect.timeout(input.timeout ?? pollTimeoutDuration), Effect.catchTags({ TimeoutException: () => new TimeoutError({ transactionId: input.id }) })) };
|
|
737
|
+
}) }) {};
|
|
738
|
+
|
|
739
|
+
//#endregion
|
|
740
|
+
//#region src/transactionHelper.ts
|
|
741
|
+
var FaucetNotAvailableError = class extends Data.TaggedError("FaucetNotAvailableError") {};
|
|
742
|
+
var TransactionLifeCycleHook = class extends Context.Tag("TransactionLifeCycleHook")() {};
|
|
743
|
+
var InsufficientXrdBalanceError = class extends Data.TaggedError("InsufficientXrdBalanceError") {};
|
|
744
|
+
var TransactionHelper = class extends Effect.Service()("TransactionHelper", {
|
|
745
|
+
dependencies: [
|
|
746
|
+
CreateTransactionIntent.Default,
|
|
747
|
+
CompileTransaction.Default,
|
|
748
|
+
SubmitTransaction.Default,
|
|
749
|
+
TransactionStatus.Default,
|
|
750
|
+
ManifestHelper.Default,
|
|
751
|
+
IntentHashService.Default,
|
|
752
|
+
EpochService.Default,
|
|
753
|
+
GetFungibleBalance.Default,
|
|
754
|
+
GetLedgerStateService.Default
|
|
755
|
+
],
|
|
756
|
+
effect: Effect.gen(function* () {
|
|
757
|
+
const createTransactionIntent = yield* CreateTransactionIntent;
|
|
758
|
+
const compileTransaction = yield* CompileTransaction;
|
|
759
|
+
const submitTransactionToNetwork = yield* SubmitTransaction;
|
|
760
|
+
const transactionStatus = yield* TransactionStatus;
|
|
761
|
+
const signer = yield* Signer;
|
|
762
|
+
const manifestHelper = yield* ManifestHelper;
|
|
763
|
+
const intentHashService = yield* IntentHashService;
|
|
764
|
+
const epochService = yield* EpochService;
|
|
765
|
+
const gatewayApiClient = yield* GatewayApiClient;
|
|
766
|
+
const getLedgerStateService = yield* GetLedgerStateService;
|
|
767
|
+
const networkId = NetworkId.make(gatewayApiClient.networkId);
|
|
768
|
+
const knownAddresses = yield* Effect.tryPromise(() => RadixEngineToolkit.Utils.knownAddresses(networkId));
|
|
769
|
+
const getFungibleBalance = yield* GetFungibleBalance;
|
|
770
|
+
const lifeCycleHook = yield* Effect.serviceOption(TransactionLifeCycleHook);
|
|
771
|
+
const onSubmitLifeCycleHook = lifeCycleHook.pipe(Option.flatMap((item) => Option.fromNullable(item.onSubmit)));
|
|
772
|
+
const onSubmitSuccessLifeCycleHook = lifeCycleHook.pipe(Option.flatMap((item) => Option.fromNullable(item.onSubmitSuccess)));
|
|
773
|
+
const onStatusFailureLifeCycleHook = lifeCycleHook.pipe(Option.flatMap((item) => Option.fromNullable(item.onStatusFailure)));
|
|
774
|
+
const onSuccessLifeCycleHook = lifeCycleHook.pipe(Option.flatMap((item) => Option.fromNullable(item.onSuccess)));
|
|
775
|
+
const xrdBalance = (account, stateVersion) => getFungibleBalance({
|
|
776
|
+
addresses: [account.address],
|
|
777
|
+
at_ledger_state: { state_version: stateVersion }
|
|
778
|
+
}).pipe(Effect.map(flow(Array.head, Option.map((value) => value.items), Option.flatMap(Array.findFirst((item) => item.resource_address === knownAddresses.resourceAddresses.xrd)))));
|
|
779
|
+
const submitTransaction = (input) => Effect.gen(function* () {
|
|
780
|
+
yield* Option.fromNullable(input.feePayer).pipe(Option.match({
|
|
781
|
+
onNone: () => Effect.void,
|
|
782
|
+
onSome: (feePayer) => getLedgerStateService({ at_ledger_state: { timestamp: /* @__PURE__ */ new Date() } }).pipe(Effect.flatMap((stateVersion) => xrdBalance(feePayer.account, stateVersion.state_version)), Effect.filterOrFail((amount) => Option.match(amount, {
|
|
783
|
+
onNone: () => false,
|
|
784
|
+
onSome: (amount$1) => amount$1.amount.gte(feePayer.amount)
|
|
785
|
+
}), () => new InsufficientXrdBalanceError({ message: `Insufficient XRD balance for account ${feePayer.account.address}` })))
|
|
786
|
+
}));
|
|
787
|
+
const { intent, id, hash } = yield* Option.fromNullable(input.transactionIntent).pipe(Option.match({
|
|
788
|
+
onNone: () => Effect.gen(function* () {
|
|
789
|
+
yield* Effect.log("Creating transaction intent");
|
|
790
|
+
const feePayerInstructions = input.feePayer ? yield* manifestHelper.addFeePayer(input.feePayer) : "";
|
|
791
|
+
return yield* createTransactionIntent({ manifest: TransactionManifestString.make(`
|
|
792
|
+
${feePayerInstructions}
|
|
793
|
+
${input.manifest}
|
|
794
|
+
`) }).pipe(Effect.flatMap((intent$1) => intentHashService.create(intent$1).pipe(Effect.map(({ id: id$1, hash: hash$1 }) => ({
|
|
795
|
+
id: id$1,
|
|
796
|
+
hash: hash$1,
|
|
797
|
+
intent: intent$1
|
|
798
|
+
})))), Effect.catchTags({
|
|
799
|
+
ParseError: Effect.die,
|
|
800
|
+
InvalidEpochError: Effect.die
|
|
801
|
+
}));
|
|
802
|
+
}),
|
|
803
|
+
onSome: (intent$1) => intentHashService.create(intent$1).pipe(Effect.flatMap(({ id: id$1, hash: hash$1 }) => epochService.verifyEpochBounds({
|
|
804
|
+
transactionId: id$1,
|
|
805
|
+
transactionIntent: intent$1
|
|
806
|
+
}).pipe(Effect.as({
|
|
807
|
+
id: id$1,
|
|
808
|
+
hash: hash$1,
|
|
809
|
+
intent: intent$1
|
|
810
|
+
}))))
|
|
811
|
+
}));
|
|
812
|
+
return yield* Effect.gen(function* () {
|
|
813
|
+
yield* Effect.log("Collecting signatures");
|
|
814
|
+
const compiledTransaction = yield* compileTransaction({
|
|
815
|
+
intent,
|
|
816
|
+
signatures: yield* signer.signToSignatureWithPublicKey(hash)
|
|
817
|
+
}).pipe(Effect.catchAll(Effect.die));
|
|
818
|
+
yield* Option.match(onSubmitLifeCycleHook, {
|
|
819
|
+
onNone: () => Effect.void,
|
|
820
|
+
onSome: (effect) => Effect.gen(function* () {
|
|
821
|
+
yield* Effect.log("Executing life cycle hook: onSubmit");
|
|
822
|
+
yield* effect({
|
|
823
|
+
id,
|
|
824
|
+
intent
|
|
825
|
+
});
|
|
826
|
+
})
|
|
827
|
+
});
|
|
828
|
+
yield* Effect.log("Submitting transaction to network");
|
|
829
|
+
yield* submitTransactionToNetwork({ compiledTransaction });
|
|
830
|
+
yield* Option.match(onSubmitSuccessLifeCycleHook, {
|
|
831
|
+
onNone: () => Effect.void,
|
|
832
|
+
onSome: (effect) => Effect.gen(function* () {
|
|
833
|
+
yield* Effect.log("Executing life cycle hook: onSubmitSuccess");
|
|
834
|
+
yield* effect({
|
|
835
|
+
id,
|
|
836
|
+
intent
|
|
837
|
+
});
|
|
838
|
+
})
|
|
839
|
+
});
|
|
840
|
+
const result = yield* transactionStatus.poll({ id }).pipe(Effect.map((status) => ({
|
|
841
|
+
statusResponse: status,
|
|
842
|
+
id
|
|
843
|
+
})), Effect.catchAllCause((cause) => Effect.gen(function* () {
|
|
844
|
+
yield* Option.match(onStatusFailureLifeCycleHook, {
|
|
845
|
+
onNone: () => Effect.void,
|
|
846
|
+
onSome: (effect) => Effect.gen(function* () {
|
|
847
|
+
yield* Effect.log("Executing life cycle hook: onFailure");
|
|
848
|
+
yield* effect({
|
|
849
|
+
id,
|
|
850
|
+
permanent: Cause.isFailType(cause) && cause.error._tag === "TransactionFailedError",
|
|
851
|
+
intent
|
|
852
|
+
});
|
|
853
|
+
})
|
|
854
|
+
});
|
|
855
|
+
return yield* Effect.failCause(cause);
|
|
856
|
+
})));
|
|
857
|
+
yield* Option.match(onSuccessLifeCycleHook, {
|
|
858
|
+
onNone: () => Effect.void,
|
|
859
|
+
onSome: (effect) => Effect.gen(function* () {
|
|
860
|
+
yield* Effect.log("Executing life cycle hook: onSuccess");
|
|
861
|
+
yield* effect({ id });
|
|
862
|
+
})
|
|
863
|
+
});
|
|
864
|
+
return result;
|
|
865
|
+
}).pipe(Effect.annotateLogs("transactionId", id));
|
|
866
|
+
}).pipe(Effect.annotateLogs({
|
|
867
|
+
networkId,
|
|
868
|
+
feePayer: input.feePayer?.account.address
|
|
869
|
+
}));
|
|
870
|
+
const getCommittedDetails = (input) => gatewayApiClient.transaction.getCommittedDetails(input.id);
|
|
871
|
+
const createBadge$1 = (input) => submitTransaction({
|
|
872
|
+
manifest: createBadge(input.account, input.initialSupply),
|
|
873
|
+
feePayer: {
|
|
874
|
+
account: input.feePayer,
|
|
875
|
+
amount: Amount.make("10")
|
|
876
|
+
}
|
|
877
|
+
}).pipe(Effect.flatMap(({ id }) => getCommittedDetails({ id })), Effect.map((result) => pipe(Option.fromNullable(result.transaction.balance_changes?.fungible_balance_changes), Option.flatMap(Array.head), Option.flatMap(Record.get("resource_address")), Option.getOrThrow, FungibleResourceAddress.make)));
|
|
878
|
+
const createFungibleToken = (input) => submitTransaction({
|
|
879
|
+
manifest: createFungibleTokenManifest(input),
|
|
880
|
+
feePayer: {
|
|
881
|
+
account: input.feePayer,
|
|
882
|
+
amount: Amount.make("10")
|
|
883
|
+
}
|
|
884
|
+
}).pipe(Effect.flatMap(({ id }) => getCommittedDetails({ id })), Effect.map((result) => pipe(Option.fromNullable(result.transaction.balance_changes?.fungible_balance_changes), Option.flatMap(Array.head), Option.flatMap(Record.get("resource_address")), Option.getOrThrow, FungibleResourceAddress.make)));
|
|
885
|
+
const faucet$1 = (input) => Effect.gen(function* () {
|
|
886
|
+
if (networkId === 1) return yield* new FaucetNotAvailableError({ message: "Faucet is only available on Testnet" }).pipe(Effect.die);
|
|
887
|
+
return yield* submitTransaction({ manifest: yield* faucet(input.account.address) });
|
|
888
|
+
});
|
|
889
|
+
return {
|
|
890
|
+
submitTransaction,
|
|
891
|
+
getCommittedDetails,
|
|
892
|
+
createBadge: createBadge$1,
|
|
893
|
+
createFungibleToken,
|
|
894
|
+
faucet: faucet$1
|
|
895
|
+
};
|
|
896
|
+
})
|
|
897
|
+
}) {};
|
|
898
|
+
|
|
899
|
+
//#endregion
|
|
900
|
+
export { CompileTransaction, CreateTransactionIntent, DisableTestClock, EpochService, FailedToCompileTransactionError, FailedToCreateIntentHashError, FailedToNotarizeTransactionError, FailedToSignTransactionError, FailedToStaticallyValidateManifestError, FaucetNotAvailableError, InsufficientXrdBalanceError, IntentHashService, InvalidEndEpochError, InvalidEpochError, InvalidManifestError, InvalidStartEpochError, ManifestHelper, NotaryKeyPair, PreviewTransaction, Signer, StaticallyValidateManifest, SubmitTransaction, TimeoutError, TransactionFailedError, TransactionHeader, TransactionHelper, TransactionLifeCycleHook, TransactionNotResolvedError, TransactionStatus, Vault, addFeePayer, createAccount, createBadge, createFungibleTokenManifest, faucet, schemas_exports as schemas };
|