From 564ca1a7170b5329e043e55584d874a2f4db59a1 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:50:37 +0000 Subject: [PATCH 1/5] feat: add batch transfer abi --- src/Enums/ContractAbiType.php | 9 +-- .../Abi/json/Abi.ERC20BatchTransfer.json | 59 +++++++++++++++++++ src/Utils/AbiBase.php | 2 + 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/Utils/Abi/json/Abi.ERC20BatchTransfer.json diff --git a/src/Enums/ContractAbiType.php b/src/Enums/ContractAbiType.php index f38b7093..8098a341 100644 --- a/src/Enums/ContractAbiType.php +++ b/src/Enums/ContractAbiType.php @@ -6,8 +6,9 @@ enum ContractAbiType: string { - case CUSTOM = 'custom'; - case CONSENSUS = 'consensus'; - case MULTIPAYMENT = 'multipayment'; - case USERNAMES = 'usernames'; + case CUSTOM = 'custom'; + case CONSENSUS = 'consensus'; + case MULTIPAYMENT = 'multipayment'; + case USERNAMES = 'usernames'; + case ERC20BATCH_TRANSFER = 'erc20batchtransfer'; } diff --git a/src/Utils/Abi/json/Abi.ERC20BatchTransfer.json b/src/Utils/Abi/json/Abi.ERC20BatchTransfer.json new file mode 100644 index 00000000..f3ddafa9 --- /dev/null +++ b/src/Utils/Abi/json/Abi.ERC20BatchTransfer.json @@ -0,0 +1,59 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "ERC20BatchTransfer", + "sourceName": "config/solidity/ERC20BatchTransfer.sol", + "abi": [ + { + "inputs": [], + "name": "LengthMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address[]", + "name": "to", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amount", + "type": "uint256[]" + } + ], + "name": "batchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "to", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amount", + "type": "uint256[]" + } + ], + "name": "multiBatchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x6080604052348015600e575f5ffd5b5061083e8061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610034575f3560e01c80634885b25414610038578063f053e99f14610054575b5f5ffd5b610052600480360381019061004d9190610491565b610070565b005b61006e60048036038101906100699190610577565b6101cd565b005b8181905084849050146100af576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b848490508110156101c5578573ffffffffffffffffffffffffffffffffffffffff166323b872dd338787858181106100ec576100eb610627565b5b9050602002016020810190610101919061067e565b86868681811061011457610113610627565b5b905060200201356040518463ffffffff1660e01b8152600401610139939291906106d0565b6020604051808303815f875af1158015610155573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610179919061073a565b6101b8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101af906107bf565b60405180910390fd5b80806001019150506100b1565b505050505050565b85859050848490501415806101e85750858590508282905014155b1561021f576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f90505b8686905081101561035f5786868281811061024257610241610627565b5b905060200201602081019061025791906107dd565b73ffffffffffffffffffffffffffffffffffffffff166323b872dd3387878581811061028657610285610627565b5b905060200201602081019061029b919061067e565b8686868181106102ae576102ad610627565b5b905060200201356040518463ffffffff1660e01b81526004016102d3939291906106d0565b6020604051808303815f875af11580156102ef573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610313919061073a565b610352576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610349906107bf565b60405180910390fd5b8080600101915050610224565b50505050505050565b5f5ffd5b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61039982610370565b9050919050565b5f6103aa8261038f565b9050919050565b6103ba816103a0565b81146103c4575f5ffd5b50565b5f813590506103d5816103b1565b92915050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f8401126103fc576103fb6103db565b5b8235905067ffffffffffffffff811115610419576104186103df565b5b602083019150836020820283011115610435576104346103e3565b5b9250929050565b5f5f83601f840112610451576104506103db565b5b8235905067ffffffffffffffff81111561046e5761046d6103df565b5b60208301915083602082028301111561048a576104896103e3565b5b9250929050565b5f5f5f5f5f606086880312156104aa576104a9610368565b5b5f6104b7888289016103c7565b955050602086013567ffffffffffffffff8111156104d8576104d761036c565b5b6104e4888289016103e7565b9450945050604086013567ffffffffffffffff8111156105075761050661036c565b5b6105138882890161043c565b92509250509295509295909350565b5f5f83601f840112610537576105366103db565b5b8235905067ffffffffffffffff811115610554576105536103df565b5b6020830191508360208202830111156105705761056f6103e3565b5b9250929050565b5f5f5f5f5f5f6060878903121561059157610590610368565b5b5f87013567ffffffffffffffff8111156105ae576105ad61036c565b5b6105ba89828a01610522565b9650965050602087013567ffffffffffffffff8111156105dd576105dc61036c565b5b6105e989828a016103e7565b9450945050604087013567ffffffffffffffff81111561060c5761060b61036c565b5b61061889828a0161043c565b92509250509295509295509295565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b61065d8161038f565b8114610667575f5ffd5b50565b5f8135905061067881610654565b92915050565b5f6020828403121561069357610692610368565b5b5f6106a08482850161066a565b91505092915050565b6106b28161038f565b82525050565b5f819050919050565b6106ca816106b8565b82525050565b5f6060820190506106e35f8301866106a9565b6106f060208301856106a9565b6106fd60408301846106c1565b949350505050565b5f8115159050919050565b61071981610705565b8114610723575f5ffd5b50565b5f8151905061073481610710565b92915050565b5f6020828403121561074f5761074e610368565b5b5f61075c84828501610726565b91505092915050565b5f82825260208201905092915050565b7f5452414e534645525f46524f4d5f4641494c45440000000000000000000000005f82015250565b5f6107a9601483610765565b91506107b482610775565b602082019050919050565b5f6020820190508181035f8301526107d68161079d565b9050919050565b5f602082840312156107f2576107f1610368565b5b5f6107ff848285016103c7565b9150509291505056fea264697066735822122072bc5bd8cacc8452edacb73e0f0321cdfbba275969c6c2476826da50766804ee64736f6c634300081e0033" +} diff --git a/src/Utils/AbiBase.php b/src/Utils/AbiBase.php index 40824197..1ac7b1d9 100644 --- a/src/Utils/AbiBase.php +++ b/src/Utils/AbiBase.php @@ -84,6 +84,8 @@ private function contractAbiPath(ContractAbiType $type, ?string $path = null): ? return __DIR__.'/Abi/json/Abi.Multipayment.json'; case ContractAbiType::USERNAMES: return __DIR__.'/Abi/json/Abi.Usernames.json'; + case ContractAbiType::ERC20BATCH_TRANSFER: + return __DIR__.'/Abi/json/Abi.ERC20BatchTransfer.json'; case ContractAbiType::CUSTOM: return $path; } From 76812dc4dc31ab1f4a5cd2c2ec79ab027e16f840 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:51:29 +0000 Subject: [PATCH 2/5] refactor: assume custom if no match for other abi types --- src/Utils/AbiBase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/AbiBase.php b/src/Utils/AbiBase.php index 1ac7b1d9..60b4dfa4 100644 --- a/src/Utils/AbiBase.php +++ b/src/Utils/AbiBase.php @@ -86,8 +86,8 @@ private function contractAbiPath(ContractAbiType $type, ?string $path = null): ? return __DIR__.'/Abi/json/Abi.Usernames.json'; case ContractAbiType::ERC20BATCH_TRANSFER: return __DIR__.'/Abi/json/Abi.ERC20BatchTransfer.json'; - case ContractAbiType::CUSTOM: - return $path; } + + return $path; } } From 03d3e755487aac198855736c3f606ece1ed953b9 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:04:03 +0000 Subject: [PATCH 3/5] Update src/Utils/AbiBase.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Utils/AbiBase.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Utils/AbiBase.php b/src/Utils/AbiBase.php index 60b4dfa4..75f66bc7 100644 --- a/src/Utils/AbiBase.php +++ b/src/Utils/AbiBase.php @@ -86,8 +86,14 @@ private function contractAbiPath(ContractAbiType $type, ?string $path = null): ? return __DIR__.'/Abi/json/Abi.Usernames.json'; case ContractAbiType::ERC20BATCH_TRANSFER: return __DIR__.'/Abi/json/Abi.ERC20BatchTransfer.json'; + case ContractAbiType::CUSTOM: + if ($path === null || $path === '') { + throw new \InvalidArgumentException('A non-empty $path must be provided when using ContractAbiType::CUSTOM.'); + } + + return $path; + default: + throw new \InvalidArgumentException('Unhandled ContractAbiType: '.$type->name); } - - return $path; } } From 5121f0ee993320bcb6fe95781a14920e81dcb3be Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:21:07 +0000 Subject: [PATCH 4/5] add test for batch transfer --- tests/Unit/Utils/AbiEncoderTest.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Unit/Utils/AbiEncoderTest.php b/tests/Unit/Utils/AbiEncoderTest.php index e812741c..f37b36b3 100644 --- a/tests/Unit/Utils/AbiEncoderTest.php +++ b/tests/Unit/Utils/AbiEncoderTest.php @@ -97,6 +97,23 @@ function testPrivateMethod(string $methodName, &$object): ReflectionMethod expect($encodedData)->toBe($expectedData); }); +test('should encode batch transfer payload', function () { + $encoder = new AbiEncoder(ContractAbiType::ERC20BATCH_TRANSFER); + + $functionName = 'batchTransferFrom'; + $args = [ + '0x8444ab9d74212f28e14b089b62ed4a4a7a8fefb3', + ['0xa5cc0bfeb09742c5e4c610f2ebaab82eb142ca10', '0xe3c31e486cca6eb2093c0f4883df949d45b021c5', '0xa5cc0bfeb09742c5e4c610f2ebaab82eb142ca10'], + ['1', '2', '3'], + ]; + + $expectedData = '0x4885b2540000000000000000000000008444ab9d74212f28e14b089b62ed4a4a7a8fefb3000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000a5cc0bfeb09742c5e4c610f2ebaab82eb142ca10000000000000000000000000e3c31e486cca6eb2093c0f4883df949d45b021c5000000000000000000000000a5cc0bfeb09742c5e4c610f2ebaab82eb142ca100000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003'; + + $encodedData = $encoder->encodeFunctionCall($functionName, $args); + + expect($encodedData)->toBe($expectedData); +}); + it('should throw an exception for unknown function', function () { $encoder = new AbiEncoder(); From 45d538253819cde30e19408475786b86f1b98173 Mon Sep 17 00:00:00 2001 From: ItsANameToo Date: Tue, 10 Mar 2026 15:26:28 +0100 Subject: [PATCH 5/5] wip --- src/Enums/ContractAbiType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Enums/ContractAbiType.php b/src/Enums/ContractAbiType.php index 8d0025a7..29a80c82 100644 --- a/src/Enums/ContractAbiType.php +++ b/src/Enums/ContractAbiType.php @@ -5,7 +5,7 @@ namespace ArkEcosystem\Crypto\Enums; enum ContractAbiType: string - +{ case CUSTOM = 'custom'; case CONSENSUS = 'consensus'; case ERC20BATCH_TRANSFER = 'erc20batchtransfer';