@aboutcircles/sdk-utils 0.1.25 → 0.1.26

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/abi.d.ts CHANGED
@@ -52,4 +52,11 @@ export declare function decodeErrorResult(config: {
52
52
  * ```
53
53
  */
54
54
  export declare function encodeAbiParameters(types: string[], values: unknown[]): Hex;
55
+ /**
56
+ * Decode ABI-encoded parameters given their type strings.
57
+ * @param types - Array of Solidity type strings (e.g. ['address', 'uint256'])
58
+ * @param data - Hex-encoded data (with or without 0x prefix), without selector
59
+ * @returns Array of decoded values
60
+ */
61
+ export declare function decodeAbiParameters(types: string[], data: string): unknown[];
55
62
  //# sourceMappingURL=abi.d.ts.map
package/dist/abi.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"abi.d.ts","sourceRoot":"","sources":["../src/abi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAe,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAwCnD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASvD;AAoXD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,GAAG,EAAE,GAAG,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC3B,GAAG,GAAG,CA+DN;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,GAAG,EAAE,GAAG,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CA0CV;AAmBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;CACd,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,GAAG,IAAI,CA+C7C;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,CA+C3E"}
1
+ {"version":3,"file":"abi.d.ts","sourceRoot":"","sources":["../src/abi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAe,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAwCnD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASvD;AAoXD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,GAAG,EAAE,GAAG,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC3B,GAAG,GAAG,CA+DN;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,GAAG,EAAE,GAAG,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CA0CV;AAmBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;CACd,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,GAAG,IAAI,CA+C7C;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,CA+C3E;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAY5E"}
package/dist/abi.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { keccak_256 } from '@noble/hashes/sha3.js';
2
- import { bytesToHex } from './bytes';
2
+ import { bytesToHex } from './bytes.js';
3
3
  // ============================================================================
4
4
  // CONSTANTS & UTILITIES
5
5
  // ============================================================================
@@ -511,3 +511,20 @@ export function encodeAbiParameters(types, values) {
511
511
  }
512
512
  return ('0x' + result + dynamicData.join(''));
513
513
  }
514
+ /**
515
+ * Decode ABI-encoded parameters given their type strings.
516
+ * @param types - Array of Solidity type strings (e.g. ['address', 'uint256'])
517
+ * @param data - Hex-encoded data (with or without 0x prefix), without selector
518
+ * @returns Array of decoded values
519
+ */
520
+ export function decodeAbiParameters(types, data) {
521
+ const cleanData = data.startsWith('0x') ? data.slice(2) : data;
522
+ const results = [];
523
+ let offset = 0;
524
+ for (const type of types) {
525
+ const result = decodeParam(type, cleanData, offset);
526
+ results.push(result.value);
527
+ offset += result.consumed;
528
+ }
529
+ return results;
530
+ }
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import type { Abi } from 'abitype';
6
6
  import type { DecodedContractError } from '@aboutcircles/sdk-types';
7
- import { CirclesError } from './errors';
7
+ import { CirclesError } from './errors.js';
8
8
  /**
9
9
  * Parse contract error from a transaction error
10
10
  *
@@ -1 +1 @@
1
- {"version":3,"file":"contractErrors.d.ts","sourceRoot":"","sources":["../src/contractErrors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAEpE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AA6HxC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,GAAG,EACV,GAAG,EAAE,GAAG,GACP,oBAAoB,GAAG,IAAI,CA4B7B;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,YAAY,CAAC,MAAM,CAAC;IACrD,SAAgB,YAAY,CAAC,EAAE,oBAAoB,CAAC;gBAGlD,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QACvB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC9B,YAAY,CAAC,EAAE,oBAAoB,CAAC;KACrC;IAMH;;OAEG;IACH,MAAM,CAAC,oBAAoB,CACzB,KAAK,EAAE,GAAG,EACV,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,aAAa;IA8BhB;;OAEG;IACH,QAAQ,IAAI,MAAM;CAcnB"}
1
+ {"version":3,"file":"contractErrors.d.ts","sourceRoot":"","sources":["../src/contractErrors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAEpE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA6H3C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,GAAG,EACV,GAAG,EAAE,GAAG,GACP,oBAAoB,GAAG,IAAI,CA4B7B;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,YAAY,CAAC,MAAM,CAAC;IACrD,SAAgB,YAAY,CAAC,EAAE,oBAAoB,CAAC;gBAGlD,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QACvB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC9B,YAAY,CAAC,EAAE,oBAAoB,CAAC;KACrC;IAMH;;OAEG;IACH,MAAM,CAAC,oBAAoB,CACzB,KAAK,EAAE,GAAG,EACV,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,aAAa;IA8BhB;;OAEG;IACH,QAAQ,IAAI,MAAM;CAcnB"}
@@ -2,8 +2,8 @@
2
2
  * Contract Error Parsing
3
3
  * Utilities for parsing and decoding contract errors from transaction failures
4
4
  */
5
- import { decodeErrorResult } from './abi';
6
- import { CirclesError } from './errors';
5
+ import { decodeErrorResult } from './abi.js';
6
+ import { CirclesError } from './errors.js';
7
7
  /**
8
8
  * Contract error codes mapping
9
9
  * Maps error codes to human-readable messages
package/dist/crypto.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { secp256k1 } from '@noble/curves/secp256k1';
2
2
  import { keccak_256 } from '@noble/hashes/sha3';
3
- import { checksumAddress } from './abi';
3
+ import { checksumAddress } from './abi.js';
4
4
  /**
5
5
  * Generate a random private key (32 bytes / 64 hex chars)
6
6
  * @returns Private key as hex string with 0x prefix
package/dist/index.d.ts CHANGED
@@ -1,12 +1,14 @@
1
- export { CirclesConverter } from './circlesConverter';
2
- export { bytesToHex, hexToBytes } from './bytes';
3
- export { encodeFunctionData, decodeFunctionResult, decodeErrorResult, checksumAddress, encodeAbiParameters } from './abi';
4
- export { cidV0ToHex, cidV0ToUint8Array } from './cid';
5
- export { uint256ToAddress } from './address';
6
- export { ZERO_ADDRESS, INVITATION_FEE, MAX_FLOW, SAFE_PROXY_FACTORY, ACCOUNT_INITIALIZER_HASH, ACCOUNT_CREATION_CODE_HASH, GNOSIS_GROUP_ADDRESS, FARM_DESTINATION } from './constants';
7
- export { circlesConfig } from './config';
8
- export { parseContractError, ContractError } from './contractErrors';
9
- export { generatePrivateKey, privateKeyToAddress, keccak256 } from './crypto';
10
- export { CirclesError, ValidationError, EncodingError, wrapError, isCirclesError, getErrorMessage, } from './errors';
11
- export type { BaseErrorSource, UtilsErrorSource } from './errors';
1
+ export { CirclesConverter } from './circlesConverter.js';
2
+ export { bytesToHex, hexToBytes } from './bytes.js';
3
+ export { encodeFunctionData, decodeFunctionResult, decodeErrorResult, checksumAddress, encodeAbiParameters, decodeAbiParameters } from './abi.js';
4
+ export { cidV0ToHex, cidV0ToUint8Array } from './cid.js';
5
+ export { uint256ToAddress } from './address.js';
6
+ export { ZERO_ADDRESS, INVITATION_FEE, MAX_FLOW, SAFE_PROXY_FACTORY, ACCOUNT_INITIALIZER_HASH, ACCOUNT_CREATION_CODE_HASH, GNOSIS_GROUP_ADDRESS, FARM_DESTINATION } from './constants.js';
7
+ export { circlesConfig } from './config.js';
8
+ export { parseContractError, ContractError } from './contractErrors.js';
9
+ export { generatePrivateKey, privateKeyToAddress, keccak256 } from './crypto.js';
10
+ export { encodeCrcV2TransferData, decodeCrcV2TransferData } from './transferData.js';
11
+ export type { TransferDataType, DecodedTransferData, DecodedAbiPayload, DecodedMetadataPayload } from './transferData.js';
12
+ export { CirclesError, ValidationError, EncodingError, wrapError, isCirclesError, getErrorMessage, } from './errors.js';
13
+ export type { BaseErrorSource, UtilsErrorSource } from './errors.js';
12
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAC1H,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvL,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG9E,OAAO,EACL,YAAY,EACZ,eAAe,EACf,aAAa,EACb,SAAS,EACT,cAAc,EACd,eAAe,GAChB,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,eAAe,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAClJ,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC1L,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AACrF,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAG1H,OAAO,EACL,YAAY,EACZ,eAAe,EACf,aAAa,EACb,SAAS,EACT,cAAc,EACd,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
- export { CirclesConverter } from './circlesConverter';
2
- export { bytesToHex, hexToBytes } from './bytes';
3
- export { encodeFunctionData, decodeFunctionResult, decodeErrorResult, checksumAddress, encodeAbiParameters } from './abi';
4
- export { cidV0ToHex, cidV0ToUint8Array } from './cid';
5
- export { uint256ToAddress } from './address';
6
- export { ZERO_ADDRESS, INVITATION_FEE, MAX_FLOW, SAFE_PROXY_FACTORY, ACCOUNT_INITIALIZER_HASH, ACCOUNT_CREATION_CODE_HASH, GNOSIS_GROUP_ADDRESS, FARM_DESTINATION } from './constants';
7
- export { circlesConfig } from './config';
8
- export { parseContractError, ContractError } from './contractErrors';
9
- export { generatePrivateKey, privateKeyToAddress, keccak256 } from './crypto';
1
+ export { CirclesConverter } from './circlesConverter.js';
2
+ export { bytesToHex, hexToBytes } from './bytes.js';
3
+ export { encodeFunctionData, decodeFunctionResult, decodeErrorResult, checksumAddress, encodeAbiParameters, decodeAbiParameters } from './abi.js';
4
+ export { cidV0ToHex, cidV0ToUint8Array } from './cid.js';
5
+ export { uint256ToAddress } from './address.js';
6
+ export { ZERO_ADDRESS, INVITATION_FEE, MAX_FLOW, SAFE_PROXY_FACTORY, ACCOUNT_INITIALIZER_HASH, ACCOUNT_CREATION_CODE_HASH, GNOSIS_GROUP_ADDRESS, FARM_DESTINATION } from './constants.js';
7
+ export { circlesConfig } from './config.js';
8
+ export { parseContractError, ContractError } from './contractErrors.js';
9
+ export { generatePrivateKey, privateKeyToAddress, keccak256 } from './crypto.js';
10
+ export { encodeCrcV2TransferData, decodeCrcV2TransferData } from './transferData.js';
10
11
  // Error handling
11
- export { CirclesError, ValidationError, EncodingError, wrapError, isCirclesError, getErrorMessage, } from './errors';
12
+ export { CirclesError, ValidationError, EncodingError, wrapError, isCirclesError, getErrorMessage, } from './errors.js';
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect } from 'bun:test';
2
- import { encodeFunctionData, decodeFunctionResult } from '../abi';
2
+ import { encodeFunctionData, decodeFunctionResult } from '../abi.js';
3
3
  import { encodeFunctionData as viemEncodeFunctionData, decodeFunctionResult as viemDecodeFunctionResult } from 'viem/utils';
4
4
  describe('encodeFunctionData', () => {
5
5
  test('encodes function with no inputs', () => {
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect } from 'bun:test';
2
- import { checksumAddress } from '../abi';
2
+ import { checksumAddress } from '../abi.js';
3
3
  describe('checksumAddress', () => {
4
4
  test('checksums lowercase address correctly', () => {
5
5
  const lowercase = '0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed';
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=transferData.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transferData.test.d.ts","sourceRoot":"","sources":["../../src/tests/transferData.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,243 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import { encodeCrcV2TransferData, decodeCrcV2TransferData } from '../transferData.js';
3
+ // ============================================================================
4
+ // Type 0x0001 — UTF-8 Text
5
+ // ============================================================================
6
+ describe('Type 0x0001 — UTF-8 text', () => {
7
+ test('encodes "Hi"', () => {
8
+ const result = encodeCrcV2TransferData(['Hi'], 0x0001);
9
+ expect(result).toBe('0x010001000248' + '69');
10
+ });
11
+ test('encodes "Happy Birthday 🎂"', () => {
12
+ const result = encodeCrcV2TransferData(['Happy Birthday 🎂'], 0x0001);
13
+ expect(result).toBe('0x0100010013486170707920426972746864617920F09F8E82'.toLowerCase());
14
+ });
15
+ test('round-trips simple text', () => {
16
+ const text = 'Thanks for the lunch! 🍣';
17
+ const encoded = encodeCrcV2TransferData([text], 0x0001);
18
+ const decoded = decodeCrcV2TransferData(encoded);
19
+ expect(decoded.type).toBe(0x0001);
20
+ expect(decoded.payload).toBe(text);
21
+ expect(decoded.length).toBe(new TextEncoder().encode(text).length);
22
+ });
23
+ test('round-trips empty string', () => {
24
+ const encoded = encodeCrcV2TransferData([''], 0x0001);
25
+ const decoded = decodeCrcV2TransferData(encoded);
26
+ expect(decoded.payload).toBe('');
27
+ expect(decoded.length).toBe(0);
28
+ });
29
+ test('round-trips multi-line text', () => {
30
+ const text = 'Line 1\nLine 2\nLine 3';
31
+ const encoded = encodeCrcV2TransferData([text], 0x0001);
32
+ const decoded = decodeCrcV2TransferData(encoded);
33
+ expect(decoded.payload).toBe(text);
34
+ });
35
+ });
36
+ // ============================================================================
37
+ // Type 0x0002 — Raw 32-byte hex (XMTP message ID)
38
+ // ============================================================================
39
+ describe('Type 0x0002 — Raw 32-byte hex', () => {
40
+ const xmtpId = '0x69236a76eff7f05583e6fa5810cf6b24100f0e9132e0787594a790b2fea254ec';
41
+ test('encodes XMTP message ID', () => {
42
+ const result = encodeCrcV2TransferData([xmtpId], 0x0002);
43
+ expect(result).toBe('0x010002002069236a76eff7f05583e6fa5810cf6b24100f0e9132e0787594a790b2fea254ec');
44
+ });
45
+ test('round-trips XMTP message ID', () => {
46
+ const encoded = encodeCrcV2TransferData([xmtpId], 0x0002);
47
+ const decoded = decodeCrcV2TransferData(encoded);
48
+ expect(decoded.type).toBe(0x0002);
49
+ expect(decoded.length).toBe(32);
50
+ expect(decoded.payload.toLowerCase()).toBe(xmtpId.toLowerCase());
51
+ });
52
+ test('accepts hex without 0x prefix', () => {
53
+ const raw = '69236a76eff7f05583e6fa5810cf6b24100f0e9132e0787594a790b2fea254ec';
54
+ const encoded = encodeCrcV2TransferData([raw], 0x0002);
55
+ const decoded = decodeCrcV2TransferData(encoded);
56
+ expect(decoded.payload.slice(2)).toBe(raw);
57
+ });
58
+ test('throws for wrong byte length', () => {
59
+ expect(() => encodeCrcV2TransferData(['0xdeadbeef'], 0x0002)).toThrow();
60
+ });
61
+ });
62
+ // ============================================================================
63
+ // Type 0x0003 — IPFS CID
64
+ // ============================================================================
65
+ describe('Type 0x0003 — IPFS CID', () => {
66
+ const cidV0 = 'QmZ4tDuvesekSs4qM5ZBKpXiZGun7S2CYtEZRB3DYXkjGx';
67
+ test('encodes CIDv0', () => {
68
+ const result = encodeCrcV2TransferData([cidV0], 0x0003);
69
+ expect(result).toMatch(/^0x01000300/);
70
+ });
71
+ test('round-trips CIDv0 back to CIDv0 string (dag-pb + sha256)', () => {
72
+ const encoded = encodeCrcV2TransferData([cidV0], 0x0003);
73
+ const decoded = decodeCrcV2TransferData(encoded);
74
+ expect(decoded.type).toBe(0x0003);
75
+ expect(decoded.payload).toBe(cidV0);
76
+ });
77
+ test('round-trips CIDv1 string', () => {
78
+ // CIDv1 base32 (dag-pb + sha256, same content as CIDv0 above)
79
+ const cidV1 = 'bafybeie7m2fsbt6sjtn7tymyb6sim7iiyz6szl4ethtn7anzx4frzfzipu';
80
+ const encoded = encodeCrcV2TransferData([cidV1], 0x0003);
81
+ const decoded = decodeCrcV2TransferData(encoded);
82
+ expect(decoded.type).toBe(0x0003);
83
+ // dag-pb + sha256 CIDv1 should decode back to CIDv0
84
+ expect(decoded.payload).toBe(cidV0);
85
+ });
86
+ test('encodes and decodes produce consistent length', () => {
87
+ const encoded = encodeCrcV2TransferData([cidV0], 0x0003);
88
+ const decoded = decodeCrcV2TransferData(encoded);
89
+ // CIDv1 hex for dag-pb sha256 = 36 bytes (1 version + 1 codec + 2 multihash header + 32 digest)
90
+ expect(decoded.length).toBe(36);
91
+ });
92
+ });
93
+ // ============================================================================
94
+ // Type 0x1001 — UTF-8 text with metadata
95
+ // ============================================================================
96
+ describe('Type 0x1001 — UTF-8 text with metadata', () => {
97
+ test('encodes message + metadata', () => {
98
+ const encoded = encodeCrcV2TransferData(['Happy Birthday', 'https://app.gnosis.io'], 0x1001);
99
+ const hex = encoded.slice(2);
100
+ // Header starts with version=01, type=1001
101
+ expect(hex.slice(0, 2)).toBe('01');
102
+ expect(hex.slice(2, 6)).toBe('1001');
103
+ // Contains separator 480a868a
104
+ expect(hex).toContain('480a868a');
105
+ });
106
+ test('round-trips message + metadata', () => {
107
+ const message = 'Happy Birthday';
108
+ const metadata = 'https://app.gnosis.io';
109
+ const encoded = encodeCrcV2TransferData([message, metadata], 0x1001);
110
+ const decoded = decodeCrcV2TransferData(encoded);
111
+ expect(decoded.type).toBe(0x1001);
112
+ const p = decoded.payload;
113
+ expect(p.message).toBe(message);
114
+ expect(p.metadata).toBe(metadata);
115
+ });
116
+ test('length equals message + separator + metadata byte count', () => {
117
+ const message = 'Hello';
118
+ const metadata = 'order:123';
119
+ const encoded = encodeCrcV2TransferData([message, metadata], 0x1001);
120
+ const decoded = decodeCrcV2TransferData(encoded);
121
+ const messageBytes = new TextEncoder().encode(message).length;
122
+ const metadataBytes = new TextEncoder().encode(metadata).length;
123
+ // 4 bytes for the separator
124
+ expect(decoded.length).toBe(messageBytes + 4 + metadataBytes);
125
+ });
126
+ test('round-trips with emoji in message and metadata', () => {
127
+ const message = 'Thanks! 🎉';
128
+ const metadata = 'app:circles:tag=gift 🎁';
129
+ const encoded = encodeCrcV2TransferData([message, metadata], 0x1001);
130
+ const decoded = decodeCrcV2TransferData(encoded);
131
+ const p = decoded.payload;
132
+ expect(p.message).toBe(message);
133
+ expect(p.metadata).toBe(metadata);
134
+ });
135
+ test('throws on decode if separator is missing', () => {
136
+ // Manually craft a 0x1001 payload without the separator
137
+ const fakePayloadHex = '48656c6c6f'; // "Hello" without separator
138
+ const length = fakePayloadHex.length / 2;
139
+ const hex = '0x' + '01' + '1001' + length.toString(16).padStart(4, '0') + fakePayloadHex;
140
+ expect(() => decodeCrcV2TransferData(hex)).toThrow(/separator/i);
141
+ });
142
+ });
143
+ // ============================================================================
144
+ // Type 0x0004 — ABI-encoded calldata
145
+ // ============================================================================
146
+ describe('Type 0x0004 — ABI-encoded calldata', () => {
147
+ const signature = 'transfer(address,uint256)';
148
+ const address = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e';
149
+ const amount = 1000000000000000000n;
150
+ const expectedSelector = 'a9059cbb';
151
+ const expectedEncodedAddress = '000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e';
152
+ const expectedEncodedAmount = '0000000000000000000000000000000000000000000000000de0b6b3a7640000';
153
+ test('encodes from function signature + params', () => {
154
+ const result = encodeCrcV2TransferData([signature, address, amount], 0x0004);
155
+ const hex = result.slice(2);
156
+ // Header: 01 0004 0044
157
+ expect(hex.slice(0, 10)).toBe('0100040044');
158
+ // Selector
159
+ expect(hex.slice(10, 18)).toBe(expectedSelector);
160
+ // Encoded address
161
+ expect(hex.slice(18, 18 + 64)).toBe(expectedEncodedAddress);
162
+ // Encoded amount
163
+ expect(hex.slice(18 + 64, 18 + 128)).toBe(expectedEncodedAmount);
164
+ });
165
+ test('encodes from pre-encoded calldata (single element)', () => {
166
+ const rawCalldata = `0x${expectedSelector}${expectedEncodedAddress}${expectedEncodedAmount}`;
167
+ const result = encodeCrcV2TransferData([rawCalldata], 0x0004);
168
+ const hex = result.slice(2);
169
+ expect(hex.slice(0, 10)).toBe('0100040044');
170
+ expect(hex.slice(10, 18)).toBe(expectedSelector);
171
+ });
172
+ test('decodes known selector with resolved signature and params', () => {
173
+ const encoded = encodeCrcV2TransferData([signature, address, amount], 0x0004);
174
+ const decoded = decodeCrcV2TransferData(encoded);
175
+ expect(decoded.type).toBe(0x0004);
176
+ expect(decoded.length).toBe(68); // 4 selector + 32 + 32
177
+ const p = decoded.payload;
178
+ expect(p.selector).toBe(`0x${expectedSelector}`);
179
+ expect(p.signature).toBe(signature);
180
+ expect(p.params.length).toBe(2);
181
+ // address param (lowercased with checksum)
182
+ expect(p.params[0].toLowerCase()).toBe(address.toLowerCase());
183
+ // uint256 param
184
+ expect(p.params[1]).toBe(amount);
185
+ });
186
+ test('decodes raw calldata with known selector', () => {
187
+ const rawCalldata = `0x${expectedSelector}${expectedEncodedAddress}${expectedEncodedAmount}`;
188
+ const encoded = encodeCrcV2TransferData([rawCalldata], 0x0004);
189
+ const decoded = decodeCrcV2TransferData(encoded);
190
+ const p = decoded.payload;
191
+ expect(p.selector).toBe(`0x${expectedSelector}`);
192
+ expect(p.signature).toBe(signature);
193
+ expect(p.params.length).toBe(2);
194
+ });
195
+ test('returns raw data for unknown selector', () => {
196
+ // Craft calldata with a selector not in the lookup table
197
+ // Use 8 zeros padded — extremely unlikely to be a real function
198
+ const unknownSelector = 'ffffffff';
199
+ const fakePayload = unknownSelector + expectedEncodedAddress;
200
+ const encoded = encodeCrcV2TransferData([`0x${fakePayload}`], 0x0004);
201
+ const decoded = decodeCrcV2TransferData(encoded);
202
+ // Unknown selector — payload is raw hex string
203
+ expect(typeof decoded.payload).toBe('string');
204
+ expect(decoded.payload).toBe(`0x${fakePayload}`);
205
+ });
206
+ test('throws for invalid signature', () => {
207
+ expect(() => encodeCrcV2TransferData(['notASignature', address], 0x0004)).toThrow();
208
+ });
209
+ });
210
+ // ============================================================================
211
+ // Version handling
212
+ // ============================================================================
213
+ describe('Version handling', () => {
214
+ test('default version is 0x01', () => {
215
+ const encoded = encodeCrcV2TransferData(['test'], 0x0001);
216
+ expect(encoded.slice(2, 4)).toBe('01');
217
+ });
218
+ test('throws on version mismatch when decoding', () => {
219
+ const encoded = encodeCrcV2TransferData(['test'], 0x0001, 0x01);
220
+ expect(() => decodeCrcV2TransferData(encoded, 0x02)).toThrow(/version/i);
221
+ });
222
+ });
223
+ // ============================================================================
224
+ // Error cases
225
+ // ============================================================================
226
+ describe('Error cases', () => {
227
+ test('throws for unsupported type in encode', () => {
228
+ expect(() => encodeCrcV2TransferData(['foo'], 0x9999)).toThrow();
229
+ });
230
+ test('throws for unsupported type in decode', () => {
231
+ // Manually craft an encoded payload with type 0x9999
232
+ const hex = '0x01999900020000';
233
+ expect(() => decodeCrcV2TransferData(hex)).toThrow();
234
+ });
235
+ test('throws for data too short to decode', () => {
236
+ expect(() => decodeCrcV2TransferData('0x0100')).toThrow(/too short/i);
237
+ });
238
+ test('throws for payload length mismatch', () => {
239
+ // Header says length=10 but no payload follows
240
+ const hex = '0x' + '01' + '0001' + '000a'; // 5 header bytes, claims 10 payload bytes
241
+ expect(() => decodeCrcV2TransferData(hex)).toThrow(/mismatch/i);
242
+ });
243
+ });
@@ -0,0 +1,30 @@
1
+ import type { Hex, DecodedTransferData, TransferDataType } from '@aboutcircles/sdk-types';
2
+ export type { TransferDataType, DecodedTransferData, DecodedAbiPayload, DecodedMetadataPayload } from '@aboutcircles/sdk-types';
3
+ /**
4
+ * Encodes payload data for Circles V2 transfer annotations.
5
+ *
6
+ * Format: [version: 1B][type: 2B][length: 2B][payload bytes]
7
+ *
8
+ * Types:
9
+ * - 0x0001: UTF-8 text string
10
+ * - 0x1001: UTF-8 text with metadata (payload[0] = message, payload[1] = metadata string)
11
+ * - 0x0002: Raw 32-byte hex (e.g. XMTP message ID)
12
+ * - 0x0003: IPFS CID string (CIDv0 or CIDv1, converted to hex)
13
+ * - 0x0004: ABI-encoded calldata
14
+ * - payload[0] can be: a function signature string + params, or raw encoded hex calldata
15
+ *
16
+ * @param payload - Array of values; interpretation depends on type
17
+ * @param type - Transfer data type (0x0001–0x0004)
18
+ * @param version - Protocol version (default 0x01)
19
+ * @returns 0x-prefixed hex string
20
+ */
21
+ export declare function encodeCrcV2TransferData(payload: unknown[], type: TransferDataType, version?: number): Hex;
22
+ /**
23
+ * Decodes Circles V2 transfer data annotation from hex.
24
+ *
25
+ * @param encoded - 0x-prefixed hex string (as returned from circles_getTransferData)
26
+ * @param version - Expected protocol version (default 0x01); throws if mismatch
27
+ * @returns Decoded struct with type, length, and payload
28
+ */
29
+ export declare function decodeCrcV2TransferData(encoded: string, version?: number): DecodedTransferData;
30
+ //# sourceMappingURL=transferData.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transferData.d.ts","sourceRoot":"","sources":["../src/transferData.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,GAAG,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAM1F,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AA8HhI;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,OAAO,EAAE,EAClB,IAAI,EAAE,gBAAgB,EACtB,OAAO,GAAE,MAAa,GACrB,GAAG,CA4FL;AAMD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAa,GACrB,mBAAmB,CA+FrB"}
@@ -0,0 +1,314 @@
1
+ import { CID } from 'multiformats/cid';
2
+ import { base16 } from 'multiformats/bases/base16';
3
+ import { base58btc } from 'multiformats/bases/base58';
4
+ import { keccak_256 } from '@noble/hashes/sha3.js';
5
+ import { encodeFunctionData } from './abi.js';
6
+ import { decodeAbiParameters } from './abi.js';
7
+ import { hexToBytes, bytesToHex } from './bytes.js';
8
+ import { EncodingError } from './errors.js';
9
+ // ============================================================================
10
+ // CONSTANTS
11
+ // ============================================================================
12
+ const VERSION_BYTES = 1;
13
+ const TYPE_BYTES = 2;
14
+ const LENGTH_BYTES = 2;
15
+ const HEADER_BYTES = VERSION_BYTES + TYPE_BYTES + LENGTH_BYTES; // 5
16
+ // ============================================================================
17
+ // HELPERS
18
+ // ============================================================================
19
+ function numToHex(value, byteLength) {
20
+ return value.toString(16).padStart(byteLength * 2, '0');
21
+ }
22
+ function hexNum(hex, offset, length) {
23
+ return parseInt(hex.slice(offset, offset + length * 2), 16);
24
+ }
25
+ function cidToHex(cid) {
26
+ try {
27
+ const parsed = CID.parse(cid, base58btc);
28
+ const v1 = parsed.toV1();
29
+ // base16 (hex) encoding includes 'f' prefix - strip it
30
+ const encoded = v1.toString(base16);
31
+ return encoded.startsWith('f') ? encoded.slice(1) : encoded;
32
+ }
33
+ catch {
34
+ // Try as CIDv1 directly
35
+ const parsed = CID.parse(cid);
36
+ const v1 = parsed.toV1();
37
+ const encoded = v1.toString(base16);
38
+ return encoded.startsWith('f') ? encoded.slice(1) : encoded;
39
+ }
40
+ }
41
+ const METADATA_SEPARATOR = '480a868a'; // keccak256("metadataStart") first 4 bytes
42
+ function hexToCid(hex) {
43
+ const bytes = hexToBytes(hex.startsWith('0x') ? hex : `0x${hex}`);
44
+ const cid = CID.decode(bytes);
45
+ // Return CIDv0 if compatible (dag-pb + sha2-256), otherwise CIDv1
46
+ const isDagPb = cid.code === 0x70;
47
+ const isSha256 = cid.multihash.code === 0x12;
48
+ if (isDagPb && isSha256) {
49
+ return cid.toV0().toString();
50
+ }
51
+ return cid.toString();
52
+ }
53
+ // @todo construct lookup table based on the contracts in the core pkg
54
+ // ============================================================================
55
+ // LOCAL SELECTOR LOOKUP TABLE
56
+ // ============================================================================
57
+ /** Known function signatures for local selector resolution. */
58
+ const KNOWN_SIGNATURES = [
59
+ // ERC20
60
+ 'transfer(address,uint256)',
61
+ 'transferFrom(address,address,uint256)',
62
+ 'approve(address,uint256)',
63
+ 'increaseAllowance(address,uint256)',
64
+ 'decreaseAllowance(address,uint256)',
65
+ 'balanceOf(address)',
66
+ 'allowance(address,address)',
67
+ 'totalSupply()',
68
+ 'decimals()',
69
+ 'permit(address,address,uint256,uint256,uint8,bytes32,bytes32)',
70
+ // ERC1155
71
+ 'safeTransferFrom(address,address,uint256,uint256,bytes)',
72
+ 'safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)',
73
+ 'setApprovalForAll(address,bool)',
74
+ 'isApprovedForAll(address,address)',
75
+ // Circles Hub V2
76
+ 'trust(address,uint96)',
77
+ 'isTrusted(address,address)',
78
+ 'isHuman(address)',
79
+ 'wrap(address,uint256,uint8)',
80
+ 'toTokenId(address)',
81
+ 'operateFlowMatrix(address[],tuple[],tuple[],bytes)',
82
+ // Wrapped Circles
83
+ 'unwrap(uint256)',
84
+ 'convertDemurrageToInflationaryValue(uint256,uint64)',
85
+ 'convertInflationaryToDemurrageValue(uint256,uint64)',
86
+ // Safe
87
+ 'enableModule(address)',
88
+ 'isModuleEnabled(address)',
89
+ // Invitation
90
+ 'trustInviter(address)',
91
+ 'claimInvite()',
92
+ 'claimInvites(uint256)',
93
+ 'createAccount(address)',
94
+ 'createAccounts(address[])',
95
+ // LiftERC20
96
+ 'erc20Circles(uint8,address)',
97
+ ];
98
+ /** Compute 4-byte selector from a function signature string. */
99
+ function computeSelector(sig) {
100
+ const hash = keccak_256(new TextEncoder().encode(sig));
101
+ return bytesToHex(hash.slice(0, 4)).slice(2); // strip 0x, 8 hex chars
102
+ }
103
+ /** Map from 8-char hex selector → full signature string. Built once at module load. */
104
+ const SELECTOR_TABLE = new Map(KNOWN_SIGNATURES.map(sig => [computeSelector(sig), sig]));
105
+ /**
106
+ * Parse a function signature string into param type strings.
107
+ * e.g. "transfer(address,uint256)" → ["address", "uint256"]
108
+ */
109
+ function parseSignatureTypes(signature) {
110
+ const parenIdx = signature.indexOf('(');
111
+ if (parenIdx === -1)
112
+ return [];
113
+ const inner = signature.slice(parenIdx + 1, -1);
114
+ if (!inner)
115
+ return [];
116
+ return inner.split(',').map(t => t.trim()).filter(Boolean);
117
+ }
118
+ // ============================================================================
119
+ // ENCODE
120
+ // ============================================================================
121
+ /**
122
+ * Encodes payload data for Circles V2 transfer annotations.
123
+ *
124
+ * Format: [version: 1B][type: 2B][length: 2B][payload bytes]
125
+ *
126
+ * Types:
127
+ * - 0x0001: UTF-8 text string
128
+ * - 0x1001: UTF-8 text with metadata (payload[0] = message, payload[1] = metadata string)
129
+ * - 0x0002: Raw 32-byte hex (e.g. XMTP message ID)
130
+ * - 0x0003: IPFS CID string (CIDv0 or CIDv1, converted to hex)
131
+ * - 0x0004: ABI-encoded calldata
132
+ * - payload[0] can be: a function signature string + params, or raw encoded hex calldata
133
+ *
134
+ * @param payload - Array of values; interpretation depends on type
135
+ * @param type - Transfer data type (0x0001–0x0004)
136
+ * @param version - Protocol version (default 0x01)
137
+ * @returns 0x-prefixed hex string
138
+ */
139
+ export function encodeCrcV2TransferData(payload, type, version = 0x01) {
140
+ let payloadHex;
141
+ switch (type) {
142
+ case 0x0001: {
143
+ // UTF-8 text
144
+ const text = payload[0];
145
+ const bytes = new TextEncoder().encode(text);
146
+ payloadHex = bytesToHex(bytes).slice(2);
147
+ break;
148
+ }
149
+ case 0x1001: {
150
+ // UTF-8 text with metadata
151
+ const message = payload[0];
152
+ const metadata = payload[1];
153
+ const messageHex = bytesToHex(new TextEncoder().encode(message)).slice(2);
154
+ const metadataHex = bytesToHex(new TextEncoder().encode(metadata)).slice(2);
155
+ payloadHex = messageHex + METADATA_SEPARATOR + metadataHex;
156
+ break;
157
+ }
158
+ case 0x0002: {
159
+ // Raw hex (XMTP message ID - 32 bytes)
160
+ const raw = payload[0];
161
+ const cleaned = raw.startsWith('0x') ? raw.slice(2) : raw;
162
+ if (cleaned.length !== 64) {
163
+ throw new EncodingError(`Type 0x0002 expects a 32-byte hex value (64 hex chars), got ${cleaned.length / 2} bytes`, {
164
+ code: 'ENCODING_INVALID_LENGTH',
165
+ });
166
+ }
167
+ payloadHex = cleaned.toLowerCase();
168
+ break;
169
+ }
170
+ case 0x0003: {
171
+ // IPFS CID
172
+ const cidStr = payload[0];
173
+ payloadHex = cidToHex(cidStr);
174
+ break;
175
+ }
176
+ case 0x0004: {
177
+ // ABI-encoded calldata
178
+ const first = payload[0];
179
+ if (payload.length === 1 && first.startsWith('0x')) {
180
+ // Raw pre-encoded calldata
181
+ payloadHex = first.slice(2).toLowerCase();
182
+ }
183
+ else {
184
+ // Function signature + params
185
+ const signature = first; // e.g. "transfer(address,uint256)"
186
+ const parenIdx = signature.indexOf('(');
187
+ if (parenIdx === -1) {
188
+ throw new EncodingError('Type 0x0004 payload[0] must be a function signature like "transfer(address,uint256)"', {
189
+ code: 'ENCODING_INVALID_SIGNATURE',
190
+ });
191
+ }
192
+ const fnName = signature.slice(0, parenIdx);
193
+ const paramTypes = signature
194
+ .slice(parenIdx + 1, -1)
195
+ .split(',')
196
+ .map(t => t.trim())
197
+ .filter(Boolean);
198
+ const abiInputs = paramTypes.map((t, i) => ({ type: t, name: `p${i}` }));
199
+ const args = payload.slice(1);
200
+ const encoded = encodeFunctionData({
201
+ abi: [{ name: fnName, type: 'function', stateMutability: 'nonpayable', inputs: abiInputs, outputs: [] }],
202
+ functionName: fnName,
203
+ args,
204
+ });
205
+ payloadHex = encoded.slice(2).toLowerCase();
206
+ }
207
+ break;
208
+ }
209
+ default:
210
+ throw new EncodingError(`Unsupported transfer data type: ${type}`, {
211
+ code: 'ENCODING_UNSUPPORTED_TYPE',
212
+ context: { type },
213
+ });
214
+ }
215
+ const payloadBytes = payloadHex.length / 2;
216
+ const header = numToHex(version, VERSION_BYTES) +
217
+ numToHex(type, TYPE_BYTES) +
218
+ numToHex(payloadBytes, LENGTH_BYTES);
219
+ return `0x${header}${payloadHex}`;
220
+ }
221
+ // ============================================================================
222
+ // DECODE
223
+ // ============================================================================
224
+ /**
225
+ * Decodes Circles V2 transfer data annotation from hex.
226
+ *
227
+ * @param encoded - 0x-prefixed hex string (as returned from circles_getTransferData)
228
+ * @param version - Expected protocol version (default 0x01); throws if mismatch
229
+ * @returns Decoded struct with type, length, and payload
230
+ */
231
+ export function decodeCrcV2TransferData(encoded, version = 0x01) {
232
+ const hex = encoded.startsWith('0x') ? encoded.slice(2) : encoded;
233
+ if (hex.length < HEADER_BYTES * 2) {
234
+ throw new EncodingError('Encoded data too short to contain header', {
235
+ code: 'ENCODING_TOO_SHORT',
236
+ context: { encoded },
237
+ });
238
+ }
239
+ const encodedVersion = hexNum(hex, 0, VERSION_BYTES);
240
+ if (encodedVersion !== version) {
241
+ throw new EncodingError(`Version mismatch: expected ${version}, got ${encodedVersion}`, {
242
+ code: 'ENCODING_VERSION_MISMATCH',
243
+ context: { expected: version, got: encodedVersion },
244
+ });
245
+ }
246
+ const type = hexNum(hex, VERSION_BYTES * 2, TYPE_BYTES);
247
+ const length = hexNum(hex, (VERSION_BYTES + TYPE_BYTES) * 2, LENGTH_BYTES);
248
+ const payloadHex = hex.slice(HEADER_BYTES * 2, HEADER_BYTES * 2 + length * 2);
249
+ if (payloadHex.length !== length * 2) {
250
+ throw new EncodingError('Payload length mismatch', {
251
+ code: 'ENCODING_LENGTH_MISMATCH',
252
+ context: { expected: length, got: payloadHex.length / 2 },
253
+ });
254
+ }
255
+ let payload;
256
+ switch (type) {
257
+ case 0x0001: {
258
+ // UTF-8 text
259
+ const bytes = hexToBytes(`0x${payloadHex}`);
260
+ payload = new TextDecoder().decode(bytes);
261
+ break;
262
+ }
263
+ case 0x1001: {
264
+ // UTF-8 text with metadata
265
+ const separatorIdx = payloadHex.indexOf(METADATA_SEPARATOR);
266
+ if (separatorIdx === -1) {
267
+ throw new EncodingError('Type 0x1001: metadata separator not found in payload', {
268
+ code: 'ENCODING_MISSING_SEPARATOR',
269
+ });
270
+ }
271
+ const messageHex = payloadHex.slice(0, separatorIdx);
272
+ const metadataHex = payloadHex.slice(separatorIdx + METADATA_SEPARATOR.length);
273
+ const message = new TextDecoder().decode(hexToBytes(`0x${messageHex}`));
274
+ const metadata = new TextDecoder().decode(hexToBytes(`0x${metadataHex}`));
275
+ payload = { message, metadata };
276
+ break;
277
+ }
278
+ case 0x0002: {
279
+ // Raw hex
280
+ payload = `0x${payloadHex}`;
281
+ break;
282
+ }
283
+ case 0x0003: {
284
+ // IPFS CID (hex → CID string)
285
+ payload = hexToCid(payloadHex);
286
+ break;
287
+ }
288
+ case 0x0004: {
289
+ // ABI-encoded calldata — try local selector lookup, fallback to raw data
290
+ const selectorHex = payloadHex.slice(0, 8);
291
+ const selector = `0x${selectorHex}`;
292
+ const signature = SELECTOR_TABLE.get(selectorHex);
293
+ if (signature) {
294
+ const paramTypes = parseSignatureTypes(signature);
295
+ const paramsHex = payloadHex.slice(8);
296
+ const params = paramTypes.length > 0
297
+ ? decodeAbiParameters(paramTypes, paramsHex)
298
+ : [];
299
+ payload = { selector, signature, params };
300
+ }
301
+ else {
302
+ // Unknown selector — return raw calldata hex
303
+ payload = `0x${payloadHex}`;
304
+ }
305
+ break;
306
+ }
307
+ default:
308
+ throw new EncodingError(`Unsupported transfer data type: ${type}`, {
309
+ code: 'ENCODING_UNSUPPORTED_TYPE',
310
+ context: { type },
311
+ });
312
+ }
313
+ return { type: type, length, payload };
314
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aboutcircles/sdk-utils",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "Utility functions for Circles SDK including demurrage calculations and conversions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",