diff --git a/intentv1/intent_transaction_delayed_abi.go b/intentv1/intent_transaction_delayed_abi.go new file mode 100644 index 0000000..4c04645 --- /dev/null +++ b/intentv1/intent_transaction_delayed_abi.go @@ -0,0 +1,196 @@ +package intents + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + "github.com/0xsequence/ethkit/ethcoder" + "github.com/0xsequence/ethkit/go-ethereum/common" +) + +type delayedEncodeType struct { + Abi string `json:"abi"` + Func string `json:"func"` + Args json.RawMessage `json:"args"` +} + +func EncodeDelayedABI(data *delayedEncodeType) (string, error) { + // Get the method from the abi + method, order, err := getMethodFromAbi(data.Abi, data.Func) + if err != nil { + return "", err + } + + // Try decode args as array + var args1 []interface{} + err = json.Unmarshal(data.Args, &args1) + if err == nil { + enc := make([]string, len(args1)) + // String args can be used right away, but any nested + // `delayedEncodeType` must be handled recursively + for i, arg := range args1 { + switch arg.(type) { + case string: + enc[i] = arg.(string) + + case map[string]interface{}: + nst := arg.(map[string]interface{}) + + rjsn, err := json.Marshal(nst["args"]) + if err != nil { + return "", err + } + + enc[i], err = EncodeDelayedABI(&delayedEncodeType{ + Abi: nst["abi"].(string), + Func: nst["func"].(string), + Args: json.RawMessage(rjsn), + }) + if err != nil { + return "", err + } + + default: + return "", fmt.Errorf("invalid arg type") + } + } + + // Encode the method call + res, err := ethcoder.AbiEncodeMethodCalldataFromStringValues(method, enc) + if err != nil { + return "", err + } + + return "0x" + common.Bytes2Hex(res), nil + } + + // Try decode args as object + var args2 map[string]interface{} + err = json.Unmarshal(data.Args, &args2) + if err == nil { + // Convert args to array using the order from the abi + // call this method again, for simplicity + args := make([]interface{}, len(order)) + for i, argName := range order { + // If argName is not found, then fail + if _, ok := args2[argName]; !ok { + return "", fmt.Errorf("arg '%s' not found", argName) + } + + args[i] = args2[argName] + } + + jsn, err := json.Marshal(args) + if err != nil { + return "", err + } + + return EncodeDelayedABI(&delayedEncodeType{ + Abi: data.Abi, + Func: data.Func, + Args: jsn, + }) + } + + return "", err +} + +// The abi may be a: +// - already encoded method abi: transferFrom(address,address,uint256) +// - already encoded named method: transferFrom(address from,address to,uint256 val) +// - an array of function abis: "[{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_orderId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"_maxCost\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_fees\",\"type\":\"address\"}],\"name\":\"fillOrKillOrder\",\"outputs\":[],\"stateMutability\":\"view\",\"type\":\"function\"}]" +// - or a single function abi: "{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_orderId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"_maxCost\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_fees\",\"type\":\"address\"}],\"name\":\"fillOrKillOrder\",\"outputs\":[],\"stateMutability\":\"view\",\"type\":\"function\"}" +// And it must always return it encoded, like this: +// - transferFrom(address,address,uint256) +// making sure that the method matches the returned one +func getMethodFromAbi(abi string, method string) (string, []string, error) { + type FunctionAbi struct { + Name string `json:"name"` + Type string `json:"type"` + Inputs []struct { + InternalType string `json:"internalType"` + Name string `json:"name"` + Type string `json:"type"` + } `json:"inputs"` + } + + // Handle the case for already encoded method abi + if strings.Contains(abi, "(") && strings.Contains(abi, ")") && strings.HasPrefix(abi, method) { + // We may or may not have name information + // transferFrom(address,address,uint256) + // vs + // transferFrom(address from,address to,uint256 val) + + // Start by obtaning only the args + args := strings.Split(abi, "(")[1] + args = strings.Split(args, ")")[0] + + // Split the args by comma, to get the individual types + argTypes := strings.Split(args, ",") + + order := make([]string, len(argTypes)) + types := make([]string, len(argTypes)) + + incompleteNaming := false + + for i, arg := range argTypes { + // If starts with space, trim it + arg = strings.TrimLeft(arg, " ") + + if strings.Contains(arg, " ") { + // We have name information, so we need to extract it + spl := strings.Split(arg, " ") + + order[i] = spl[1] + types[i] = spl[0] + + } else { + // We don't have name information, so we must + // mark this case as incomplete + incompleteNaming = true + + // Assume that arg is the type + types[i] = arg + } + } + + if incompleteNaming { + order = nil + } + + // Re encode abi, now without name information + fnc := method + "(" + strings.Join(types, ",") + ")" + return fnc, order, nil + } + + // Handle array of function abis and single function abi + var abis []FunctionAbi + if strings.HasPrefix(abi, "[") { + if err := json.Unmarshal([]byte(abi), &abis); err != nil { + return "", nil, err + } + } else { + var singleAbi FunctionAbi + if err := json.Unmarshal([]byte(abi), &singleAbi); err != nil { + return "", nil, err + } + abis = append(abis, singleAbi) + } + + // Find the correct method and encode it + for _, fnAbi := range abis { + if fnAbi.Name == method { + var paramTypes []string + order := make([]string, len(fnAbi.Inputs)) + for i, input := range fnAbi.Inputs { + paramTypes = append(paramTypes, input.Type) + order[i] = input.Name + } + return method + "(" + strings.Join(paramTypes, ",") + ")", order, nil + } + } + + return "", nil, errors.New("Method not found in ABI") +} diff --git a/intentv1/intent_transaction_delayed_abi_test.go b/intentv1/intent_transaction_delayed_abi_test.go new file mode 100644 index 0000000..da1315b --- /dev/null +++ b/intentv1/intent_transaction_delayed_abi_test.go @@ -0,0 +1,153 @@ +package intents + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetMethodFromABI(t *testing.T) { + // From ABI, alone, in array + res, order, err := getMethodFromAbi(`[{"name":"transfer","type":"function","inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}]}]`, "transfer") + assert.Nil(t, err) + + assert.Equal(t, "transfer(address,uint256)", res) + assert.Equal(t, []string{"_to", "_value"}, order) + + // From ABI, alone, as object + res, order, err = getMethodFromAbi(`{"name":"transfer","type":"function","inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}]}`, "transfer") + assert.Nil(t, err) + + assert.Equal(t, "transfer(address,uint256)", res) + assert.Equal(t, []string{"_to", "_value"}, order) + + // From ABI, with many args + res, order, err = getMethodFromAbi(`[{"inputs":[{"internalType":"bytes32","name":"_orderId","type":"bytes32"},{"internalType":"uint256","name":"_maxCost","type":"uint256"},{"internalType":"address[]","name":"_fees","type":"address[]"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"fillOrKillOrder","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_val","type":"uint256"},{"internalType":"string","name":"_data","type":"string"}],"name":"notExpired","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"otherMethods","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, "fillOrKillOrder") + assert.Nil(t, err) + + assert.Equal(t, "fillOrKillOrder(bytes32,uint256,address[],bytes)", res) + assert.Equal(t, []string{"_orderId", "_maxCost", "_fees", "_data"}, order) + + res, order, err = getMethodFromAbi(`[{"inputs":[{"internalType":"bytes32","name":"_orderId","type":"bytes32"},{"internalType":"uint256","name":"_maxCost","type":"uint256"},{"internalType":"address[]","name":"_fees","type":"address[]"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"fillOrKillOrder","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_val","type":"uint256"},{"internalType":"string","name":"_data","type":"string"}],"name":"notExpired","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"otherMethods","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, "notExpired") + assert.Nil(t, err) + + assert.Equal(t, "notExpired(uint256,string)", res) + assert.Equal(t, []string{"_val", "_data"}, order) + + res, order, err = getMethodFromAbi(`[{"inputs":[{"internalType":"bytes32","name":"_orderId","type":"bytes32"},{"internalType":"uint256","name":"_maxCost","type":"uint256"},{"internalType":"address[]","name":"_fees","type":"address[]"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"fillOrKillOrder","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_val","type":"uint256"},{"internalType":"string","name":"_data","type":"string"}],"name":"notExpired","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"otherMethods","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, "otherMethods") + assert.Nil(t, err) + + assert.Equal(t, "otherMethods()", res) + assert.Equal(t, []string{}, order) + + // From plain method, without named args + res, order, err = getMethodFromAbi(`transfer(address,uint256)`, "transfer") + assert.Nil(t, err) + + assert.Equal(t, "transfer(address,uint256)", res) + assert.Nil(t, order) + + // From plain method, with named args + res, order, err = getMethodFromAbi(`transfer(address _to,uint256 _value, bytes _mas)`, "transfer") + assert.Nil(t, err) + + assert.Equal(t, "transfer(address,uint256,bytes)", res) + assert.Equal(t, []string{"_to", "_value", "_mas"}, order) + + // Mixed plain method should return nil order + res, order, err = getMethodFromAbi(`transfer(address _to,uint256, bytes _mas)`, "transfer") + + assert.Nil(t, err) + assert.Equal(t, "transfer(address,uint256,bytes)", res) + assert.Nil(t, order) +} + +func TestEncodeDelayedABI(t *testing.T) { + // Encode simple transferFrom, not named + res, err := EncodeDelayedABI(&delayedEncodeType{ + Abi: `[{"name":"transferFrom","type":"function","inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}]}]`, + Func: "transferFrom", + Args: json.RawMessage(`["0x0dc9603d4da53841C1C83f3B550C6143e60e0425","0x0dc9603d4da53841C1C83f3B550C6143e60e0425","100"]`), + }) + + assert.Nil(t, err) + assert.Equal(t, res, "0x23b872dd0000000000000000000000000dc9603d4da53841c1c83f3b550c6143e60e04250000000000000000000000000dc9603d4da53841c1c83f3b550c6143e60e04250000000000000000000000000000000000000000000000000000000000000064") + + // Encode simple transferFrom, named + res, err = EncodeDelayedABI(&delayedEncodeType{ + Abi: `[{"name":"transferFrom","type":"function","inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}]}]`, + Func: "transferFrom", + Args: json.RawMessage(`{"_from": "0x0dc9603d4da53841C1C83f3B550C6143e60e0425", "_value": "100", "_to": "0x0dc9603d4da53841C1C83f3B550C6143e60e0425"}`), + }) + + assert.Nil(t, err) + assert.Equal(t, res, "0x23b872dd0000000000000000000000000dc9603d4da53841c1c83f3b550c6143e60e04250000000000000000000000000dc9603d4da53841c1c83f3b550c6143e60e04250000000000000000000000000000000000000000000000000000000000000064") + + // Encode simple transferFrom, not named, passed as function + res, err = EncodeDelayedABI(&delayedEncodeType{ + Abi: `transferFrom(address,address,uint256)`, + Func: "transferFrom", + Args: json.RawMessage(`["0x13915b1ea28Fd2E8197c88ff9D2422182E83bf25","0x4Ad47F1611c78C824Ff3892c4aE1CC04637D6462","5192381927398174182391237"]`), + }) + + assert.Nil(t, err) + assert.Equal(t, res, "0x23b872dd00000000000000000000000013915b1ea28fd2e8197c88ff9d2422182e83bf250000000000000000000000004ad47f1611c78c824ff3892c4ae1cc04637d6462000000000000000000000000000000000000000000044b87969b06250e50bdc5") + + // Encode simple transferFrom, named, passed as function + res, err = EncodeDelayedABI(&delayedEncodeType{ + Abi: `transferFrom(address _from,address _to,uint256 _value)`, + Func: "transferFrom", + Args: json.RawMessage(`{"_from": "0x13915b1ea28Fd2E8197c88ff9D2422182E83bf25", "_value": "5192381927398174182391237", "_to": "0x4Ad47F1611c78C824Ff3892c4aE1CC04637D6462"}`), + }) + + assert.Nil(t, err) + assert.Equal(t, res, "0x23b872dd00000000000000000000000013915b1ea28fd2e8197c88ff9d2422182e83bf250000000000000000000000004ad47f1611c78c824ff3892c4ae1cc04637d6462000000000000000000000000000000000000000000044b87969b06250e50bdc5") + + // Encode nested bytes, passed as function + nestedEncodeType1 := &delayedEncodeType{ + Abi: `transferFrom(uint256)`, + Func: "transferFrom", + Args: json.RawMessage(`["481923749816926378123"]`), + } + + nestedEncodeType2 := &delayedEncodeType{ + Abi: `hola(string)`, + Func: "hola", + Args: json.RawMessage(`["mundo"]`), + } + + net1jsn, err := json.Marshal(nestedEncodeType1) + assert.Nil(t, err) + + net2jsn, err := json.Marshal(nestedEncodeType2) + assert.Nil(t, err) + + res, err = EncodeDelayedABI(&delayedEncodeType{ + Abi: `[{"inputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"bytes","name":"_arg1","type":"bytes"},{"internalType":"bytes","name":"_arg2","type":"bytes"}],"name":"caller","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_orderId","type":"bytes32"},{"internalType":"uint256","name":"_maxCost","type":"uint256"},{"internalType":"address[]","name":"_fees","type":"address[]"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"fillOrKillOrder","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_val","type":"uint256"},{"internalType":"string","name":"_data","type":"string"}],"name":"notExpired","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, + Func: "caller", + Args: json.RawMessage(`{"addr": "0x13915b1ea28Fd2E8197c88ff9D2422182E83bf25", "_arg1": ` + string(net1jsn) + `, "_arg2": ` + string(net2jsn) + `}`), + }) + + assert.Nil(t, err) + assert.Equal(t, res, "0x8b6701df00000000000000000000000013915b1ea28fd2e8197c88ff9d2422182e83bf25000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002477a11f7e00000000000000000000000000000000000000000000001a2009191df61e988b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000646ce8ea55000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000056d756e646f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + + // Fail passing named args to non-named abi + res, err = EncodeDelayedABI(&delayedEncodeType{ + Abi: `transferFrom(address,uint256)`, + Func: "transferFrom", + Args: json.RawMessage(`{"_from": "0x13915b1ea28Fd2E8197c88ff9D2422182E83bf25", "_value": "5192381927398174182391237", "_to": "0x4Ad47F1611c78C824Ff3892c4aE1CC04637D6462"}`), + }) + + assert.NotNil(t, err) + + // Accept passing ordened args to named abi + res, err = EncodeDelayedABI(&delayedEncodeType{ + Abi: `transferFrom(address _from,address _to,uint256 _value)`, + Func: "transferFrom", + Args: json.RawMessage(`["0x13915b1ea28Fd2E8197c88ff9D2422182E83bf25", "0x4Ad47F1611c78C824Ff3892c4aE1CC04637D6462", "9"]`), + }) + + assert.Nil(t, err) + assert.Equal(t, res, "0x23b872dd00000000000000000000000013915b1ea28fd2e8197c88ff9d2422182e83bf250000000000000000000000004ad47f1611c78c824ff3892c4ae1cc04637d64620000000000000000000000000000000000000000000000000000000000000009") +} diff --git a/intentv1/intent_transaction_ext.go b/intentv1/intent_transaction_ext.go index edd7b67..21bd97c 100644 --- a/intentv1/intent_transaction_ext.go +++ b/intentv1/intent_transaction_ext.go @@ -1 +1,352 @@ package intents + +import ( + "bytes" + "encoding/json" + "fmt" + "math/big" + + "github.com/0xsequence/ethkit/ethcoder" + "github.com/0xsequence/ethkit/go-ethereum/common" + "github.com/0xsequence/ethkit/go-ethereum/core/types" + "github.com/0xsequence/go-sequence" + "github.com/0xsequence/go-sequence/relayer/proto" +) + +func (p *IntentDataSendTransaction) chainID() (*big.Int, error) { + n, ok := sequence.ParseHexOrDec(p.Network) + if !ok { + return nil, fmt.Errorf("invalid network id '%s'", p.Network) + } + + return n, nil +} + +func (p *IntentDataSendTransaction) wallet() common.Address { + return common.HexToAddress(p.Wallet) +} + +type ExpectedValuesForTransaction struct { + To *common.Address + Value *big.Int + Data []byte +} + +func (p *IntentDataSendTransaction) ExpectedValuesFor(subpacket *json.RawMessage) (*ExpectedValuesForTransaction, error) { + // Get the subpacket type + var subpacketType struct { + Type string `json:"type"` + } + + err := json.Unmarshal(*subpacket, &subpacketType) + if err != nil { + return nil, err + } + + switch subpacketType.Type { + case "transaction": + // This packet explicitly defines the transaction values + var subpacketTransactionType struct { + To string `json:"to"` + Value string `json:"value"` + Data string `json:"data"` + } + + err := json.Unmarshal(*subpacket, &subpacketTransactionType) + if err != nil { + return nil, err + } + + to := common.HexToAddress(subpacketTransactionType.To) + value, ok := sequence.ParseHexOrDec(subpacketTransactionType.Value) + if !ok { + return nil, fmt.Errorf("invalid value '%s'", subpacketTransactionType.Value) + } + + data := common.FromHex(subpacketTransactionType.Data) + + return &ExpectedValuesForTransaction{ + To: &to, + Value: value, + Data: data, + }, nil + + case "erc20send": + // This packet defines the transaction values for an ERC20 transfer + // so this should be an ABI encoded transfer call to `to`. The `value` + // field must be 0. + var subpacketERC20SendType struct { + Token string `json:"token"` + To string `json:"to"` + Value string `json:"value"` + } + + err := json.Unmarshal(*subpacket, &subpacketERC20SendType) + if err != nil { + return nil, err + } + + to := common.HexToAddress(subpacketERC20SendType.To) + token := common.HexToAddress(subpacketERC20SendType.Token) + value, ok := sequence.ParseHexOrDec(subpacketERC20SendType.Value) + if !ok { + return nil, fmt.Errorf("invalid value '%s'", subpacketERC20SendType.Value) + } + + // Encode the transfer call + data, err := ethcoder.AbiEncodeMethodCalldata("transfer(address,uint256)", []interface{}{to, value}) + if err != nil { + return nil, err + } + + return &ExpectedValuesForTransaction{ + To: &token, + Value: big.NewInt(0), + Data: data, + }, nil + + case "erc721send": + // This packet defines the transaction values for an ERC721 transfer + // so this should be an ABI encoded transfer call to `to`. The `value` + // field must be 0. + var subpacketERC721SendType struct { + Token string `json:"token"` + To string `json:"to"` + ID string `json:"id"` + Safe bool `json:"safe,omitempty"` + Data string `json:"data,omitempty"` + } + + // Safe defaults to false + if err := json.Unmarshal(*subpacket, &subpacketERC721SendType); err != nil { + return nil, err + } + + // If data is not empty, then safe *must* be true + + to := common.HexToAddress(subpacketERC721SendType.To) + token := common.HexToAddress(subpacketERC721SendType.Token) + id, ok := sequence.ParseHexOrDec(subpacketERC721SendType.ID) + if !ok { + return nil, fmt.Errorf("invalid id '%s'", subpacketERC721SendType.ID) + } + data := common.FromHex(subpacketERC721SendType.Data) + + // If data is not empty, then safe *must* be true + if len(data) > 0 && !subpacketERC721SendType.Safe { + return nil, fmt.Errorf("safe must be true if data is not empty") + } + + var encodedData []byte + if subpacketERC721SendType.Safe { + // Encode the safe transfer call + encodedData, err = ethcoder.AbiEncodeMethodCalldata("safeTransferFrom(address,address,uint256,bytes)", []interface{}{p.wallet(), to, id, data}) + if err != nil { + return nil, err + } + } else { + // Encode the transfer call + encodedData, err = ethcoder.AbiEncodeMethodCalldata("transferFrom(address,address,uint256)", []interface{}{p.wallet(), to, id}) + if err != nil { + return nil, err + } + } + + return &ExpectedValuesForTransaction{ + To: &token, + Value: big.NewInt(0), + Data: encodedData, + }, nil + + case "erc1155send": + // This packet defines the transaction values for an ERC1155 transfer + // so this should be an ABI encoded transfer call to `to`. The `value` + // field must be 0. + type subpacketERC1155SendValsType struct { + ID string `json:"id"` + Amount string `json:"amount"` + } + + var subpacketERC1155SendType struct { + Token string `json:"token"` + To string `json:"to"` + Vals []subpacketERC1155SendValsType `json:"vals"` + Data string `json:"data,omitempty"` + } + + err := json.Unmarshal(*subpacket, &subpacketERC1155SendType) + if err != nil { + return nil, err + } + + to := common.HexToAddress(subpacketERC1155SendType.To) + token := common.HexToAddress(subpacketERC1155SendType.Token) + + var parsedIDs []*big.Int + var parsedAmounts []*big.Int + + for _, val := range subpacketERC1155SendType.Vals { + id, ok := sequence.ParseHexOrDec(val.ID) + if !ok { + return nil, fmt.Errorf("invalid id '%s'", val.ID) + } + + amount, ok := sequence.ParseHexOrDec(val.Amount) + if !ok { + return nil, fmt.Errorf("invalid amount '%s'", val.Amount) + } + + parsedIDs = append(parsedIDs, id) + parsedAmounts = append(parsedAmounts, amount) + } + + data := common.FromHex(subpacketERC1155SendType.Data) + + encodedData, err := ethcoder.AbiEncodeMethodCalldata("safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)", []interface{}{p.wallet(), to, parsedIDs, parsedAmounts, data}) + + if err != nil { + return nil, err + } + + return &ExpectedValuesForTransaction{ + To: &token, + Value: big.NewInt(0), + Data: encodedData, + }, nil + + case "delayedEncode": + var subpacketDelayedEncodeType struct { + To string `json:"to"` + Value string `json:"value"` + Data json.RawMessage `json:"data"` + } + + err := json.Unmarshal(*subpacket, &subpacketDelayedEncodeType) + if err != nil { + return nil, err + } + + nst := &delayedEncodeType{} + err = json.Unmarshal(subpacketDelayedEncodeType.Data, nst) + if err != nil { + return nil, err + } + + encoded, err := EncodeDelayedABI(nst) + if err != nil { + return nil, err + } + + to := common.HexToAddress(subpacketDelayedEncodeType.To) + value, ok := sequence.ParseHexOrDec(subpacketDelayedEncodeType.Value) + if !ok { + return nil, fmt.Errorf("invalid value '%s'", subpacketDelayedEncodeType.Value) + } + + return &ExpectedValuesForTransaction{ + To: &to, + Value: value, + Data: common.FromHex(encoded), + }, nil + default: + return nil, fmt.Errorf("invalid subpacket type '%s'", subpacketType.Type) + } +} + +func (p *IntentDataSendTransaction) Nonce() (*big.Int, error) { + // Hash the identifier, it will be used as the nonce + // space. The nonce number is always 0. + hashed := ethcoder.Keccak256([]byte(p.Identifier)) + + // The space contains only 160 bits + return sequence.EncodeNonce(big.NewInt(0).SetBytes(hashed[:20]), common.Big0) +} + +func (p *IntentDataSendTransaction) IsValidInterpretation(subdigest common.Hash, txns sequence.Transactions, nonce *big.Int) bool { + // Nonce must be the expected one + // (defined by the identifier) + enonce, err := p.Nonce() + if err != nil { + return false + } + + if enonce.Cmp(nonce) != 0 { + return false + } + + // Compare the digest with the provided transactions + // otherwise we can't be sure that the subdigest belongs to the transactions + bundle := sequence.Transaction{ + Transactions: txns, + Nonce: nonce, + } + + calcDigest, err := bundle.Digest() + if err != nil { + return false + } + + chainID, err := p.chainID() + if err != nil { + return false + } + + calcSubdigest, err := sequence.SubDigest(chainID, p.wallet(), calcDigest) + if err != nil || !bytes.Equal(calcSubdigest, subdigest[:]) { + return false + } + + // Now check that every transaction maps 1:1 to the transactions in the packet + // meaning that they follow the intent signed by it + if len(txns) != len(p.Transactions) { + return false + } + + for i, txn := range txns { + if txn.DelegateCall != false { + return false + } + + expected, err := p.ExpectedValuesFor(&p.Transactions[i]) + if err != nil { + return false + } + + if !bytes.Equal(txn.To.Bytes(), expected.To.Bytes()) { + return false + } + + if txn.Value.Cmp(expected.Value) != 0 { + return false + } + + if !bytes.Equal(txn.Data, expected.Data) { + return false + } + } + + return true +} + +type SendTransactionResponse struct { + Code string `json:"code"` + Data struct { + Request *IntentDataSendTransaction `json:"request"` + TxHash string `json:"txHash"` + Receipt *proto.MetaTxnReceipt `json:"receipt"` + NativeReceipt *types.Receipt `json:"nativeReceipt"` + Simulations []*proto.SimulateResult `json:"simulations"` + } +} + +const SendTransactionResponseCode = "transactionReceipt" + +type SendTransactionFailed struct { + Code string `json:"code"` + Data struct { + Request *IntentDataSendTransaction `json:"request"` + Simulations []*proto.SimulateResult `json:"simulations"` + } +} + +const SendTransactionFailedCode = "transactionFailed"