Skip to content

Commit 6b57c0d

Browse files
authored
Merge pull request #127 from PracticalParticle/dev
Dev
2 parents 64b7543 + 54e9ded commit 6b57c0d

37 files changed

Lines changed: 1524 additions & 1202 deletions

.github/dependabot.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Dependabot opens PRs against dev so they follow the dev -> main flow
2+
# required by .github/workflows/require-dev-to-main.yml
3+
version: 2
4+
updates:
5+
# Root workspace / tooling
6+
- package-ecosystem: "npm"
7+
directory: "/"
8+
schedule:
9+
interval: "weekly"
10+
target-branch: "dev"
11+
groups:
12+
npm_and_yarn:
13+
patterns:
14+
- "*"
15+
16+
# Doc generation utilities
17+
- package-ecosystem: "npm"
18+
directory: "/docgen"
19+
schedule:
20+
interval: "weekly"
21+
target-branch: "dev"
22+
23+
# Published contract package
24+
- package-ecosystem: "npm"
25+
directory: "/package"
26+
schedule:
27+
interval: "weekly"
28+
target-branch: "dev"
29+
30+
# TypeScript SDK package
31+
- package-ecosystem: "npm"
32+
directory: "/sdk/typescript"
33+
schedule:
34+
interval: "weekly"
35+
target-branch: "dev"

.github/workflows/sync-contract-versions.yml

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,34 @@ jobs:
3333
- name: Install dependencies
3434
run: npm ci
3535

36-
- name: Check for uncommitted changes in EngineBlox.sol
36+
- name: Check for uncommitted changes in version files
3737
id: check-uncommitted
3838
run: |
39+
SOL_CHANGED=false
40+
TSX_CHANGED=false
3941
if ! git diff --quiet HEAD -- contracts/core/lib/EngineBlox.sol; then
40-
echo "❌ ERROR: EngineBlox.sol has uncommitted changes!"
42+
SOL_CHANGED=true
43+
fi
44+
if ! git diff --quiet HEAD -- sdk/typescript/lib/EngineBlox.tsx; then
45+
TSX_CHANGED=true
46+
fi
47+
if [ "$SOL_CHANGED" = true ] || [ "$TSX_CHANGED" = true ]; then
48+
echo "❌ ERROR: Version files have uncommitted changes!"
4149
echo ""
4250
echo "The workflow cannot safely update version constants when there are uncommitted changes."
43-
echo "Please commit or stash your changes to EngineBlox.sol before this workflow runs."
51+
echo "Please commit or stash your changes before this workflow runs."
4452
echo ""
45-
echo "Uncommitted changes:"
46-
git diff HEAD -- contracts/core/lib/EngineBlox.sol
53+
if [ "$SOL_CHANGED" = true ]; then
54+
echo "Uncommitted changes in contracts/core/lib/EngineBlox.sol:"
55+
git diff HEAD -- contracts/core/lib/EngineBlox.sol
56+
fi
57+
if [ "$TSX_CHANGED" = true ]; then
58+
echo "Uncommitted changes in sdk/typescript/lib/EngineBlox.tsx:"
59+
git diff HEAD -- sdk/typescript/lib/EngineBlox.tsx
60+
fi
4761
exit 1
4862
else
49-
echo "✓ EngineBlox.sol is clean, safe to proceed with version sync"
63+
echo "✓ EngineBlox.sol and EngineBlox.tsx are clean, safe to proceed with version sync"
5064
echo "uncommitted=false" >> $GITHUB_OUTPUT
5165
fi
5266
@@ -56,44 +70,61 @@ jobs:
5670
- name: Check for contract version changes
5771
id: check-changes
5872
run: |
73+
SOL_CHANGED=false
74+
TSX_CHANGED=false
5975
if ! git diff --quiet contracts/core/lib/EngineBlox.sol; then
76+
SOL_CHANGED=true
77+
fi
78+
if ! git diff --quiet sdk/typescript/lib/EngineBlox.tsx; then
79+
TSX_CHANGED=true
80+
fi
81+
if [ "$SOL_CHANGED" = true ] || [ "$TSX_CHANGED" = true ]; then
6082
echo "changed=true" >> $GITHUB_OUTPUT
6183
echo "Version changes detected:"
62-
git diff contracts/core/lib/EngineBlox.sol
84+
[ "$SOL_CHANGED" = true ] && git diff contracts/core/lib/EngineBlox.sol
85+
[ "$TSX_CHANGED" = true ] && git diff sdk/typescript/lib/EngineBlox.tsx
6386
else
6487
echo "changed=false" >> $GITHUB_OUTPUT
65-
echo "✓ Contract version constants already in sync"
88+
echo "✓ Contract and SDK version constants already in sync"
6689
fi
6790
6891
- name: Verify only version constants changed
6992
if: steps.check-changes.outputs.changed == 'true'
7093
id: verify-changes
7194
run: |
72-
# Get the diff and check if only VERSION_* constants changed
73-
DIFF_OUTPUT=$(git diff contracts/core/lib/EngineBlox.sol)
95+
# Get the diff for both version files
96+
DIFF_OUTPUT=$(git diff contracts/core/lib/EngineBlox.sol sdk/typescript/lib/EngineBlox.tsx)
7497
75-
# Check if diff contains only version constant changes
76-
# Version constants are: VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH
77-
if echo "$DIFF_OUTPUT" | grep -qE "^[-+].*VERSION_(MAJOR|MINOR|PATCH)" && \
78-
! echo "$DIFF_OUTPUT" | grep -vE "^[-+].*VERSION_(MAJOR|MINOR|PATCH)|^@|^index|^---|^\+\+\+" | grep -qE "^[-+]"; then
79-
echo "✓ Only version constants changed, safe to commit"
80-
echo "safe=true" >> $GITHUB_OUTPUT
81-
else
82-
echo "❌ ERROR: Changes detected in EngineBlox.sol are not limited to version constants!"
98+
# All added/removed lines must be a VERSION constant change (.sol: constant VERSION = ... or .tsx: static readonly VERSION: string = ...)
99+
ADDED_OR_REMOVED=$(echo "$DIFF_OUTPUT" | grep -E "^[-+]" | grep -v -E "^\+\+\+|^---")
100+
OTHER_CHANGES=$(echo "$ADDED_OR_REMOVED" | grep -v -E "constant\s+VERSION\s*=" | grep -v -E "static readonly VERSION: string =")
101+
102+
if [ -n "$OTHER_CHANGES" ]; then
103+
echo "❌ ERROR: Changes are not limited to VERSION constants!"
83104
echo ""
84-
echo "The workflow detected changes beyond VERSION_MAJOR, VERSION_MINOR, or VERSION_PATCH."
105+
echo "The workflow detected changes beyond the VERSION constant in EngineBlox.sol or EngineBlox.VERSION in EngineBlox.tsx."
85106
echo "This could indicate uncommitted changes were accidentally included."
86107
echo ""
87108
echo "Full diff:"
88109
echo "$DIFF_OUTPUT"
89110
exit 1
90111
fi
112+
113+
# Require at least one VERSION change (sol or tsx)
114+
if ! echo "$DIFF_OUTPUT" | grep -qE "^[-+].*constant\s+VERSION\s*=" && \
115+
! echo "$DIFF_OUTPUT" | grep -qE "^[-+].*static readonly VERSION: string ="; then
116+
echo "❌ ERROR: No VERSION constant changes found in diff."
117+
exit 1
118+
fi
119+
120+
echo "✓ Only version constants changed (contract + SDK), safe to commit"
121+
echo "safe=true" >> $GITHUB_OUTPUT
91122
92123
- name: Commit and push contract version updates
93124
if: steps.check-changes.outputs.changed == 'true' && steps.verify-changes.outputs.safe == 'true'
94125
run: |
95126
git config user.name "github-actions[bot]"
96127
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
97-
git add contracts/core/lib/EngineBlox.sol
128+
git add contracts/core/lib/EngineBlox.sol sdk/typescript/lib/EngineBlox.tsx
98129
git commit -m "chore: sync contract version constants with package version"
99130
git push

abi/EngineBlox.abi.json

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -689,38 +689,12 @@
689689
},
690690
{
691691
"inputs": [],
692-
"name": "VERSION_MAJOR",
692+
"name": "VERSION",
693693
"outputs": [
694694
{
695-
"internalType": "uint8",
696-
"name": "",
697-
"type": "uint8"
698-
}
699-
],
700-
"stateMutability": "view",
701-
"type": "function"
702-
},
703-
{
704-
"inputs": [],
705-
"name": "VERSION_MINOR",
706-
"outputs": [
707-
{
708-
"internalType": "uint8",
695+
"internalType": "string",
709696
"name": "",
710-
"type": "uint8"
711-
}
712-
],
713-
"stateMutability": "view",
714-
"type": "function"
715-
},
716-
{
717-
"inputs": [],
718-
"name": "VERSION_PATCH",
719-
"outputs": [
720-
{
721-
"internalType": "uint8",
722-
"name": "",
723-
"type": "uint8"
697+
"type": "string"
724698
}
725699
],
726700
"stateMutability": "view",

contracts/core/lib/EngineBlox.sol

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ import "./interfaces/IEventForwarder.sol";
3232
library EngineBlox {
3333
// ============ VERSION INFORMATION ============
3434
bytes32 public constant PROTOCOL_NAME_HASH = keccak256("Bloxchain");
35-
uint8 public constant VERSION_MAJOR = 1;
36-
uint8 public constant VERSION_MINOR = 0;
37-
uint8 public constant VERSION_PATCH = 0;
35+
string public constant VERSION = "1.0.0";
3836

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

208206
// EIP-712 Type Hashes (selective meta-tx payload: MetaTxRecord = txId + params + payment only)
209-
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)");
210-
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)");
207+
// These follow the canonical EIP-712 convention so that eth_signTypedData_v4 and equivalent
208+
// wallet typed-data signers can reproduce the same hashes when given matching type definitions.
209+
//
210+
// Canonical primary type string for MetaTransaction (primary type + all referenced types,
211+
// appended in alphabetical order by type name
212+
bytes32 private constant META_TX_TYPE_HASH = keccak256(
213+
"MetaTransaction(MetaTxRecord txRecord,MetaTxParams params,bytes data)"
214+
"MetaTxParams(uint256 chainId,uint256 nonce,address handlerContract,bytes4 handlerSelector,uint8 action,uint256 deadline,uint256 maxGasPrice,address signer)"
215+
"MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)"
216+
"PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)"
217+
"TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)"
218+
);
219+
220+
// Canonical primary type string for MetaTxRecord (primary type + its referenced types).
221+
bytes32 private constant META_TX_RECORD_TYPE_HASH = keccak256(
222+
"MetaTxRecord(uint256 txId,TxParams params,PaymentDetails payment)"
223+
"PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)"
224+
"TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)"
225+
);
211226
bytes32 private constant TX_PARAMS_TYPE_HASH = keccak256("TxParams(address requester,address target,uint256 value,uint256 gasLimit,bytes32 operationType,bytes4 executionSelector,bytes executionParams)");
212227
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)");
213228
bytes32 private constant PAYMENT_DETAILS_TYPE_HASH = keccak256("PaymentDetails(address recipient,uint256 nativeTokenAmount,address erc20TokenAddress,uint256 erc20TokenAmount)");
@@ -1690,27 +1705,36 @@ library EngineBlox {
16901705

16911706
/**
16921707
* @dev Generates the EIP-712 message hash for the meta-transaction.
1693-
* Uses selective MetaTxRecord (txId, params, payment only).
1694-
* Integrators must sign this digest as a raw hash with no EIP-191 or personal_sign prefix—
1695-
* e.g. account.sign({ hash: contractDigest }) or equivalent raw-hash signing API—so that
1696-
* signatures match the raw ecrecover(messageHash, v, r, s) verification in recoverSigner.
1697-
* Do NOT use personal_sign or generic eth_signTypedData_v4; the contract uses
1698-
* abi.encodePacked for the version string and a custom META_TX_TYPE_HASH, so those produce
1699-
* incompatible hashes.
1700-
* The resulting digest is also written into the `message` field of the helper-built
1701-
* `MetaTransaction` structs (see `createMetaTransactionForSigning`) so integrators can use
1702-
* it directly without recomputing the hash client-side.
1708+
* Uses selective MetaTxRecord (txId, params, payment only) with standard EIP-712 type hashes
1709+
* so that eth_signTypedData_v4 (and equivalent) can reproduce the same digest when given
1710+
* matching domain + types:
1711+
*
1712+
* - primaryType: MetaTransaction
1713+
* - domain: { name: "Bloxchain", version: "1.0.0", chainId, verifyingContract }
1714+
* - types: MetaTransaction, MetaTxRecord, TxParams, MetaTxParams, PaymentDetails
1715+
*
1716+
* Integrators MAY:
1717+
* - use typed-data signing (eth_signTypedData_v4 / signTypedData) with the above domain/types, or
1718+
* - sign the resulting digest as a raw hash (e.g. account.sign({ hash: contractDigest })).
1719+
*
1720+
* In all cases, on-chain verification uses recoverSigner(messageHash, signature) which applies
1721+
* ecrecover(messageHash, v, r, s) with no personal_sign / EIP-191 prefix.
1722+
*
1723+
* The resulting digest is also written into the `message` field of helper-built `MetaTransaction`
1724+
* structs so integrators can use it directly without recomputing the hash client-side.
17031725
* @param metaTx The meta-transaction to generate the hash for
17041726
* @return The EIP-712 digest (no prefix; use standard recovery)
17051727
*/
17061728
function generateMessageHash(MetaTransaction memory metaTx) private view returns (bytes32) {
1707-
bytes32 domainSeparator = keccak256(abi.encode(
1708-
DOMAIN_SEPARATOR_TYPE_HASH,
1709-
PROTOCOL_NAME_HASH,
1710-
keccak256(abi.encodePacked(VERSION_MAJOR, ".", VERSION_MINOR, ".", VERSION_PATCH)),
1711-
block.chainid,
1712-
address(this)
1713-
));
1729+
bytes32 domainSeparator = keccak256(
1730+
abi.encode(
1731+
DOMAIN_SEPARATOR_TYPE_HASH,
1732+
PROTOCOL_NAME_HASH,
1733+
keccak256(bytes(VERSION)),
1734+
block.chainid,
1735+
address(this)
1736+
)
1737+
);
17141738

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

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

0 commit comments

Comments
 (0)