Skip to content
Merged

Dev #127

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d79d293
feat: standardize EIP-712 type hashes and signing process in EngineBl…
JaCoderX Mar 13, 2026
7cf6a66
chore: update package versions to 1.0.0-alpha.18 across all modules
JaCoderX Mar 13, 2026
c617c59
feat: enhance typed-data signing validation in MetaTransactionSigner
JaCoderX Mar 13, 2026
e8d9dcb
Merge pull request #126 from PracticalParticle/work
JaCoderX Mar 13, 2026
97faa65
chore: consolidate versioning approach in EngineBlox library and rela…
JaCoderX Mar 14, 2026
7578702
chore: update package-lock.json to version 1.0.0-alpha.18 and upgrade…
JaCoderX Mar 14, 2026
0be5e8b
chore: update package-lock.json to include conventional-commits-filte…
JaCoderX Mar 14, 2026
4a7b5d3
Merge pull request #129 from PracticalParticle/work
JaCoderX Mar 14, 2026
ac0345b
chore: standardize OpenZeppelin contract versions in package.json and…
JaCoderX Mar 14, 2026
fe3bb37
fix: correct chainId assignment in MetaTransactionSigner class
JaCoderX Mar 14, 2026
f04bebe
chore: enhance version file checks in sync workflow
JaCoderX Mar 16, 2026
e80cebb
chore: improve timeout handling and logging in remote Ganache health …
JaCoderX Mar 16, 2026
b657894
Merge pull request #130 from PracticalParticle/work
JaCoderX Mar 16, 2026
9caf9d5
chore: enhance dependabot configuration for improved package management
JaCoderX Mar 16, 2026
3e475ee
fix: improve gas limit handling in BaseStateMachine
JaCoderX Mar 16, 2026
124ec65
fix: add validation for non-negative gas limit in BaseStateMachine
JaCoderX Mar 16, 2026
8553caf
fix: enhance gas option type handling in TransactionOptions interface
JaCoderX Mar 16, 2026
54e9ded
Merge pull request #131 from PracticalParticle/work
JaCoderX Mar 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Dependabot opens PRs against dev so they follow the dev -> main flow
# required by .github/workflows/require-dev-to-main.yml
version: 2
updates:
# Root workspace / tooling
- package-ecosystem: "npm"
directory: "/"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
schedule:
interval: "weekly"
target-branch: "dev"
groups:
npm_and_yarn:
patterns:
- "*"

# Doc generation utilities
- package-ecosystem: "npm"
directory: "/docgen"
schedule:
interval: "weekly"
target-branch: "dev"

# Published contract package
- package-ecosystem: "npm"
directory: "/package"
schedule:
interval: "weekly"
target-branch: "dev"

# TypeScript SDK package
- package-ecosystem: "npm"
directory: "/sdk/typescript"
schedule:
interval: "weekly"
target-branch: "dev"
71 changes: 51 additions & 20 deletions .github/workflows/sync-contract-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,34 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Check for uncommitted changes in EngineBlox.sol
- name: Check for uncommitted changes in version files
id: check-uncommitted
run: |
SOL_CHANGED=false
TSX_CHANGED=false
if ! git diff --quiet HEAD -- contracts/core/lib/EngineBlox.sol; then
echo "❌ ERROR: EngineBlox.sol has uncommitted changes!"
SOL_CHANGED=true
fi
if ! git diff --quiet HEAD -- sdk/typescript/lib/EngineBlox.tsx; then
TSX_CHANGED=true
fi
if [ "$SOL_CHANGED" = true ] || [ "$TSX_CHANGED" = true ]; then
echo "❌ ERROR: Version files have uncommitted changes!"
echo ""
echo "The workflow cannot safely update version constants when there are uncommitted changes."
echo "Please commit or stash your changes to EngineBlox.sol before this workflow runs."
echo "Please commit or stash your changes before this workflow runs."
echo ""
echo "Uncommitted changes:"
git diff HEAD -- contracts/core/lib/EngineBlox.sol
if [ "$SOL_CHANGED" = true ]; then
echo "Uncommitted changes in contracts/core/lib/EngineBlox.sol:"
git diff HEAD -- contracts/core/lib/EngineBlox.sol
fi
if [ "$TSX_CHANGED" = true ]; then
echo "Uncommitted changes in sdk/typescript/lib/EngineBlox.tsx:"
git diff HEAD -- sdk/typescript/lib/EngineBlox.tsx
fi
exit 1
else
echo "✓ EngineBlox.sol is clean, safe to proceed with version sync"
echo "✓ EngineBlox.sol and EngineBlox.tsx are clean, safe to proceed with version sync"
echo "uncommitted=false" >> $GITHUB_OUTPUT
fi

Expand All @@ -56,44 +70,61 @@ jobs:
- name: Check for contract version changes
id: check-changes
run: |
SOL_CHANGED=false
TSX_CHANGED=false
if ! git diff --quiet contracts/core/lib/EngineBlox.sol; then
SOL_CHANGED=true
fi
if ! git diff --quiet sdk/typescript/lib/EngineBlox.tsx; then
TSX_CHANGED=true
fi
if [ "$SOL_CHANGED" = true ] || [ "$TSX_CHANGED" = true ]; then
echo "changed=true" >> $GITHUB_OUTPUT
echo "Version changes detected:"
git diff contracts/core/lib/EngineBlox.sol
[ "$SOL_CHANGED" = true ] && git diff contracts/core/lib/EngineBlox.sol
[ "$TSX_CHANGED" = true ] && git diff sdk/typescript/lib/EngineBlox.tsx
else
echo "changed=false" >> $GITHUB_OUTPUT
echo "✓ Contract version constants already in sync"
echo "✓ Contract and SDK version constants already in sync"
fi

- name: Verify only version constants changed
if: steps.check-changes.outputs.changed == 'true'
id: verify-changes
run: |
# Get the diff and check if only VERSION_* constants changed
DIFF_OUTPUT=$(git diff contracts/core/lib/EngineBlox.sol)
# Get the diff for both version files
DIFF_OUTPUT=$(git diff contracts/core/lib/EngineBlox.sol sdk/typescript/lib/EngineBlox.tsx)

# Check if diff contains only version constant changes
# Version constants are: VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH
if echo "$DIFF_OUTPUT" | grep -qE "^[-+].*VERSION_(MAJOR|MINOR|PATCH)" && \
! echo "$DIFF_OUTPUT" | grep -vE "^[-+].*VERSION_(MAJOR|MINOR|PATCH)|^@|^index|^---|^\+\+\+" | grep -qE "^[-+]"; then
echo "✓ Only version constants changed, safe to commit"
echo "safe=true" >> $GITHUB_OUTPUT
else
echo "❌ ERROR: Changes detected in EngineBlox.sol are not limited to version constants!"
# All added/removed lines must be a VERSION constant change (.sol: constant VERSION = ... or .tsx: static readonly VERSION: string = ...)
ADDED_OR_REMOVED=$(echo "$DIFF_OUTPUT" | grep -E "^[-+]" | grep -v -E "^\+\+\+|^---")
OTHER_CHANGES=$(echo "$ADDED_OR_REMOVED" | grep -v -E "constant\s+VERSION\s*=" | grep -v -E "static readonly VERSION: string =")

if [ -n "$OTHER_CHANGES" ]; then
echo "❌ ERROR: Changes are not limited to VERSION constants!"
echo ""
echo "The workflow detected changes beyond VERSION_MAJOR, VERSION_MINOR, or VERSION_PATCH."
echo "The workflow detected changes beyond the VERSION constant in EngineBlox.sol or EngineBlox.VERSION in EngineBlox.tsx."
echo "This could indicate uncommitted changes were accidentally included."
echo ""
echo "Full diff:"
echo "$DIFF_OUTPUT"
exit 1
fi

# Require at least one VERSION change (sol or tsx)
if ! echo "$DIFF_OUTPUT" | grep -qE "^[-+].*constant\s+VERSION\s*=" && \
! echo "$DIFF_OUTPUT" | grep -qE "^[-+].*static readonly VERSION: string ="; then
echo "❌ ERROR: No VERSION constant changes found in diff."
exit 1
fi

echo "✓ Only version constants changed (contract + SDK), safe to commit"
echo "safe=true" >> $GITHUB_OUTPUT

- name: Commit and push contract version updates
if: steps.check-changes.outputs.changed == 'true' && steps.verify-changes.outputs.safe == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add contracts/core/lib/EngineBlox.sol
git add contracts/core/lib/EngineBlox.sol sdk/typescript/lib/EngineBlox.tsx
git commit -m "chore: sync contract version constants with package version"
git push
32 changes: 3 additions & 29 deletions abi/EngineBlox.abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -689,38 +689,12 @@
},
{
"inputs": [],
"name": "VERSION_MAJOR",
"name": "VERSION",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "VERSION_MINOR",
"outputs": [
{
"internalType": "uint8",
"internalType": "string",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "VERSION_PATCH",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
"type": "string"
}
],
"stateMutability": "view",
Expand Down
87 changes: 60 additions & 27 deletions contracts/core/lib/EngineBlox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ import "./interfaces/IEventForwarder.sol";
library EngineBlox {
// ============ VERSION INFORMATION ============
bytes32 public constant PROTOCOL_NAME_HASH = keccak256("Bloxchain");
uint8 public constant VERSION_MAJOR = 1;
uint8 public constant VERSION_MINOR = 0;
uint8 public constant VERSION_PATCH = 0;
string public constant VERSION = "1.0.0";

// ============ SYSTEM SAFETY LIMITS ============
// These constants define the safety range limits for system operations
Expand Down Expand Up @@ -206,8 +204,25 @@ library EngineBlox {
bytes32 public constant NATIVE_TRANSFER_OPERATION = keccak256("NATIVE_TRANSFER");

// EIP-712 Type Hashes (selective meta-tx payload: MetaTxRecord = txId + params + payment only)
bytes32 private constant META_TX_TYPE_HASH = keccak256("MetaTransaction(MetaTxRecord txRecord,MetaTxParams params,bytes data)MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)MetaTxParams(uint256 chainId,uint256 nonce,address handlerContract,bytes4 handlerSelector,uint8 action,uint256 deadline,uint256 maxGasPrice,address signer)PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)");
bytes32 private constant META_TX_RECORD_TYPE_HASH = keccak256("MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)");
// These follow the canonical EIP-712 convention so that eth_signTypedData_v4 and equivalent
// wallet typed-data signers can reproduce the same hashes when given matching type definitions.
//
// Canonical primary type string for MetaTransaction (primary type + all referenced types,
// appended in alphabetical order by type name
bytes32 private constant META_TX_TYPE_HASH = keccak256(
"MetaTransaction(MetaTxRecord txRecord,MetaTxParams params,bytes data)"
"MetaTxParams(uint256 chainId,uint256 nonce,address handlerContract,bytes4 handlerSelector,uint8 action,uint256 deadline,uint256 maxGasPrice,address signer)"
"MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)"
"PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)"
"TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)"
);

// Canonical primary type string for MetaTxRecord (primary type + its referenced types).
bytes32 private constant META_TX_RECORD_TYPE_HASH = keccak256(
"MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)"
"PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)"
"TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)"
);
bytes32 private constant TX_PARAMS_TYPE_HASH = keccak256("TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)");
bytes32 private constant META_TX_PARAMS_TYPE_HASH = keccak256("MetaTxParams(uint256 chainId,uint256 nonce,address handlerContract,bytes4 handlerSelector,uint8 action,uint256 deadline,uint256 maxGasPrice,address signer)");
bytes32 private constant PAYMENT_DETAILS_TYPE_HASH = keccak256("PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)");
Expand Down Expand Up @@ -1690,27 +1705,36 @@ library EngineBlox {

/**
* @dev Generates the EIP-712 message hash for the meta-transaction.
* Uses selective MetaTxRecord (txId, params, payment only).
* Integrators must sign this digest as a raw hash with no EIP-191 or personal_sign prefix—
* e.g. account.sign({ hash: contractDigest }) or equivalent raw-hash signing API—so that
* signatures match the raw ecrecover(messageHash, v, r, s) verification in recoverSigner.
* Do NOT use personal_sign or generic eth_signTypedData_v4; the contract uses
* abi.encodePacked for the version string and a custom META_TX_TYPE_HASH, so those produce
* incompatible hashes.
* The resulting digest is also written into the `message` field of the helper-built
* `MetaTransaction` structs (see `createMetaTransactionForSigning`) so integrators can use
* it directly without recomputing the hash client-side.
* Uses selective MetaTxRecord (txId, params, payment only) with standard EIP-712 type hashes
* so that eth_signTypedData_v4 (and equivalent) can reproduce the same digest when given
* matching domain + types:
*
* - primaryType: MetaTransaction
* - domain: { name: "Bloxchain", version: "1.0.0", chainId, verifyingContract }
* - types: MetaTransaction, MetaTxRecord, TxParams, MetaTxParams, PaymentDetails
*
* Integrators MAY:
* - use typed-data signing (eth_signTypedData_v4 / signTypedData) with the above domain/types, or
* - sign the resulting digest as a raw hash (e.g. account.sign({ hash: contractDigest })).
*
* In all cases, on-chain verification uses recoverSigner(messageHash, signature) which applies
* ecrecover(messageHash, v, r, s) with no personal_sign / EIP-191 prefix.
*
* The resulting digest is also written into the `message` field of helper-built `MetaTransaction`
* structs so integrators can use it directly without recomputing the hash client-side.
* @param metaTx The meta-transaction to generate the hash for
* @return The EIP-712 digest (no prefix; use standard recovery)
*/
function generateMessageHash(MetaTransaction memory metaTx) private view returns (bytes32) {
bytes32 domainSeparator = keccak256(abi.encode(
DOMAIN_SEPARATOR_TYPE_HASH,
PROTOCOL_NAME_HASH,
keccak256(abi.encodePacked(VERSION_MAJOR, ".", VERSION_MINOR, ".", VERSION_PATCH)),
block.chainid,
address(this)
));
bytes32 domainSeparator = keccak256(
abi.encode(
DOMAIN_SEPARATOR_TYPE_HASH,
PROTOCOL_NAME_HASH,
keccak256(bytes(VERSION)),
block.chainid,
address(this)
)
);

TxParams memory tp = metaTx.txRecord.params;
bytes32 txParamsStructHash = keccak256(abi.encode(
Expand Down Expand Up @@ -1769,11 +1793,20 @@ library EngineBlox {

/**
* @dev Recovers the signer from the EIP-712 digest and signature. Uses standard EIP-712 recovery (no message prefix).
* Integrators must sign the digest returned by generateMessageHash as a raw hash—e.g.
* account.sign({ hash: contractDigest }) or equivalent raw-hash signing API—with no
* EIP-191/personal prefix, so signatures match this ecrecover(messageHash, v, r, s) verification.
* Do NOT use personal_sign or generic eth_signTypedData_v4; the contract uses abi.encodePacked
* for the domain version and a custom META_TX_TYPE_HASH, so those produce incompatible hashes.
*
* Integrators have two equivalent options:
* - Use typed-data signing (eth_signTypedData_v4 / signTypedData) with:
* - primaryType: MetaTransaction
* - domain: { name: "Bloxchain", version: "1.0.0", chainId, verifyingContract }
* - types: MetaTransaction, MetaTxRecord, TxParams, MetaTxParams, PaymentDetails
* In this case the wallet computes the same digest as generateMessageHash and signs it.
* - Sign the digest returned by generateMessageHash as a raw hash—e.g.
* account.sign({ hash: contractDigest }) or equivalent raw-hash signing API—with no
* EIP-191/personal prefix.
*
* In all cases, this function applies ecrecover(messageHash, v, r, s) over the raw EIP-712 digest.
* personal_sign / EIP-191-prefixed signatures remain incompatible.
*
* @param messageHash The EIP-712 digest (keccak256("\x19\x01" || domainSeparator || structHash))
* @param signature The signature (r, s, v)
* @return The address of the signer
Expand Down
Loading
Loading