@arkade-os/sdk 0.4.0-next.2 → 0.4.0-next.3
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/cjs/{asset → extension/asset}/index.js +1 -2
- package/dist/cjs/extension/asset/packet.js +111 -0
- package/dist/cjs/{asset → extension/asset}/types.js +1 -4
- package/dist/cjs/extension/index.js +254 -0
- package/dist/cjs/extension/packet.js +20 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/providers/ark.js +52 -37
- package/dist/cjs/providers/indexer.js +1 -1
- package/dist/cjs/providers/utils.js +39 -29
- package/dist/cjs/wallet/asset-manager.js +9 -17
- package/dist/cjs/wallet/asset.js +1 -1
- package/dist/cjs/wallet/validation.js +6 -2
- package/dist/cjs/wallet/wallet.js +5 -4
- package/dist/esm/{asset → extension/asset}/index.js +1 -1
- package/dist/esm/extension/asset/packet.js +107 -0
- package/dist/esm/{asset → extension/asset}/types.js +0 -3
- package/dist/esm/extension/index.js +248 -0
- package/dist/esm/extension/packet.js +16 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/providers/ark.js +52 -37
- package/dist/esm/providers/indexer.js +1 -1
- package/dist/esm/providers/utils.js +39 -29
- package/dist/esm/wallet/asset-manager.js +9 -17
- package/dist/esm/wallet/asset.js +1 -1
- package/dist/esm/wallet/validation.js +6 -2
- package/dist/esm/wallet/wallet.js +5 -4
- package/dist/types/{asset → extension/asset}/index.d.ts +1 -1
- package/dist/types/extension/asset/packet.d.ts +38 -0
- package/dist/types/{asset → extension/asset}/types.d.ts +0 -2
- package/dist/types/extension/index.d.ts +56 -0
- package/dist/types/extension/packet.d.ts +21 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/providers/utils.d.ts +6 -0
- package/dist/types/wallet/asset-manager.d.ts +4 -13
- package/dist/types/wallet/asset.d.ts +1 -1
- package/package.json +1 -1
- package/dist/cjs/asset/packet.js +0 -164
- package/dist/esm/asset/packet.js +0 -159
- package/dist/types/asset/packet.d.ts +0 -27
- /package/dist/cjs/{asset → extension/asset}/assetGroup.js +0 -0
- /package/dist/cjs/{asset → extension/asset}/assetId.js +0 -0
- /package/dist/cjs/{asset → extension/asset}/assetInput.js +0 -0
- /package/dist/cjs/{asset → extension/asset}/assetOutput.js +0 -0
- /package/dist/cjs/{asset → extension/asset}/assetRef.js +0 -0
- /package/dist/cjs/{asset → extension/asset}/metadata.js +0 -0
- /package/dist/cjs/{asset → extension/asset}/utils.js +0 -0
- /package/dist/esm/{asset → extension/asset}/assetGroup.js +0 -0
- /package/dist/esm/{asset → extension/asset}/assetId.js +0 -0
- /package/dist/esm/{asset → extension/asset}/assetInput.js +0 -0
- /package/dist/esm/{asset → extension/asset}/assetOutput.js +0 -0
- /package/dist/esm/{asset → extension/asset}/assetRef.js +0 -0
- /package/dist/esm/{asset → extension/asset}/metadata.js +0 -0
- /package/dist/esm/{asset → extension/asset}/utils.js +0 -0
- /package/dist/types/{asset → extension/asset}/assetGroup.d.ts +0 -0
- /package/dist/types/{asset → extension/asset}/assetId.d.ts +0 -0
- /package/dist/types/{asset → extension/asset}/assetInput.d.ts +0 -0
- /package/dist/types/{asset → extension/asset}/assetOutput.d.ts +0 -0
- /package/dist/types/{asset → extension/asset}/assetRef.d.ts +0 -0
- /package/dist/types/{asset → extension/asset}/metadata.d.ts +0 -0
- /package/dist/types/{asset → extension/asset}/utils.d.ts +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AssetManager = exports.ReadonlyAssetManager = void 0;
|
|
4
|
-
const asset_1 = require("../asset");
|
|
4
|
+
const asset_1 = require("../extension/asset");
|
|
5
5
|
const address_1 = require("../script/address");
|
|
6
6
|
const asset_2 = require("./asset");
|
|
7
|
+
const extension_1 = require("../extension");
|
|
7
8
|
const wallet_1 = require("./wallet");
|
|
8
9
|
class ReadonlyAssetManager {
|
|
9
10
|
constructor(indexer) {
|
|
@@ -24,30 +25,21 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
24
25
|
* Issue a new asset.
|
|
25
26
|
* @param params - Parameters for asset issuance
|
|
26
27
|
* @param params.amount - Amount of asset units to issue
|
|
27
|
-
* @param params.
|
|
28
|
+
* @param params.controlAssetId - Optional control asset ID (for reissuable assets)
|
|
28
29
|
* @param params.metadata - Optional metadata to attach to the asset
|
|
29
30
|
* @returns Promise resolving to the ark transaction ID and asset ID
|
|
30
31
|
*
|
|
31
32
|
* @example
|
|
32
33
|
* ```typescript
|
|
33
34
|
* // Issue a simple non-reissuable asset
|
|
34
|
-
* const result = await wallet.
|
|
35
|
-
* console.log('Asset ID:', result.assetId);
|
|
36
|
-
*
|
|
37
|
-
* // Issue a reissuable asset with a new control asset
|
|
38
|
-
* const result = await wallet.issueAsset({
|
|
39
|
-
* amount: 1000,
|
|
40
|
-
* controlAsset: 1 // creates new control asset with amount 1
|
|
41
|
-
* });
|
|
42
|
-
* console.log('Control Asset ID:', result.controlAssetId);
|
|
35
|
+
* const result = await wallet.assetManager.issue({ amount: 1000 });
|
|
43
36
|
* console.log('Asset ID:', result.assetId);
|
|
44
37
|
*
|
|
45
38
|
* // Issue a reissuable asset with an existing control asset
|
|
46
|
-
* const result = await wallet.
|
|
39
|
+
* const result = await wallet.assetManager.issue({
|
|
47
40
|
* amount: 1000,
|
|
48
|
-
*
|
|
41
|
+
* controlAssetId: 'existingControlAssetId'
|
|
49
42
|
* });
|
|
50
|
-
* console.log('Control Asset ID:', result.controlAssetId);
|
|
51
43
|
* console.log('Asset ID:', result.assetId);
|
|
52
44
|
* ```
|
|
53
45
|
*/
|
|
@@ -105,7 +97,7 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
105
97
|
script: outputAddress.pkScript,
|
|
106
98
|
amount: BigInt(totalBtcSelected),
|
|
107
99
|
},
|
|
108
|
-
asset_1.Packet.create(groups).txOut(),
|
|
100
|
+
extension_1.Extension.create([asset_1.Packet.create(groups)]).txOut(),
|
|
109
101
|
];
|
|
110
102
|
const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(coinSelection.inputs, outputs);
|
|
111
103
|
return {
|
|
@@ -217,7 +209,7 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
217
209
|
script: outputAddress.pkScript,
|
|
218
210
|
amount: BigInt(totalBtcSelected),
|
|
219
211
|
},
|
|
220
|
-
asset_1.Packet.create(groups).txOut(),
|
|
212
|
+
extension_1.Extension.create([asset_1.Packet.create(groups)]).txOut(),
|
|
221
213
|
];
|
|
222
214
|
const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(selectedCoins, outputs);
|
|
223
215
|
return arkTxid;
|
|
@@ -301,7 +293,7 @@ class AssetManager extends ReadonlyAssetManager {
|
|
|
301
293
|
script: outputAddress.pkScript,
|
|
302
294
|
amount: BigInt(totalBtcSelected),
|
|
303
295
|
},
|
|
304
|
-
asset_1.Packet.create(groups).txOut(),
|
|
296
|
+
extension_1.Extension.create([asset_1.Packet.create(groups)]).txOut(),
|
|
305
297
|
];
|
|
306
298
|
const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(selectedCoins, outputs);
|
|
307
299
|
return arkTxid;
|
package/dist/cjs/wallet/asset.js
CHANGED
|
@@ -4,7 +4,7 @@ exports.createAssetPacket = createAssetPacket;
|
|
|
4
4
|
exports.selectCoinsWithAsset = selectCoinsWithAsset;
|
|
5
5
|
exports.computeAssetChange = computeAssetChange;
|
|
6
6
|
exports.selectedCoinsToAssetInputs = selectedCoinsToAssetInputs;
|
|
7
|
-
const asset_1 = require("../asset");
|
|
7
|
+
const asset_1 = require("../extension/asset");
|
|
8
8
|
/**
|
|
9
9
|
* Creates an asset packet from asset inputs and receivers.
|
|
10
10
|
* Groups inputs and outputs by asset ID and creates the Packet object
|
|
@@ -4,7 +4,7 @@ exports.ErrInvalidOffchainOutputAmount = exports.ErrOnchainOutputNotFound = expo
|
|
|
4
4
|
exports.validateBatchRecipients = validateBatchRecipients;
|
|
5
5
|
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
6
6
|
const address_1 = require("../script/address");
|
|
7
|
-
const
|
|
7
|
+
const extension_1 = require("../extension");
|
|
8
8
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
9
9
|
const ErrOffchainOutputNotFound = (address) => new Error(`offchain send output not found: ${address}`);
|
|
10
10
|
exports.ErrOffchainOutputNotFound = ErrOffchainOutputNotFound;
|
|
@@ -125,7 +125,11 @@ function validateOffchainRecipient(leaves, arkAddress, recipient, usedOutputs //
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
function validateAssetOutputs(leafTx, outputIndex, expectedAssets) {
|
|
128
|
-
const
|
|
128
|
+
const ext = extension_1.Extension.fromTx(leafTx);
|
|
129
|
+
const assetPacket = ext.getAssetPacket();
|
|
130
|
+
if (!assetPacket) {
|
|
131
|
+
throw new Error("no asset packet found in extension");
|
|
132
|
+
}
|
|
129
133
|
for (const { assetId, amount } of expectedAssets) {
|
|
130
134
|
validateAssetGroupOutput(assetPacket, outputIndex, assetId, amount);
|
|
131
135
|
}
|
|
@@ -30,6 +30,7 @@ const batch_1 = require("./batch");
|
|
|
30
30
|
const arkfee_1 = require("../arkfee");
|
|
31
31
|
const transactionHistory_1 = require("../utils/transactionHistory");
|
|
32
32
|
const asset_manager_1 = require("./asset-manager");
|
|
33
|
+
const extension_1 = require("../extension");
|
|
33
34
|
const delegate_1 = require("../script/delegate");
|
|
34
35
|
const delegator_1 = require("./delegator");
|
|
35
36
|
const repositories_1 = require("../repositories");
|
|
@@ -918,7 +919,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
918
919
|
}));
|
|
919
920
|
if (outputAssets && outputAssets.length > 0) {
|
|
920
921
|
const assetPacket = (0, asset_1.createAssetPacket)(assetInputs, recipients);
|
|
921
|
-
outputs.push(assetPacket.txOut());
|
|
922
|
+
outputs.push(extension_1.Extension.create([assetPacket]).txOut());
|
|
922
923
|
}
|
|
923
924
|
// session holds the state of the musig2 signing process of the vtxo tree
|
|
924
925
|
let session;
|
|
@@ -931,15 +932,15 @@ class Wallet extends ReadonlyWallet {
|
|
|
931
932
|
this.makeRegisterIntentSignature(params.inputs, outputs, onchainOutputIndexes, signingPublicKeys),
|
|
932
933
|
this.makeDeleteIntentSignature(params.inputs),
|
|
933
934
|
]);
|
|
934
|
-
const intentId = await this.safeRegisterIntent(intent);
|
|
935
935
|
const topics = [
|
|
936
936
|
...signingPublicKeys,
|
|
937
937
|
...params.inputs.map((input) => `${input.txid}:${input.vout}`),
|
|
938
938
|
];
|
|
939
|
-
const handler = this.createBatchHandler(intentId, params.inputs, recipients, session);
|
|
940
939
|
const abortController = new AbortController();
|
|
941
940
|
try {
|
|
942
941
|
const stream = this.arkProvider.getEventStream(abortController.signal, topics);
|
|
942
|
+
const intentId = await this.safeRegisterIntent(intent);
|
|
943
|
+
const handler = this.createBatchHandler(intentId, params.inputs, recipients, session);
|
|
943
944
|
const commitmentTxid = await batch_1.Batch.join(stream, handler, {
|
|
944
945
|
abortController,
|
|
945
946
|
skipVtxoTreeSigning: !hasOffchainOutputs,
|
|
@@ -1404,7 +1405,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1404
1405
|
recipients.some((r) => r.assets && r.assets.length > 0);
|
|
1405
1406
|
if (hasAssets) {
|
|
1406
1407
|
const assetPacket = (0, asset_1.createAssetPacket)(assetInputs, recipients, changeReceiver);
|
|
1407
|
-
outputs.push(assetPacket.txOut());
|
|
1408
|
+
outputs.push(extension_1.Extension.create([assetPacket]).txOut());
|
|
1408
1409
|
}
|
|
1409
1410
|
const sentAmount = recipients.reduce((sum, r) => sum + r.amount, 0);
|
|
1410
1411
|
const { arkTxid, signedCheckpointTxs } = await this.buildAndSubmitOffchainTx(selectedCoins, outputs);
|
|
@@ -5,4 +5,4 @@ export { AssetInput, AssetInputs } from './assetInput.js';
|
|
|
5
5
|
export { AssetOutput, AssetOutputs } from './assetOutput.js';
|
|
6
6
|
export { Metadata, MetadataList } from './metadata.js';
|
|
7
7
|
export { AssetGroup } from './assetGroup.js';
|
|
8
|
-
export { Packet
|
|
8
|
+
export { Packet } from './packet.js';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { hex } from "@scure/base";
|
|
2
|
+
import { AssetRefType } from './types.js';
|
|
3
|
+
import { AssetGroup } from './assetGroup.js';
|
|
4
|
+
import { BufferReader, BufferWriter } from './utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* Packet represents a collection of asset groups.
|
|
7
|
+
* It encodes/decodes as raw bytes only — OP_RETURN framing is handled by the Extension module.
|
|
8
|
+
*/
|
|
9
|
+
export class Packet {
|
|
10
|
+
constructor(groups) {
|
|
11
|
+
this.groups = groups;
|
|
12
|
+
}
|
|
13
|
+
static create(groups) {
|
|
14
|
+
const p = new Packet(groups);
|
|
15
|
+
p.validate();
|
|
16
|
+
return p;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* fromBytes parses a Packet from raw bytes.
|
|
20
|
+
*/
|
|
21
|
+
static fromBytes(buf) {
|
|
22
|
+
return Packet.fromReader(new BufferReader(buf));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* fromString parses a Packet from a raw hex string (not an OP_RETURN script).
|
|
26
|
+
*/
|
|
27
|
+
static fromString(s) {
|
|
28
|
+
if (!s) {
|
|
29
|
+
throw new Error("missing packet data");
|
|
30
|
+
}
|
|
31
|
+
let buf;
|
|
32
|
+
try {
|
|
33
|
+
buf = hex.decode(s);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new Error("invalid packet format, must be hex");
|
|
37
|
+
}
|
|
38
|
+
return Packet.fromBytes(buf);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* type returns the TLV packet type tag. Implements ExtensionPacket interface.
|
|
42
|
+
*/
|
|
43
|
+
type() {
|
|
44
|
+
return Packet.PACKET_TYPE;
|
|
45
|
+
}
|
|
46
|
+
leafTxPacket(intentTxid) {
|
|
47
|
+
const leafGroups = this.groups.map((group) => group.toBatchLeafAssetGroup(intentTxid));
|
|
48
|
+
return new Packet(leafGroups);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* serialize encodes the packet as raw bytes (varint group count + group data).
|
|
52
|
+
* Does NOT include OP_RETURN, ARK magic, or TLV type/length — those are
|
|
53
|
+
* added by the Extension module.
|
|
54
|
+
*/
|
|
55
|
+
serialize() {
|
|
56
|
+
if (this.groups.length === 0) {
|
|
57
|
+
return new Uint8Array(0);
|
|
58
|
+
}
|
|
59
|
+
const writer = new BufferWriter();
|
|
60
|
+
writer.writeVarUint(this.groups.length);
|
|
61
|
+
for (const group of this.groups) {
|
|
62
|
+
group.serializeTo(writer);
|
|
63
|
+
}
|
|
64
|
+
return writer.toBytes();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* toString returns the hex-encoded raw packet bytes.
|
|
68
|
+
*/
|
|
69
|
+
toString() {
|
|
70
|
+
return hex.encode(this.serialize());
|
|
71
|
+
}
|
|
72
|
+
validate() {
|
|
73
|
+
if (this.groups.length === 0) {
|
|
74
|
+
throw new Error("missing assets");
|
|
75
|
+
}
|
|
76
|
+
const seenAssetIds = new Set();
|
|
77
|
+
for (const group of this.groups) {
|
|
78
|
+
if (group.assetId !== null) {
|
|
79
|
+
const key = group.assetId.toString();
|
|
80
|
+
if (seenAssetIds.has(key)) {
|
|
81
|
+
throw new Error(`duplicate asset group for asset ${key}`);
|
|
82
|
+
}
|
|
83
|
+
seenAssetIds.add(key);
|
|
84
|
+
}
|
|
85
|
+
if (group.controlAsset !== null &&
|
|
86
|
+
group.controlAsset.ref.type === AssetRefType.ByGroup &&
|
|
87
|
+
group.controlAsset.ref.groupIndex >= this.groups.length) {
|
|
88
|
+
throw new Error(`invalid control asset group index, ${group.controlAsset.ref.groupIndex} out of range [0, ${this.groups.length - 1}]`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
static fromReader(reader) {
|
|
93
|
+
const count = Number(reader.readVarUint());
|
|
94
|
+
const groups = [];
|
|
95
|
+
for (let i = 0; i < count; i++) {
|
|
96
|
+
groups.push(AssetGroup.fromReader(reader));
|
|
97
|
+
}
|
|
98
|
+
if (reader.remaining() > 0) {
|
|
99
|
+
throw new Error(`invalid packet length, left ${reader.remaining()} unknown bytes to read`);
|
|
100
|
+
}
|
|
101
|
+
const packet = new Packet(groups);
|
|
102
|
+
packet.validate();
|
|
103
|
+
return packet;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** PACKET_TYPE is the 1-byte TLV type tag used in the Extension envelope. */
|
|
107
|
+
Packet.PACKET_TYPE = 0;
|
|
@@ -17,6 +17,3 @@ export var AssetRefType;
|
|
|
17
17
|
export const MASK_ASSET_ID = 0x01;
|
|
18
18
|
export const MASK_CONTROL_ASSET = 0x02;
|
|
19
19
|
export const MASK_METADATA = 0x04;
|
|
20
|
-
// ARK magic bytes and marker
|
|
21
|
-
export const ARKADE_MAGIC = new Uint8Array([0x41, 0x52, 0x4b]); // "ARK"
|
|
22
|
-
export const MARKER_ASSET_PAYLOAD = 0x00;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { hex } from "@scure/base";
|
|
2
|
+
import { Script } from "@scure/btc-signer";
|
|
3
|
+
import { equalBytes } from "@scure/btc-signer/utils.js";
|
|
4
|
+
import { Packet } from './asset/packet.js';
|
|
5
|
+
import { BufferReader } from './asset/utils.js';
|
|
6
|
+
import { UnknownPacket } from './packet.js';
|
|
7
|
+
export { UnknownPacket } from './packet.js';
|
|
8
|
+
/**
|
|
9
|
+
* ArkadeMagic is the 3-byte magic prefix ("ARK") that identifies an OP_RETURN
|
|
10
|
+
* output as an ark extension blob.
|
|
11
|
+
*/
|
|
12
|
+
export const ARKADE_MAGIC = new Uint8Array([0x41, 0x52, 0x4b]); // "ARK"
|
|
13
|
+
/**
|
|
14
|
+
* ErrExtensionNotFound is thrown when no extension output is found in a transaction.
|
|
15
|
+
*/
|
|
16
|
+
export class ExtensionNotFoundError extends Error {
|
|
17
|
+
constructor() {
|
|
18
|
+
super("no extension output found in transaction");
|
|
19
|
+
this.name = "ExtensionNotFoundError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Extension is a set of typed packets encoded in an OP_RETURN output.
|
|
24
|
+
*
|
|
25
|
+
* Wire format:
|
|
26
|
+
* OP_RETURN | <push> | ARK(3B) | [type(1B) | varint_len | data]...
|
|
27
|
+
*/
|
|
28
|
+
export class Extension {
|
|
29
|
+
constructor(packets) {
|
|
30
|
+
this.packets = packets;
|
|
31
|
+
}
|
|
32
|
+
static create(packets) {
|
|
33
|
+
if (packets.length === 0) {
|
|
34
|
+
throw new Error("missing packets");
|
|
35
|
+
}
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
for (const p of packets) {
|
|
38
|
+
if (seen.has(p.type())) {
|
|
39
|
+
throw new Error(`duplicate packet type ${p.type()}`);
|
|
40
|
+
}
|
|
41
|
+
seen.add(p.type());
|
|
42
|
+
}
|
|
43
|
+
return new Extension(packets);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* isExtension returns true if the script is an OP_RETURN whose push data
|
|
47
|
+
* begins with the ARK magic bytes.
|
|
48
|
+
*/
|
|
49
|
+
static isExtension(script) {
|
|
50
|
+
try {
|
|
51
|
+
const decoded = Script.decode(script);
|
|
52
|
+
if (decoded.length < 2 || decoded[0] !== "RETURN")
|
|
53
|
+
return false;
|
|
54
|
+
const data = decoded[1];
|
|
55
|
+
if (!(data instanceof Uint8Array))
|
|
56
|
+
return false;
|
|
57
|
+
return (data.length >= ARKADE_MAGIC.length &&
|
|
58
|
+
equalBytes(data.slice(0, ARKADE_MAGIC.length), ARKADE_MAGIC));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* fromBytes parses an Extension from a raw OP_RETURN script.
|
|
66
|
+
*/
|
|
67
|
+
static fromBytes(script) {
|
|
68
|
+
if (!script || script.length === 0) {
|
|
69
|
+
throw new Error("missing OP_RETURN");
|
|
70
|
+
}
|
|
71
|
+
let decoded;
|
|
72
|
+
try {
|
|
73
|
+
decoded = Script.decode(script);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
throw new Error("expected OP_RETURN");
|
|
77
|
+
}
|
|
78
|
+
if (decoded.length === 0 || decoded[0] !== "RETURN") {
|
|
79
|
+
throw new Error("expected OP_RETURN");
|
|
80
|
+
}
|
|
81
|
+
const dataPushes = decoded
|
|
82
|
+
.slice(1)
|
|
83
|
+
.filter((x) => x instanceof Uint8Array);
|
|
84
|
+
if (dataPushes.length === 0) {
|
|
85
|
+
throw new Error("missing magic prefix: EOF");
|
|
86
|
+
}
|
|
87
|
+
// Concatenate all data pushes (handles OP_PUSHDATA1/2/4)
|
|
88
|
+
const payload = new Uint8Array(dataPushes.reduce((acc, d) => acc + d.length, 0));
|
|
89
|
+
let offset = 0;
|
|
90
|
+
for (const d of dataPushes) {
|
|
91
|
+
payload.set(d, offset);
|
|
92
|
+
offset += d.length;
|
|
93
|
+
}
|
|
94
|
+
// Check ARK magic
|
|
95
|
+
if (payload.length < ARKADE_MAGIC.length ||
|
|
96
|
+
!equalBytes(payload.slice(0, ARKADE_MAGIC.length), ARKADE_MAGIC)) {
|
|
97
|
+
throw new Error(`expected magic prefix ${hex.encode(ARKADE_MAGIC)}, got ${hex.encode(payload.slice(0, Math.min(payload.length, ARKADE_MAGIC.length)))}`);
|
|
98
|
+
}
|
|
99
|
+
// Parse TLV records
|
|
100
|
+
const reader = new BufferReader(payload.slice(ARKADE_MAGIC.length));
|
|
101
|
+
const packets = [];
|
|
102
|
+
while (reader.remaining() > 0) {
|
|
103
|
+
const packetType = reader.readByte();
|
|
104
|
+
let data;
|
|
105
|
+
try {
|
|
106
|
+
data = reader.readVarSlice();
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
throw new Error("missing packet data");
|
|
110
|
+
}
|
|
111
|
+
packets.push(parsePacket(packetType, data));
|
|
112
|
+
}
|
|
113
|
+
if (packets.length === 0) {
|
|
114
|
+
throw new Error("missing packets");
|
|
115
|
+
}
|
|
116
|
+
// Reject duplicate packet types
|
|
117
|
+
const seen = new Set();
|
|
118
|
+
for (const p of packets) {
|
|
119
|
+
if (seen.has(p.type())) {
|
|
120
|
+
throw new Error(`duplicate packet type ${p.type()}`);
|
|
121
|
+
}
|
|
122
|
+
seen.add(p.type());
|
|
123
|
+
}
|
|
124
|
+
return new Extension(packets);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* fromTx searches the transaction outputs for an extension blob and parses it.
|
|
128
|
+
* Throws ExtensionNotFoundError if none is found.
|
|
129
|
+
*/
|
|
130
|
+
static fromTx(tx) {
|
|
131
|
+
for (let i = 0; i < tx.outputsLength; i++) {
|
|
132
|
+
const output = tx.getOutput(i);
|
|
133
|
+
if (!output?.script)
|
|
134
|
+
continue;
|
|
135
|
+
if (Extension.isExtension(output.script)) {
|
|
136
|
+
return Extension.fromBytes(output.script);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
throw new ExtensionNotFoundError();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* serialize encodes the extension as an OP_RETURN script.
|
|
143
|
+
*
|
|
144
|
+
* Layout: OP_RETURN | <push> | ARK | [type | varint_len | data]...
|
|
145
|
+
*/
|
|
146
|
+
serialize() {
|
|
147
|
+
// Build payload: ARK magic + TLV records
|
|
148
|
+
const parts = [ARKADE_MAGIC];
|
|
149
|
+
for (const p of this.packets) {
|
|
150
|
+
const data = p.serialize();
|
|
151
|
+
// type (1 byte)
|
|
152
|
+
const typeByte = new Uint8Array([p.type()]);
|
|
153
|
+
// varint length prefix + data
|
|
154
|
+
const lengthBuf = encodeVarUint(data.length);
|
|
155
|
+
parts.push(typeByte, lengthBuf, data);
|
|
156
|
+
}
|
|
157
|
+
const totalLen = parts.reduce((acc, p) => acc + p.length, 0);
|
|
158
|
+
const payload = new Uint8Array(totalLen);
|
|
159
|
+
let off = 0;
|
|
160
|
+
for (const p of parts) {
|
|
161
|
+
payload.set(p, off);
|
|
162
|
+
off += p.length;
|
|
163
|
+
}
|
|
164
|
+
return buildOpReturnScript(payload);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* txOut returns the extension as a zero-value OP_RETURN transaction output.
|
|
168
|
+
*/
|
|
169
|
+
txOut() {
|
|
170
|
+
return {
|
|
171
|
+
script: this.serialize(),
|
|
172
|
+
amount: 0n,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* getAssetPacket returns the embedded Packet, or null if not present.
|
|
177
|
+
*/
|
|
178
|
+
getAssetPacket() {
|
|
179
|
+
for (const p of this.packets) {
|
|
180
|
+
if (p instanceof Packet) {
|
|
181
|
+
return p;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* parsePacket dispatches to a known packet type or falls back to UnknownPacket.
|
|
189
|
+
*/
|
|
190
|
+
function parsePacket(packetType, data) {
|
|
191
|
+
if (packetType === Packet.PACKET_TYPE) {
|
|
192
|
+
return Packet.fromBytes(data);
|
|
193
|
+
}
|
|
194
|
+
return new UnknownPacket(packetType, data);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* encodeVarUint encodes a non-negative integer as a LEB128 unsigned varint.
|
|
198
|
+
*/
|
|
199
|
+
function encodeVarUint(value) {
|
|
200
|
+
const bytes = [];
|
|
201
|
+
let remaining = value;
|
|
202
|
+
do {
|
|
203
|
+
let byte = remaining & 0x7f;
|
|
204
|
+
remaining >>>= 7;
|
|
205
|
+
if (remaining > 0)
|
|
206
|
+
byte |= 0x80;
|
|
207
|
+
bytes.push(byte);
|
|
208
|
+
} while (remaining > 0);
|
|
209
|
+
return new Uint8Array(bytes);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* buildOpReturnScript builds an OP_RETURN script with an arbitrary-length data push.
|
|
213
|
+
* Manually constructed to avoid the 520-byte cap in some script builders.
|
|
214
|
+
*
|
|
215
|
+
* Opcodes: 0x6a=OP_RETURN, 0x4c=OP_PUSHDATA1, 0x4d=OP_PUSHDATA2, 0x4e=OP_PUSHDATA4
|
|
216
|
+
*/
|
|
217
|
+
function buildOpReturnScript(data) {
|
|
218
|
+
const n = data.length;
|
|
219
|
+
let script;
|
|
220
|
+
if (n <= 75) {
|
|
221
|
+
script = new Uint8Array(2 + n);
|
|
222
|
+
script[0] = 0x6a; // OP_RETURN
|
|
223
|
+
script[1] = n;
|
|
224
|
+
script.set(data, 2);
|
|
225
|
+
}
|
|
226
|
+
else if (n <= 255) {
|
|
227
|
+
script = new Uint8Array(3 + n);
|
|
228
|
+
script[0] = 0x6a;
|
|
229
|
+
script[1] = 0x4c; // OP_PUSHDATA1
|
|
230
|
+
script[2] = n;
|
|
231
|
+
script.set(data, 3);
|
|
232
|
+
}
|
|
233
|
+
else if (n <= 65535) {
|
|
234
|
+
script = new Uint8Array(4 + n);
|
|
235
|
+
script[0] = 0x6a;
|
|
236
|
+
script[1] = 0x4d; // OP_PUSHDATA2
|
|
237
|
+
new DataView(script.buffer).setUint16(2, n, true);
|
|
238
|
+
script.set(data, 4);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
script = new Uint8Array(6 + n);
|
|
242
|
+
script[0] = 0x6a;
|
|
243
|
+
script[1] = 0x4e; // OP_PUSHDATA4
|
|
244
|
+
new DataView(script.buffer).setUint32(2, n, true);
|
|
245
|
+
script.set(data, 6);
|
|
246
|
+
}
|
|
247
|
+
return script;
|
|
248
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UnknownPacket holds a packet whose type is not recognized by this implementation.
|
|
3
|
+
* It round-trips opaquely: the raw bytes are preserved as-is.
|
|
4
|
+
*/
|
|
5
|
+
export class UnknownPacket {
|
|
6
|
+
constructor(packetType, data) {
|
|
7
|
+
this.packetType = packetType;
|
|
8
|
+
this.data = data;
|
|
9
|
+
}
|
|
10
|
+
type() {
|
|
11
|
+
return this.packetType;
|
|
12
|
+
}
|
|
13
|
+
serialize() {
|
|
14
|
+
return this.data;
|
|
15
|
+
}
|
|
16
|
+
}
|
package/dist/esm/index.js
CHANGED
|
@@ -35,7 +35,7 @@ import { buildForfeitTx } from './forfeit.js';
|
|
|
35
35
|
import { IndexedDBWalletRepository, IndexedDBContractRepository, InMemoryWalletRepository, InMemoryContractRepository, MIGRATION_KEY, migrateWalletRepository, requiresMigration, getMigrationStatus, rollbackMigration, WalletRepositoryImpl, ContractRepositoryImpl, } from './repositories/index.js';
|
|
36
36
|
import { DelegatorManagerImpl } from './wallet/delegator.js';
|
|
37
37
|
export * from './arkfee/index.js';
|
|
38
|
-
export * as asset from './asset/index.js';
|
|
38
|
+
export * as asset from './extension/asset/index.js';
|
|
39
39
|
// Contracts
|
|
40
40
|
import { ContractManager, ContractWatcher, contractHandlers, DefaultContractHandler, DelegateContractHandler, VHTLCContractHandler, encodeArkContract, decodeArkContract, contractFromArkContract, contractFromArkContractWithAddress, isArkContract, } from './contracts/index.js';
|
|
41
41
|
import { closeDatabase, openDatabase } from './repositories/indexedDB/manager.js';
|
|
@@ -221,54 +221,69 @@ export class RestArkProvider {
|
|
|
221
221
|
handleError(errorText, `Failed to submit forfeit transactions: ${response.statusText}`);
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
|
-
|
|
224
|
+
getEventStream(signal, topics) {
|
|
225
225
|
const url = `${this.serverUrl}/v1/batch/events`;
|
|
226
226
|
const queryParams = topics.length > 0
|
|
227
227
|
? `?${topics.map((topic) => `topics=${encodeURIComponent(topic)}`).join("&")}`
|
|
228
228
|
: "";
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
229
|
+
// Create first EventSource eagerly so events are buffered
|
|
230
|
+
// before the caller starts iterating, preventing race conditions
|
|
231
|
+
// where the server emits events before iteration begins.
|
|
232
|
+
const eagerEventSource = new EventSource(url + queryParams);
|
|
233
|
+
const eagerIterator = eventSourceIterator(eagerEventSource);
|
|
234
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
235
|
+
const self = this;
|
|
236
|
+
return (async function* () {
|
|
237
|
+
let firstIteration = true;
|
|
238
|
+
while (!signal?.aborted) {
|
|
239
|
+
const eventSource = firstIteration
|
|
240
|
+
? eagerEventSource
|
|
241
|
+
: new EventSource(url + queryParams);
|
|
242
|
+
const iterator = firstIteration
|
|
243
|
+
? eagerIterator
|
|
244
|
+
: eventSourceIterator(eventSource);
|
|
245
|
+
firstIteration = false;
|
|
237
246
|
try {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (
|
|
245
|
-
|
|
247
|
+
const abortHandler = () => {
|
|
248
|
+
eventSource.close();
|
|
249
|
+
};
|
|
250
|
+
signal?.addEventListener("abort", abortHandler);
|
|
251
|
+
try {
|
|
252
|
+
for await (const event of iterator) {
|
|
253
|
+
if (signal?.aborted)
|
|
254
|
+
break;
|
|
255
|
+
try {
|
|
256
|
+
const data = JSON.parse(event.data);
|
|
257
|
+
const settlementEvent = self.parseSettlementEvent(data);
|
|
258
|
+
if (settlementEvent) {
|
|
259
|
+
yield settlementEvent;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
console.error("Failed to parse event:", err);
|
|
264
|
+
throw err;
|
|
246
265
|
}
|
|
247
|
-
}
|
|
248
|
-
catch (err) {
|
|
249
|
-
console.error("Failed to parse event:", err);
|
|
250
|
-
throw err;
|
|
251
266
|
}
|
|
252
267
|
}
|
|
268
|
+
finally {
|
|
269
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
270
|
+
eventSource.close();
|
|
271
|
+
}
|
|
253
272
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
console.debug("Timeout error ignored");
|
|
266
|
-
continue;
|
|
273
|
+
catch (error) {
|
|
274
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
278
|
+
if (isFetchTimeoutError(error)) {
|
|
279
|
+
console.debug("Timeout error ignored");
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
console.error("Event stream error:", error);
|
|
283
|
+
throw error;
|
|
267
284
|
}
|
|
268
|
-
console.error("Event stream error:", error);
|
|
269
|
-
throw error;
|
|
270
285
|
}
|
|
271
|
-
}
|
|
286
|
+
})();
|
|
272
287
|
}
|
|
273
288
|
async *getTransactionsStream(signal) {
|
|
274
289
|
const url = `${this.serverUrl}/v1/txs`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { hex } from "@scure/base";
|
|
2
2
|
import { isFetchTimeoutError } from './ark.js';
|
|
3
3
|
import { eventSourceIterator } from './utils.js';
|
|
4
|
-
import { MetadataList } from '../asset/index.js';
|
|
4
|
+
import { MetadataList } from '../extension/asset/index.js';
|
|
5
5
|
export var IndexerTxType;
|
|
6
6
|
(function (IndexerTxType) {
|
|
7
7
|
IndexerTxType[IndexerTxType["INDEXER_TX_TYPE_UNSPECIFIED"] = 0] = "INDEXER_TX_TYPE_UNSPECIFIED";
|