@boostxyz/sdk 5.3.0 → 5.5.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.
@@ -6,8 +6,8 @@ import {
6
6
  writeEventActionExecute,
7
7
  } from '@boostxyz/evm';
8
8
  import { bytecode } from '@boostxyz/evm/artifacts/contracts/actions/EventAction.sol/EventAction.json';
9
+ import { abi } from '@boostxyz/signatures/events';
9
10
  import { getTransaction, getTransactionReceipt } from '@wagmi/core';
10
- import type { AbiEventParameter } from 'abitype';
11
11
  import { match } from 'ts-pattern';
12
12
  import {
13
13
  type AbiEvent,
@@ -26,7 +26,9 @@ import {
26
26
  fromHex,
27
27
  isAddress,
28
28
  isAddressEqual,
29
+ pad,
29
30
  toEventSelector,
31
+ trim,
30
32
  zeroAddress,
31
33
  zeroHash,
32
34
  } from 'viem';
@@ -157,7 +159,7 @@ export interface ActionClaimant {
157
159
  *
158
160
  * @type {SignatureType}
159
161
  */
160
- signatureType: SignatureType;
162
+ signatureType?: SignatureType;
161
163
  /**
162
164
  * The 4 byte signature of the event or function
163
165
  *
@@ -202,7 +204,7 @@ export interface ActionStep {
202
204
  *
203
205
  * @type {SignatureType}
204
206
  */
205
- signatureType: SignatureType;
207
+ signatureType?: SignatureType;
206
208
  /**
207
209
  * The type of action being performed.
208
210
  *
@@ -585,9 +587,15 @@ export class EventAction extends DeployableTarget<
585
587
  ) {
586
588
  return undefined;
587
589
  }
588
- const decodedLogs = receipt.logs
589
- .filter((log) => log.topics[0] === toEventSelector(event))
590
- .map((log) => decodeAndReorderLogArgs(event, log));
590
+
591
+ let decodedLogs: EventLogs;
592
+ if (signature === TRANSFER_SIGNATURE) {
593
+ ({ decodedLogs } = await this.decodeTransferLogs(receipt));
594
+ } else {
595
+ decodedLogs = receipt.logs
596
+ .filter((log) => log.topics[0] === toEventSelector(event))
597
+ .map((log) => decodeAndReorderLogArgs(event, log));
598
+ }
591
599
 
592
600
  for (let log of decodedLogs) {
593
601
  if (!isAddressEqual(log.address, claimant.targetContract)) continue;
@@ -728,7 +736,8 @@ export class EventAction extends DeployableTarget<
728
736
 
729
737
  // Special handling for Transfer events
730
738
  if (actionStep.signature === TRANSFER_SIGNATURE) {
731
- return this.decodeTransferLogs(receipt, actionStep);
739
+ const { decodedLogs, event } = await this.decodeTransferLogs(receipt);
740
+ return this.isActionEventValid(actionStep, decodedLogs, event);
732
741
  }
733
742
 
734
743
  const decodedLogs = receipt.logs
@@ -801,7 +810,7 @@ export class EventAction extends DeployableTarget<
801
810
  }
802
811
 
803
812
  /**
804
- * Decodes transfer logs specifically for ERC721 and ERC20 Transfer events.
813
+ * Decodes logs specifically for ERC721 and ERC20 Transfer events.
805
814
  *
806
815
  * This special handling is required because both ERC20 and ERC721 Transfer events:
807
816
  * 1. Share the same event signature (0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef)
@@ -813,79 +822,51 @@ export class EventAction extends DeployableTarget<
813
822
  * try decoding both ways to determine which type of Transfer event we're dealing with.
814
823
  *
815
824
  * @param {GetTransactionReceiptReturnType} receipt - The transaction receipt containing the logs
816
- * @param {ActionStep} actionStep - The action step being validated
817
- * @returns {Promise<boolean>} - Returns true if the transfer logs are valid for either ERC20 or ERC721
825
+ * @returns {Promise<{ decodedLogs: EventLogs; event: AbiEvent }>} - Returns the decoded logs and the transfer event ABI used for decoding
818
826
  * @throws {DecodedArgsError} - Throws if neither ERC20 nor ERC721 decoding succeeds
819
827
  */
820
828
  private async decodeTransferLogs(
821
829
  receipt: GetTransactionReceiptReturnType,
822
- actionStep: ActionStep,
823
- ) {
830
+ ): Promise<{ decodedLogs: EventLogs; event: AbiEvent }> {
824
831
  const filteredLogs = receipt.logs.filter(
825
832
  (log) => log.topics[0] === TRANSFER_SIGNATURE,
826
833
  );
834
+ const event = structuredClone(
835
+ abi['Transfer(address indexed,address indexed,uint256 indexed)'],
836
+ ) as AbiEvent;
827
837
 
828
838
  // ERC721
829
839
  try {
830
840
  const decodedLogs = filteredLogs.map((log) => {
831
841
  const { eventName, args } = decodeEventLog({
832
- abi: [
833
- {
834
- name: 'Transfer',
835
- type: 'event',
836
- inputs: [
837
- { type: 'address', indexed: true },
838
- { type: 'address', indexed: true },
839
- { type: 'uint256', indexed: true },
840
- ],
841
- },
842
- ],
842
+ abi: [event],
843
843
  data: log.data,
844
844
  topics: log.topics,
845
845
  });
846
846
  return { ...log, eventName, args };
847
847
  });
848
848
 
849
- return this.isActionEventValid(actionStep, decodedLogs, {
850
- name: 'Transfer',
851
- type: 'event',
852
- inputs: [
853
- { type: 'address', indexed: true },
854
- { type: 'address', indexed: true },
855
- { type: 'uint256', indexed: true },
856
- ],
857
- });
849
+ return {
850
+ decodedLogs,
851
+ event,
852
+ };
858
853
  } catch {
859
854
  // ERC20
860
855
  try {
856
+ event.inputs[2]!.indexed = false;
861
857
  const decodedLogs = filteredLogs.map((log) => {
862
858
  const { eventName, args } = decodeEventLog({
863
- abi: [
864
- {
865
- name: 'Transfer',
866
- type: 'event',
867
- inputs: [
868
- { type: 'address', indexed: true },
869
- { type: 'address', indexed: true },
870
- { type: 'uint256' },
871
- ],
872
- },
873
- ],
859
+ abi: [event],
874
860
  data: log.data,
875
861
  topics: log.topics,
876
862
  });
877
863
  return { ...log, eventName, args };
878
864
  });
879
865
 
880
- return this.isActionEventValid(actionStep, decodedLogs, {
881
- name: 'Transfer',
882
- type: 'event',
883
- inputs: [
884
- { type: 'address', indexed: true },
885
- { type: 'address', indexed: true },
886
- { type: 'uint256' },
887
- ],
888
- });
866
+ return {
867
+ decodedLogs,
868
+ event,
869
+ };
889
870
  } catch {
890
871
  throw new DecodedArgsError('Failed to decode transfer logs');
891
872
  }
@@ -1398,6 +1379,72 @@ function _isEventActionPayloadSimple(
1398
1379
  return Array.isArray((opts as EventActionPayloadSimple).actionSteps);
1399
1380
  }
1400
1381
 
1382
+ /**
1383
+ * Determines whether a signature is an event or function signature based on its format.
1384
+ * - 32-byte signatures (0x + 64 chars) that don't start with 28 zeros are event signatures
1385
+ * - 4-byte signatures (0x + 8 chars) or 32-byte signatures with 28 leading zeros are function signatures
1386
+ *
1387
+ * @param {Hex} signature - The signature to check
1388
+ * @returns {SignatureType} The detected signature type
1389
+ */
1390
+ export function detectSignatureType(signature: Hex): SignatureType {
1391
+ const hexWithoutPrefix = signature.slice(2);
1392
+
1393
+ // 4-byte function selectors (8 hex chars)
1394
+ if (hexWithoutPrefix.length === 8) {
1395
+ return SignatureType.FUNC;
1396
+ }
1397
+
1398
+ // 32-byte selectors (64 hex chars)
1399
+ if (hexWithoutPrefix.length === 64) {
1400
+ // Check if it starts with 28 bytes (56 chars) of zeros
1401
+ const leadingPart = hexWithoutPrefix.slice(0, 56);
1402
+ if (leadingPart === '0'.repeat(56)) {
1403
+ return SignatureType.FUNC;
1404
+ }
1405
+ return SignatureType.EVENT;
1406
+ }
1407
+
1408
+ throw new Error('Invalid signature format');
1409
+ }
1410
+
1411
+ /**
1412
+ * Normalizes a hex value to ensure proper byte padding.
1413
+ * This prevents viem's automatic padding which can change the value.
1414
+ * For example:
1415
+ * - "0x1" -> "0x01"
1416
+ * - "0xabc" -> "0x0abc"
1417
+ * - "0xabcd" -> "0xabcd"
1418
+ *
1419
+ * @param {Hex} value - The hex value to normalize
1420
+ * @returns {Hex} The normalized hex string
1421
+ */
1422
+ function normalizeUintValue(value: Hex): Hex {
1423
+ return trim(pad(value));
1424
+ }
1425
+
1426
+ /**
1427
+ * Helper function to prepare an action step for encoding
1428
+ *
1429
+ * @param {ActionStep} step - The action step to prepare
1430
+ * @returns {ActionStep} The prepared action step
1431
+ */
1432
+ function prepareActionStep(step: ActionStep) {
1433
+ return {
1434
+ ..._toRawActionStep(step),
1435
+ signatureType: step.signatureType ?? detectSignatureType(step.signature),
1436
+ signature: pad(step.signature),
1437
+ actionType: step.actionType || 0,
1438
+ actionParameter:
1439
+ step.actionParameter.fieldType === PrimitiveType.UINT
1440
+ ? {
1441
+ ...step.actionParameter,
1442
+ filterData: normalizeUintValue(step.actionParameter.filterData),
1443
+ }
1444
+ : step.actionParameter,
1445
+ };
1446
+ }
1447
+
1401
1448
  /**
1402
1449
  * Function to properly encode an event action payload.
1403
1450
  *
@@ -1523,23 +1570,17 @@ export function prepareEventActionPayload({
1523
1570
  ],
1524
1571
  [
1525
1572
  {
1526
- actionClaimant: _toRawActionStep(actionClaimant),
1527
- actionStepOne: {
1528
- ..._toRawActionStep(actionStepOne),
1529
- actionType: actionStepOne.actionType || 0,
1530
- },
1531
- actionStepTwo: {
1532
- ..._toRawActionStep(actionStepTwo),
1533
- actionType: actionStepTwo.actionType || 0,
1534
- },
1535
- actionStepThree: {
1536
- ..._toRawActionStep(actionStepThree),
1537
- actionType: actionStepThree.actionType || 0,
1538
- },
1539
- actionStepFour: {
1540
- ..._toRawActionStep(actionStepFour),
1541
- actionType: actionStepFour.actionType || 0,
1573
+ actionClaimant: {
1574
+ ..._toRawActionStep(actionClaimant),
1575
+ signatureType:
1576
+ actionClaimant.signatureType ??
1577
+ detectSignatureType(actionClaimant.signature),
1578
+ signature: pad(actionClaimant.signature),
1542
1579
  },
1580
+ actionStepOne: prepareActionStep(actionStepOne),
1581
+ actionStepTwo: prepareActionStep(actionStepTwo),
1582
+ actionStepThree: prepareActionStep(actionStepThree),
1583
+ actionStepFour: prepareActionStep(actionStepFour),
1543
1584
  },
1544
1585
  ],
1545
1586
  );
@@ -1632,6 +1673,9 @@ export function packFieldIndexes(indexes: number[]): number {
1632
1673
  }
1633
1674
  packed |= (index & MAX_FIELD_INDEX) << (i * 6); // Each index occupies 6 bits
1634
1675
  });
1676
+ if (indexes.length < 5) {
1677
+ packed |= MAX_FIELD_INDEX << (indexes.length * 6); // Terminator
1678
+ }
1635
1679
 
1636
1680
  return packed;
1637
1681
  }
@@ -1672,7 +1716,10 @@ export function decodeAndReorderLogArgs(event: AbiEvent, log: Log) {
1672
1716
  : Object.values(decodedLog.args);
1673
1717
 
1674
1718
  if (!event.inputs.some((input) => input.indexed)) {
1675
- return decodedLog as EventLog;
1719
+ return {
1720
+ ...log,
1721
+ ...decodedLog,
1722
+ } as EventLog;
1676
1723
  }
1677
1724
 
1678
1725
  const indexedIndices: number[] = [];
@@ -1685,7 +1732,7 @@ export function decodeAndReorderLogArgs(event: AbiEvent, log: Log) {
1685
1732
  }
1686
1733
  }
1687
1734
 
1688
- const reorderedArgs = new Array(event.inputs.length);
1735
+ const reorderedArgs = Array.from({ length: event.inputs.length });
1689
1736
  let currentIndex = 0;
1690
1737
 
1691
1738
  // Place the indexed arguments in their original positions