@mysten-incubation/hashi 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/client.d.mts +6 -9
- package/dist/client.mjs +92 -32
- package/dist/types.d.mts +2 -0
- package/dist/util.mjs +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @mysten-incubation/hashi
|
|
2
2
|
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 75fcdca: Fix btcTxid display values to strip the 0x prefix. Add GraphQL-based discovery of pending deposits to transaction history — confirmed requests still read from the on-chain user_requests index; in-flight deposits are discovered via DepositRequestedEvent queries and deduplicated. Bump GET_OBJECTS_BATCH to 500.
|
|
8
|
+
|
|
3
9
|
## 0.0.2
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/dist/client.d.mts
CHANGED
|
@@ -36,7 +36,8 @@ declare class HashiClient {
|
|
|
36
36
|
hashiObjectId,
|
|
37
37
|
packageId,
|
|
38
38
|
bitcoinNetwork,
|
|
39
|
-
btcRpcUrl
|
|
39
|
+
btcRpcUrl,
|
|
40
|
+
graphqlUrl
|
|
40
41
|
}: {
|
|
41
42
|
client: ClientWithCoreApi;
|
|
42
43
|
network: SuiNetwork;
|
|
@@ -44,6 +45,7 @@ declare class HashiClient {
|
|
|
44
45
|
packageId?: string;
|
|
45
46
|
bitcoinNetwork?: BitcoinNetwork;
|
|
46
47
|
btcRpcUrl?: string;
|
|
48
|
+
graphqlUrl?: string;
|
|
47
49
|
});
|
|
48
50
|
/**
|
|
49
51
|
* Generates a unique Bitcoin P2TR deposit address for a Sui address.
|
|
@@ -344,14 +346,9 @@ declare class HashiClient {
|
|
|
344
346
|
withdrawalFees: (sender?: string) => Promise<WithdrawalFees>;
|
|
345
347
|
/**
|
|
346
348
|
* Fetch the unified transaction history (deposits + withdrawals) for
|
|
347
|
-
* a Sui address.
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* Deposit items include `btcTxid` / `btcVout` extracted from the
|
|
352
|
-
* UTXO stored on the `DepositRequest`. Withdrawal items include the
|
|
353
|
-
* BTC txid from the linked `WithdrawalTransaction` once the committee
|
|
354
|
-
* has committed one.
|
|
349
|
+
* a Sui address. Confirmed requests come from the on-chain
|
|
350
|
+
* `user_requests` index; in-flight deposits are discovered via
|
|
351
|
+
* GraphQL `DepositRequestedEvent` queries (indexed by sender).
|
|
355
352
|
*/
|
|
356
353
|
transactionHistory: (suiAddress: string) => Promise<TransactionHistoryItem[]>;
|
|
357
354
|
};
|
package/dist/client.mjs
CHANGED
|
@@ -21,8 +21,17 @@ import { Transaction, coinWithBalance } from "@mysten/sui/transactions";
|
|
|
21
21
|
const OBJECT_BAG_ADDRESS_TYPE = "0x0000000000000000000000000000000000000000000000000000000000000002::dynamic_object_field::Wrapper<address>";
|
|
22
22
|
/** Max value of an unsigned 32-bit integer; vout is a u32 on the Bitcoin side. */
|
|
23
23
|
const U32_MAX = 4294967295;
|
|
24
|
-
/** Max objects per `getObjects` call
|
|
25
|
-
const GET_OBJECTS_BATCH =
|
|
24
|
+
/** Max objects per `getObjects` call. */
|
|
25
|
+
const GET_OBJECTS_BATCH = 500;
|
|
26
|
+
const GRAPHQL_URLS = {
|
|
27
|
+
devnet: "https://fullnode.devnet.sui.io:443/graphql",
|
|
28
|
+
testnet: "https://fullnode.testnet.sui.io:443/graphql",
|
|
29
|
+
mainnet: "https://fullnode.mainnet.sui.io:443/graphql",
|
|
30
|
+
localnet: "http://127.0.0.1:9000/graphql"
|
|
31
|
+
};
|
|
32
|
+
function defaultGraphqlUrl(network) {
|
|
33
|
+
return GRAPHQL_URLS[network] ?? GRAPHQL_URLS["mainnet"];
|
|
34
|
+
}
|
|
26
35
|
/**
|
|
27
36
|
* Recognize the per-object errors that mean "the object (or dynamic field)
|
|
28
37
|
* genuinely does not exist" — the only Error shape that `findUsedUtxos` is
|
|
@@ -80,11 +89,12 @@ var HashiClient = class {
|
|
|
80
89
|
#packageId;
|
|
81
90
|
#bitcoinNetwork;
|
|
82
91
|
#btcRpcUrl;
|
|
83
|
-
|
|
92
|
+
#graphqlUrl;
|
|
93
|
+
constructor({ client, network, hashiObjectId, packageId, bitcoinNetwork, btcRpcUrl, graphqlUrl }) {
|
|
84
94
|
this.tx = {
|
|
85
95
|
deposit: (params) => {
|
|
86
96
|
const tx = new Transaction();
|
|
87
|
-
const internalTxid = reverseTxidBytes(params.txid)
|
|
97
|
+
const internalTxid = `0x${reverseTxidBytes(params.txid)}`;
|
|
88
98
|
for (const { vout, amountSats } of params.utxos) {
|
|
89
99
|
const utxoId$1 = tx.add(utxoId({
|
|
90
100
|
package: this.#packageId,
|
|
@@ -205,7 +215,7 @@ var HashiClient = class {
|
|
|
205
215
|
const fieldIds = [];
|
|
206
216
|
for (const u of utxos) {
|
|
207
217
|
const keyBcs = UtxoId.serialize({
|
|
208
|
-
txid: reverseTxidBytes(u.txid)
|
|
218
|
+
txid: `0x${reverseTxidBytes(u.txid)}`,
|
|
209
219
|
vout: u.vout
|
|
210
220
|
}).toBytes();
|
|
211
221
|
fieldIds.push(deriveDynamicFieldID(activePoolId, typeTag, keyBcs), deriveDynamicFieldID(spentPoolId, typeTag, keyBcs));
|
|
@@ -384,13 +394,29 @@ var HashiClient = class {
|
|
|
384
394
|
},
|
|
385
395
|
transactionHistory: async (suiAddress) => {
|
|
386
396
|
const btcState = await this.#fetchBitcoinState();
|
|
397
|
+
const confirmedIds = /* @__PURE__ */ new Set();
|
|
398
|
+
const items = [];
|
|
387
399
|
const userBagId = await this.#fetchUserRequestsBagId(btcState.user_requests.id, suiAddress);
|
|
388
|
-
if (userBagId
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
400
|
+
if (userBagId !== null) {
|
|
401
|
+
const requestIds = await this.#listAllDynamicFieldAddressKeys(userBagId);
|
|
402
|
+
if (requestIds.length > 0) {
|
|
403
|
+
const objects = await this.#batchGetObjects(requestIds, { content: true });
|
|
404
|
+
const classified = this.#classifyRequestObjects(objects);
|
|
405
|
+
items.push(...classified.items);
|
|
406
|
+
await this.#populateWithdrawalBtcTxids(items, classified.withdrawalTxnLookups);
|
|
407
|
+
for (const id of requestIds) confirmedIds.add(id);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
const depositEventType = `${this.#packageId}::deposit::DepositRequestedEvent`;
|
|
412
|
+
const pendingIds = (await this.#queryEventRequestIds(suiAddress, depositEventType)).filter((id) => !confirmedIds.has(id));
|
|
413
|
+
if (pendingIds.length > 0) {
|
|
414
|
+
const objects = await this.#batchGetObjects(pendingIds, { content: true });
|
|
415
|
+
const classified = this.#classifyRequestObjects(objects);
|
|
416
|
+
items.push(...classified.items);
|
|
417
|
+
}
|
|
418
|
+
} catch {}
|
|
419
|
+
items.sort((a, b) => Number(b.timestampMs - a.timestampMs));
|
|
394
420
|
return items;
|
|
395
421
|
}
|
|
396
422
|
};
|
|
@@ -417,6 +443,7 @@ var HashiClient = class {
|
|
|
417
443
|
this.#packageId = resolvedPackageId;
|
|
418
444
|
this.#bitcoinNetwork = bitcoinNetwork ?? config?.bitcoinNetwork ?? "testnet";
|
|
419
445
|
this.#btcRpcUrl = btcRpcUrl;
|
|
446
|
+
this.#graphqlUrl = graphqlUrl ?? defaultGraphqlUrl(network);
|
|
420
447
|
}
|
|
421
448
|
/**
|
|
422
449
|
* Generates a unique Bitcoin P2TR deposit address for a Sui address.
|
|
@@ -683,26 +710,8 @@ var HashiClient = class {
|
|
|
683
710
|
return BitcoinState.parse(new Uint8Array(dynamicField.value.bcs));
|
|
684
711
|
}
|
|
685
712
|
/**
|
|
686
|
-
* Batch-fetches objects in chunks of `GET_OBJECTS_BATCH`, concatenating
|
|
687
|
-
* the results. Each element is either an object or an `Error` (for
|
|
688
|
-
* missing / deleted objects).
|
|
689
|
-
*/
|
|
690
|
-
async #batchGetObjects(objectIds, include) {
|
|
691
|
-
const results = [];
|
|
692
|
-
for (let i = 0; i < objectIds.length; i += GET_OBJECTS_BATCH) {
|
|
693
|
-
const batch = objectIds.slice(i, i + GET_OBJECTS_BATCH);
|
|
694
|
-
const { objects } = await this.#client.core.getObjects({
|
|
695
|
-
objectIds: batch,
|
|
696
|
-
include
|
|
697
|
-
});
|
|
698
|
-
results.push(...objects);
|
|
699
|
-
}
|
|
700
|
-
return results;
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
713
|
* Resolve the Bag id holding a user's request IDs from `user_requests`,
|
|
704
|
-
* or `null` if the user has never had a request
|
|
705
|
-
* `getDynamicField` rejects rather than returning a sentinel).
|
|
714
|
+
* or `null` if the user has never had a request.
|
|
706
715
|
*/
|
|
707
716
|
async #fetchUserRequestsBagId(tableId, suiAddress) {
|
|
708
717
|
try {
|
|
@@ -720,8 +729,7 @@ var HashiClient = class {
|
|
|
720
729
|
}
|
|
721
730
|
/**
|
|
722
731
|
* Enumerate every `address`-keyed dynamic field on `parentId`, paginating
|
|
723
|
-
* through `listDynamicFields` until exhausted.
|
|
724
|
-
* a Sui address.
|
|
732
|
+
* through `listDynamicFields` until exhausted.
|
|
725
733
|
*/
|
|
726
734
|
async #listAllDynamicFieldAddressKeys(parentId) {
|
|
727
735
|
const keys = [];
|
|
@@ -737,6 +745,58 @@ var HashiClient = class {
|
|
|
737
745
|
return keys;
|
|
738
746
|
}
|
|
739
747
|
/**
|
|
748
|
+
* Batch-fetches objects in chunks of `GET_OBJECTS_BATCH`, concatenating
|
|
749
|
+
* the results. Each element is either an object or an `Error` (for
|
|
750
|
+
* missing / deleted objects).
|
|
751
|
+
*/
|
|
752
|
+
async #batchGetObjects(objectIds, include) {
|
|
753
|
+
const results = [];
|
|
754
|
+
for (let i = 0; i < objectIds.length; i += GET_OBJECTS_BATCH) {
|
|
755
|
+
const batch = objectIds.slice(i, i + GET_OBJECTS_BATCH);
|
|
756
|
+
const { objects } = await this.#client.core.getObjects({
|
|
757
|
+
objectIds: batch,
|
|
758
|
+
include
|
|
759
|
+
});
|
|
760
|
+
results.push(...objects);
|
|
761
|
+
}
|
|
762
|
+
return results;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Query the Sui GraphQL endpoint for events of a given type emitted by
|
|
766
|
+
* a sender. Returns the `request_id` from each event's JSON payload,
|
|
767
|
+
* paginating through all results.
|
|
768
|
+
*/
|
|
769
|
+
async #queryEventRequestIds(sender, eventType) {
|
|
770
|
+
const ids = [];
|
|
771
|
+
let cursor = null;
|
|
772
|
+
let hasMore = true;
|
|
773
|
+
while (hasMore) {
|
|
774
|
+
const query = `{
|
|
775
|
+
events(
|
|
776
|
+
filter: { sender: "${sender}", type: "${eventType}" }
|
|
777
|
+
first: 50${cursor ? `, after: "${cursor}"` : ""}
|
|
778
|
+
) {
|
|
779
|
+
nodes { contents { json } }
|
|
780
|
+
pageInfo { hasNextPage endCursor }
|
|
781
|
+
}
|
|
782
|
+
}`;
|
|
783
|
+
const res = await fetch(this.#graphqlUrl, {
|
|
784
|
+
method: "POST",
|
|
785
|
+
headers: { "Content-Type": "application/json" },
|
|
786
|
+
body: JSON.stringify({ query })
|
|
787
|
+
});
|
|
788
|
+
if (!res.ok) throw new Error(`GraphQL request failed: ${res.status}`);
|
|
789
|
+
const body = await res.json();
|
|
790
|
+
if (body.errors?.length) throw new Error(`GraphQL error: ${body.errors[0].message}`);
|
|
791
|
+
const events = body.data?.events;
|
|
792
|
+
if (!events) break;
|
|
793
|
+
for (const node of events.nodes) ids.push(node.contents.json.request_id);
|
|
794
|
+
hasMore = events.pageInfo.hasNextPage;
|
|
795
|
+
cursor = events.pageInfo.endCursor ?? null;
|
|
796
|
+
}
|
|
797
|
+
return ids;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
740
800
|
* Classify a batch of request objects into deposit / withdrawal history
|
|
741
801
|
* items. Errors in the batch are skipped (the request object may have
|
|
742
802
|
* been deleted between the bag enumeration and the fetch). Returns the
|
package/dist/types.d.mts
CHANGED
|
@@ -18,6 +18,8 @@ interface HashiClientOptions<Name = "HashiClient"> {
|
|
|
18
18
|
bitcoinNetwork?: BitcoinNetwork;
|
|
19
19
|
/** Optional Bitcoin Core JSON-RPC URL for UTXO lookups and confirmation checks. */
|
|
20
20
|
btcRpcUrl?: string;
|
|
21
|
+
/** Override the Sui GraphQL endpoint URL (defaults to `https://fullnode.{network}.sui.io:443/graphql`). */
|
|
22
|
+
graphqlUrl?: string;
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
23
25
|
* Frozen snapshot of every governance-controlled protocol parameter, returned
|
package/dist/util.mjs
CHANGED
|
@@ -29,7 +29,7 @@ function assertHex32(value, fieldName) {
|
|
|
29
29
|
function reverseTxidBytes(txid) {
|
|
30
30
|
assertHex32(txid, "txid");
|
|
31
31
|
const hex = txid.slice(2);
|
|
32
|
-
let reversed = "
|
|
32
|
+
let reversed = "";
|
|
33
33
|
for (let i = hex.length - 2; i >= 0; i -= 2) reversed += hex.slice(i, i + 2);
|
|
34
34
|
return reversed;
|
|
35
35
|
}
|