@0xslots/sdk 0.5.0 → 0.6.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/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { gql as gql$1, GraphQLClient } from 'graphql-request';
2
2
  import gql from 'graphql-tag';
3
+ import { encodeFunctionData, erc20Abi } from 'viem';
4
+ import { getSlotsHubAddress, slotAbi, slotFactoryAbi } from '@0xslots/contracts';
3
5
 
4
6
  // src/client.ts
5
7
  var AccountFieldsFragmentDoc = gql`
@@ -32,7 +34,12 @@ var SlotFieldsFragmentDoc = gql`
32
34
  mutableTax
33
35
  mutableModule
34
36
  taxPercentage
35
- module
37
+ module {
38
+ id
39
+ verified
40
+ name
41
+ version
42
+ }
36
43
  occupant
37
44
  occupantAccount {
38
45
  id
@@ -147,6 +154,40 @@ var GetRecentEventsDocument = gql`
147
154
  timestamp
148
155
  tx
149
156
  }
157
+ taxUpdateProposedEvents(first: $first, orderBy: timestamp, orderDirection: desc) {
158
+ id
159
+ slot {
160
+ id
161
+ }
162
+ newPercentage
163
+ timestamp
164
+ tx
165
+ }
166
+ moduleUpdateProposedEvents(
167
+ first: $first
168
+ orderBy: timestamp
169
+ orderDirection: desc
170
+ ) {
171
+ id
172
+ slot {
173
+ id
174
+ }
175
+ newModule
176
+ timestamp
177
+ tx
178
+ }
179
+ pendingUpdateCancelledEvents(
180
+ first: $first
181
+ orderBy: timestamp
182
+ orderDirection: desc
183
+ ) {
184
+ id
185
+ slot {
186
+ id
187
+ }
188
+ timestamp
189
+ tx
190
+ }
150
191
  }
151
192
  `;
152
193
  var GetBoughtEventsDocument = gql`
@@ -420,6 +461,38 @@ var GetSlotActivityDocument = gql`
420
461
  timestamp
421
462
  tx
422
463
  }
464
+ taxUpdateProposedEvents(
465
+ first: $first
466
+ orderBy: timestamp
467
+ orderDirection: desc
468
+ where: {slot: $slotId}
469
+ ) {
470
+ id
471
+ newPercentage
472
+ timestamp
473
+ tx
474
+ }
475
+ moduleUpdateProposedEvents(
476
+ first: $first
477
+ orderBy: timestamp
478
+ orderDirection: desc
479
+ where: {slot: $slotId}
480
+ ) {
481
+ id
482
+ newModule
483
+ timestamp
484
+ tx
485
+ }
486
+ pendingUpdateCancelledEvents(
487
+ first: $first
488
+ orderBy: timestamp
489
+ orderDirection: desc
490
+ where: {slot: $slotId}
491
+ ) {
492
+ id
493
+ timestamp
494
+ tx
495
+ }
423
496
  }
424
497
  `;
425
498
  var GetFactoryDocument = gql`
@@ -548,8 +621,6 @@ function getSdk(client, withWrapper = defaultWrapper) {
548
621
  }
549
622
  };
550
623
  }
551
-
552
- // src/client.ts
553
624
  var META_QUERY = gql$1`
554
625
  query GetMeta {
555
626
  _meta {
@@ -568,26 +639,50 @@ var SlotsChain = /* @__PURE__ */ ((SlotsChain2) => {
568
639
  return SlotsChain2;
569
640
  })(SlotsChain || {});
570
641
  var SUBGRAPH_URLS = {
571
- [84532 /* BASE_SEPOLIA */]: "https://api.studio.thegraph.com/query/958/0-x-slots-base-sepolia/v3.5.0",
642
+ [84532 /* BASE_SEPOLIA */]: "https://api.studio.thegraph.com/query/958/0-x-slots-base-sepolia/version/latest",
572
643
  [42161 /* ARBITRUM */]: "https://api.studio.thegraph.com/query/958/0-x-slots-arb/version/latest"
573
644
  };
574
645
  var SlotsClient = class {
575
646
  constructor(config) {
576
647
  this.chainId = config.chainId;
648
+ this._publicClient = config.publicClient;
649
+ this.walletClient = config.walletClient;
650
+ this._factory = config.factoryAddress ?? getSlotsHubAddress(config.chainId);
577
651
  const url = config.subgraphUrl || SUBGRAPH_URLS[config.chainId];
578
652
  if (!url) throw new Error(`No subgraph URL for chain ${config.chainId}`);
579
- this.client = new GraphQLClient(url, { headers: config.headers });
580
- this.sdk = getSdk(this.client);
653
+ this.gqlClient = new GraphQLClient(url, { headers: config.headers });
654
+ this.sdk = getSdk(this.gqlClient);
581
655
  }
656
+ // ─── Accessors ──────────────────────────────────────────────────────────────
582
657
  getChainId() {
583
658
  return this.chainId;
584
659
  }
585
660
  getClient() {
586
- return this.client;
661
+ return this.gqlClient;
587
662
  }
588
663
  getSdk() {
589
664
  return this.sdk;
590
665
  }
666
+ get publicClient() {
667
+ if (!this._publicClient) throw new Error("No publicClient provided");
668
+ return this._publicClient;
669
+ }
670
+ get factory() {
671
+ if (!this._factory) throw new Error("No factoryAddress provided");
672
+ return this._factory;
673
+ }
674
+ get wallet() {
675
+ if (!this.walletClient) throw new Error("No walletClient provided");
676
+ return this.walletClient;
677
+ }
678
+ get account() {
679
+ const account = this.wallet.account;
680
+ if (!account) throw new Error("WalletClient must have an account");
681
+ return account.address;
682
+ }
683
+ // ═══════════════════════════════════════════════════════════════════════════
684
+ // READ — Subgraph Queries
685
+ // ═══════════════════════════════════════════════════════════════════════════
591
686
  // Slot queries
592
687
  getSlots(...args) {
593
688
  return this.sdk.GetSlots(...args);
@@ -631,7 +726,7 @@ var SlotsClient = class {
631
726
  getAccounts(...args) {
632
727
  return this.sdk.GetAccounts(...args);
633
728
  }
634
- // Individual event queries (full params)
729
+ // Individual event queries
635
730
  getReleasedEvents(...args) {
636
731
  return this.sdk.GetReleasedEvents(...args);
637
732
  }
@@ -649,7 +744,278 @@ var SlotsClient = class {
649
744
  }
650
745
  // Meta
651
746
  getMeta() {
652
- return this.client.request(META_QUERY);
747
+ return this.gqlClient.request(META_QUERY);
748
+ }
749
+ // ═══════════════════════════════════════════════════════════════════════════
750
+ // READ — On-chain (RPC)
751
+ // ═══════════════════════════════════════════════════════════════════════════
752
+ /** Read full slot info from on-chain (RPC, not subgraph). */
753
+ async getSlotInfo(slot) {
754
+ return this.publicClient.readContract({
755
+ address: slot,
756
+ abi: slotAbi,
757
+ functionName: "getSlotInfo"
758
+ });
759
+ }
760
+ // ═══════════════════════════════════════════════════════════════════════════
761
+ // WRITE — Factory Functions
762
+ // ═══════════════════════════════════════════════════════════════════════════
763
+ async createSlot(params) {
764
+ return this.wallet.writeContract({
765
+ address: this.factory,
766
+ abi: slotFactoryAbi,
767
+ functionName: "createSlot",
768
+ args: [params.recipient, params.currency, params.config, params.initParams],
769
+ account: this.wallet.account,
770
+ chain: this.wallet.chain
771
+ });
772
+ }
773
+ async createSlots(params) {
774
+ return this.wallet.writeContract({
775
+ address: this.factory,
776
+ abi: slotFactoryAbi,
777
+ functionName: "createSlots",
778
+ args: [params.recipient, params.currency, params.config, params.initParams, params.count],
779
+ account: this.wallet.account,
780
+ chain: this.wallet.chain
781
+ });
782
+ }
783
+ // ═══════════════════════════════════════════════════════════════════════════
784
+ // WRITE — Slot Functions
785
+ // ═══════════════════════════════════════════════════════════════════════════
786
+ /** Buy a slot (or force buy an occupied one). Handles ERC-20 approval automatically. */
787
+ async buy(params) {
788
+ return this.withAllowance(params.slot, params.depositAmount, {
789
+ to: params.slot,
790
+ abi: slotAbi,
791
+ functionName: "buy",
792
+ args: [params.depositAmount, params.selfAssessedPrice]
793
+ });
794
+ }
795
+ /** Self-assess a new price for an occupied slot (occupant only). */
796
+ async selfAssess(slot, newPrice) {
797
+ return this.wallet.writeContract({
798
+ address: slot,
799
+ abi: slotAbi,
800
+ functionName: "selfAssess",
801
+ args: [newPrice],
802
+ account: this.wallet.account,
803
+ chain: this.wallet.chain
804
+ });
805
+ }
806
+ /** Top up deposit on a slot (occupant only). Handles ERC-20 approval automatically. */
807
+ async topUp(slot, amount) {
808
+ return this.withAllowance(slot, amount, {
809
+ to: slot,
810
+ abi: slotAbi,
811
+ functionName: "topUp",
812
+ args: [amount]
813
+ });
814
+ }
815
+ /** Withdraw from deposit (occupant only). Cannot go below minimum deposit. */
816
+ async withdraw(slot, amount) {
817
+ return this.wallet.writeContract({
818
+ address: slot,
819
+ abi: slotAbi,
820
+ functionName: "withdraw",
821
+ args: [amount],
822
+ account: this.wallet.account,
823
+ chain: this.wallet.chain
824
+ });
825
+ }
826
+ /** Release a slot (occupant only). Returns remaining deposit. */
827
+ async release(slot) {
828
+ return this.wallet.writeContract({
829
+ address: slot,
830
+ abi: slotAbi,
831
+ functionName: "release",
832
+ account: this.wallet.account,
833
+ chain: this.wallet.chain
834
+ });
835
+ }
836
+ /** Collect accumulated tax (permissionless). */
837
+ async collect(slot) {
838
+ return this.wallet.writeContract({
839
+ address: slot,
840
+ abi: slotAbi,
841
+ functionName: "collect",
842
+ account: this.wallet.account,
843
+ chain: this.wallet.chain
844
+ });
845
+ }
846
+ /** Liquidate an insolvent slot (permissionless). Caller receives bounty. */
847
+ async liquidate(slot) {
848
+ return this.wallet.writeContract({
849
+ address: slot,
850
+ abi: slotAbi,
851
+ functionName: "liquidate",
852
+ account: this.wallet.account,
853
+ chain: this.wallet.chain
854
+ });
855
+ }
856
+ // ═══════════════════════════════════════════════════════════════════════════
857
+ // WRITE — Manager Functions
858
+ // ═══════════════════════════════════════════════════════════════════════════
859
+ /** Propose a tax rate update (manager only, slot must have mutableTax). */
860
+ async proposeTaxUpdate(slot, newPct) {
861
+ return this.wallet.writeContract({
862
+ address: slot,
863
+ abi: slotAbi,
864
+ functionName: "proposeTaxUpdate",
865
+ args: [newPct],
866
+ account: this.wallet.account,
867
+ chain: this.wallet.chain
868
+ });
869
+ }
870
+ /** Propose a module update (manager only, slot must have mutableModule). */
871
+ async proposeModuleUpdate(slot, newModule) {
872
+ return this.wallet.writeContract({
873
+ address: slot,
874
+ abi: slotAbi,
875
+ functionName: "proposeModuleUpdate",
876
+ args: [newModule],
877
+ account: this.wallet.account,
878
+ chain: this.wallet.chain
879
+ });
880
+ }
881
+ /** Cancel pending updates (manager only). */
882
+ async cancelPendingUpdates(slot) {
883
+ return this.wallet.writeContract({
884
+ address: slot,
885
+ abi: slotAbi,
886
+ functionName: "cancelPendingUpdates",
887
+ account: this.wallet.account,
888
+ chain: this.wallet.chain
889
+ });
890
+ }
891
+ /** Set liquidation bounty bps (manager only). */
892
+ async setLiquidationBounty(slot, newBps) {
893
+ return this.wallet.writeContract({
894
+ address: slot,
895
+ abi: slotAbi,
896
+ functionName: "setLiquidationBounty",
897
+ args: [newBps],
898
+ account: this.wallet.account,
899
+ chain: this.wallet.chain
900
+ });
901
+ }
902
+ // ═══════════════════════════════════════════════════════════════════════════
903
+ // WRITE — Multicall
904
+ // ═══════════════════════════════════════════════════════════════════════════
905
+ /** Batch multiple slot calls into one transaction via multicall. */
906
+ async multicall(slot, calls) {
907
+ const data = calls.map(
908
+ (call) => encodeFunctionData({
909
+ abi: slotAbi,
910
+ functionName: call.functionName,
911
+ args: call.args
912
+ })
913
+ );
914
+ return this.wallet.writeContract({
915
+ address: slot,
916
+ abi: slotAbi,
917
+ functionName: "multicall",
918
+ args: [data],
919
+ account: this.wallet.account,
920
+ chain: this.wallet.chain
921
+ });
922
+ }
923
+ // ─── Internals ──────────────────────────────────────────────────────────────
924
+ /** Check if wallet supports atomic batch calls (EIP-5792). */
925
+ async supportsAtomicBatch() {
926
+ if (this._atomicSupport !== void 0) return this._atomicSupport;
927
+ try {
928
+ const capabilities = await this.wallet.getCapabilities?.();
929
+ if (!capabilities) {
930
+ this._atomicSupport = false;
931
+ return false;
932
+ }
933
+ const chainId = this.wallet.chain.id;
934
+ const chainCaps = capabilities[chainId] || capabilities[`0x${chainId.toString(16)}`];
935
+ const atomic = chainCaps?.atomicBatch ?? chainCaps?.atomic;
936
+ const status = atomic && typeof atomic === "object" && "status" in atomic ? atomic.status : void 0;
937
+ this._atomicSupport = status === "supported" || status === "ready";
938
+ } catch {
939
+ this._atomicSupport = false;
940
+ }
941
+ return this._atomicSupport;
942
+ }
943
+ /** Poll EIP-5792 getCallsStatus until the batch settles, then return a tx hash. */
944
+ async pollBatchReceipt(id) {
945
+ const MAX_ATTEMPTS = 60;
946
+ const INTERVAL = 1500;
947
+ for (let i = 0; i < MAX_ATTEMPTS; i++) {
948
+ const result = await this.wallet.getCallsStatus({ id });
949
+ if (result.status === "success") {
950
+ const receipts = result.receipts ?? [];
951
+ return receipts[receipts.length - 1]?.transactionHash ?? id;
952
+ }
953
+ if (result.status === "failure") {
954
+ throw new Error("Batch transaction reverted");
955
+ }
956
+ await new Promise((r) => setTimeout(r, INTERVAL));
957
+ }
958
+ throw new Error("Batch transaction timed out");
959
+ }
960
+ /**
961
+ * Execute a contract call that needs ERC-20 allowance.
962
+ * If wallet supports atomic batch (EIP-5792): sends approve + action as one atomic call.
963
+ * Otherwise: chains approve tx (if needed) then action tx.
964
+ */
965
+ async withAllowance(spender, amount, call) {
966
+ const currency = await this.publicClient.readContract({
967
+ address: spender,
968
+ abi: slotAbi,
969
+ functionName: "currency"
970
+ });
971
+ const allowance = await this.publicClient.readContract({
972
+ address: currency,
973
+ abi: erc20Abi,
974
+ functionName: "allowance",
975
+ args: [this.account, spender]
976
+ });
977
+ const needsApproval = allowance < amount;
978
+ if (needsApproval && await this.supportsAtomicBatch()) {
979
+ const approveData = encodeFunctionData({
980
+ abi: erc20Abi,
981
+ functionName: "approve",
982
+ args: [spender, amount]
983
+ });
984
+ const actionData = encodeFunctionData({
985
+ abi: call.abi,
986
+ functionName: call.functionName,
987
+ args: call.args
988
+ });
989
+ const id = await this.wallet.sendCalls({
990
+ account: this.wallet.account,
991
+ chain: this.wallet.chain,
992
+ calls: [
993
+ { to: currency, data: approveData },
994
+ { to: call.to, data: actionData }
995
+ ]
996
+ });
997
+ const txHash = await this.pollBatchReceipt(id);
998
+ return txHash;
999
+ }
1000
+ if (needsApproval) {
1001
+ const hash = await this.wallet.writeContract({
1002
+ address: currency,
1003
+ abi: erc20Abi,
1004
+ functionName: "approve",
1005
+ args: [spender, amount],
1006
+ account: this.wallet.account,
1007
+ chain: this.wallet.chain
1008
+ });
1009
+ await this.publicClient.waitForTransactionReceipt({ hash });
1010
+ }
1011
+ return this.wallet.writeContract({
1012
+ address: call.to,
1013
+ abi: call.abi,
1014
+ functionName: call.functionName,
1015
+ args: call.args,
1016
+ account: this.wallet.account,
1017
+ chain: this.wallet.chain
1018
+ });
653
1019
  }
654
1020
  };
655
1021
  function createSlotsClient(config) {