# 交易接口

## 1 构造并发送交易

要发送一个交易，均需要经过三个步骤：构造交易->交易签名->发送交易；

**构造交易：**填写交易的关键信息，从而构造一条完整的交易数据；
**交易签名：**对交易数据进行签名，即标识交易的所有者身份，也防止交易数据被篡改；
**发送交易：**将交易数据发送到区块链上去执行；

下面分别介绍三个接口

### 1.1 构造交易 CreateRawTransaction

**调用接口**

```
rpc CreateRawTransaction(CreateTx) returns (UnsignTx) {}
```

**参数：**

1. ctx context.Context 类型，默认可以用context.Background(), 其他用法可以参考context 包内容

2. CreateTx 结构体参数展示

   ```
   message CreateTx {
    string to          = 1;
    int64  amount      = 2;
    int64  fee         = 3;
    bytes  note        = 4;
    bool   isWithdraw  = 5;
    bool   isToken     = 6;
    string tokenSymbol = 7;
    string execName    = 8;
    string execer      = 9;
    }
   ```

**参数说明：**

| 参数        | 类型   | 是否必填 | 说明                                                         |
| :---------- | :----- | :------- | :----------------------------------------------------------- |
| to          | string | 是       | 发送到地址；如果是合约的充提则为合约地址，合约地址为execName转换而来，详情查看ConvertExectoAddr接口 |
| amount      | int64  | 是       | 发送金额，注意基础货币单位为10^8                             |
| fee         | int64  | 是       | 手续费，注意基础货币单位为10^8                               |
| note        | bytes  | 否       | 备注                                                         |
| isToken     | bool   | 否       | 是否是token类型的转账 （非token转账这个不用填 包括平行链的基础代币转账也不用填） |
| isWithdraw  | bool   | 是       | 是否为从合约中提款的交易，普通转账为false                    |
| tokenSymbol | string | 否       | token 的 symbol （非token转账这个不用填）                    |
| execName    | string | 否       | 目标合约名，如果要构造平行链上的转账或普通转账，此参数置空   |
| execer      | string | 是       | 资产的执行器名称，如果是普通转账，此处应填coins，如果是构造平行链的基础代币，此处要填写user.p.xxx.coins token同上 |

**返回数据：**

```
message UnsignTx {
    bytes data = 1;
}
```

**参数说明：**

| 参数 | 类型  | 说明                         |
| :--- | :---- | :--------------------------- |
| data | bytes | 交易对象的十六进制字符串编码 |

### 1.2 交易签名 SignRawTx

**调用接口**

```
rpc SignRawTx(ReqSignRawTx) returns (ReplySignRawTx) {}
```

**参数：**

```
message ReqSignRawTx {
    string addr    = 1;
    string privkey = 2;
    string txHex   = 3;
    string expire  = 4;
    int32  index   = 5;
    // 签名的模式类型
    // 0：普通交易
    // 1：隐私交易
    // int32  mode  = 6;
    string token = 7;
    int64  fee   = 8;
    // bytes  newExecer = 9;
    string newToAddr = 10;
}
```

**参数说明：**

| 参数      | 类型   | 是否必填 | 说明                                                         |
| :-------- | :----- | :------- | :----------------------------------------------------------- |
| addr      | string | 否       | addr与privkey可以只输入其一，如果使用addr则依赖钱包中存储的私钥签名 |
| privkey   | string | 否       | addr与privkey可以只输入其一，如果使用privkey则直接签名       |
| txHex     | string | 是       | 上一步生成的原始交易数据                                     |
| expire    | string | 是       | 过期时间可输入如”300s”，”-1.5h”或者”2h45m”的字符串，有效时间单位为”ns”, “us” (or “µs”), “ms”, “s”, “m”, “h” |
| index     | int32  | 否       | 若是签名交易组，则为要签名的交易序号，从1开始，小于等于0则为签名组内全部交易 |
| token     | string | 否       | token 名称                                                   |
| fee       | int64  | 是       | 费用                                                         |
| newToAddr | string | 否       | -                                                            |

**返回数据：**

```
message ReplySignRawTx {    string txHex = 1;}
```

**参数说明：**

| 参数  | 类型   | 说明                       |
| :---- | :----- | :------------------------- |
| txHex | string | 交易签名后的十六进制字符串 |

### 1.3 发送交易 SendTransaction

**调用接口**

```
rpc SendTransaction(Transaction) returns (Reply) {}
```

**参数：**

```
message Transaction {
    bytes     execer    = 1;
    bytes     payload   = 2;
    Signature signature = 3;
    int64     fee       = 4;
    int64     expire    = 5;
    //随机ID，可以防止payload 相同的时候，交易重复
    int64 nonce = 6;
    //对方地址，如果没有对方地址，可以为空
    string to         = 7;
    int32  groupCount = 8;
    bytes  header     = 9;
    bytes  next       = 10;
    int32  chainID    = 11;
}
message Signature {
    int32 ty     = 1;
    bytes pubkey = 2;
    bytes signature = 3;
}
```

**参数说明：**

| 参数                | 类型      | 是否必填 | 说明                                                         |
| :------------------ | :-------- | :------- | :----------------------------------------------------------- |
| execer              | bytes     | 是       | 资产的执行器名称，如果是普通转账，此处应填coins，如果是构造平行链的基础代币，此处要填写user.p.xxx.coins token同上 |
| payload             | bytes     | 是       | 参数                                                         |
| signature           | Signature | 是       | 签名                                                         |
| signature.ty        | int32     | 是       | 签名类型，1 -> secp256k1; 2 -> ed25519; 3 -> sm2; 4 -> OnetimeED25519; -> RingBaseonED25519 |
| signature.pubkey    | bytes     | 是       | 公钥                                                         |
| signature.signature | bytes     | 是       | 签名 //当ty为5时，格式应该用RingSignature去解析              |
| fee                 | int64     | 是       | 手续费，注意基础货币单位为10^8,默认10^5,即Fee=1e5            |
| expire              | int64     | 否       | 超时时间                                                     |
| nonce               | int64     | 否       | 随机ID                                                       |
| to                  | string    | 是       | 发送到地址；如果是合约的充提则为合约地址，合约地址为execName转换而来，详情查看ConvertExectoAddr接口 |
| groupCount          | int32     | 否       | 交易组                                                       |
| header              | bytes     | 否       | 区块头                                                       |
| next                | bytes     | 否       | 下一个交易的哈希                                             |
| chainID             | bool      | 是       | chain ID                                                     |

**返回数据：**

```
message Reply {    bool  isOk = 1;    bytes msg  = 2;}
```

**参数说明：**

| 参数 | 类型  | 说明                    |
| :--- | :---- | :---------------------- |
| isOk | bool  | 执行是否成功，true 成功 |
| msg  | bytes | 交易信息                |

**go 代码示例：**
假定 grpc服务地址：localhost:8802

```go
package main
import (
    "context"
    "github.com/33cn/chain33/common"
    chan33Ty "github.com/33cn/chain33/types"
    "google.golang.org/grpc"
)
func main() {
    conn, err := grpc.Dial("localhost:8802", grpc.WithInsecure(), grpc.WithBlock(), grpc.FailOnNonTempDialError(true))
    if err != nil {
        //异常处理逻辑
        panic(err)
    }
    defer conn.Close()
    gclient := chan33Ty.NewChain33Client(conn)
    txData, err := gclient.CreateRawTransaction(context.Background(), &chan33Ty.CreateTx{Amount: 10000000, To: "16hXXX", Execer: "coins"})
    if err != nil {
        panic(err)
    }
    signedTx, err := gclient.SignRawTx(context.Background(), &chan33Ty.ReqSignRawTx{Privkey: "xxx", TxHex: common.ToHex(txData.Data)})
    if err != nil {
        panic(err)
    }
    txBytes, err := common.FromHex(signedTx.TxHex)
    if err != nil {
        panic(err)
    }
    var tx chan33Ty.Transaction
    chan33Ty.Decode(txBytes, &tx)
    reply, err := gclient.SendTransaction(context.Background(), &tx)
    if err != nil {
        panic(err)
    }
    t.Log(reply.IsOk, common.ToHex(reply.Msg))
}
```

### 1.4 构造并发送不收手续费交易 CreateNoBalanceTransaction（平行链）

在平行链的使用场景中，可以发送不收手续费的交易，具体步骤如下：
构造交易 -> **平行链交易包装** -> 交易签名 -> 发送交易
后面的交易签名步骤里需要注意一点，参数index需填2

它和普通交易相比，多了一个 **平行链交易包装** 的步骤，对应接口如下

**调用接口**

```
rpc CreateNoBalanceTransaction(NoBalanceTx) returns (ReplySignRawTx) {}
```

**参数：**

```
message NoBalanceTx {
    string txHex   = 1;
    string payAddr = 2;
    string privkey = 3;
    string expire  = 4;
}
```

**参数说明：**

| 参数    | 类型   | 是否必填 | 说明                                                         |
| :------ | :----- | :------- | :----------------------------------------------------------- |
| txHex   | string | 是       | 未签名的原始交易数据                                         |
| payAddr | string | 是       | 用于付费的地址，这个地址要在主链上存在，并且里面有比特元用于支付手续费, payAddr与privkey可以只输入其一，如果使用payAddr则依赖钱包中存储的私钥签名 |
| privkey | string | 否       | 对应于payAddr的私钥。如果payAddr已经导入到平行链，可以只传地址 |
| expire  | string | 否       | 过期时间可输入如”300s”，”-1.5h”或者”2h45m”的字符串，有效时间单位为”ns”, “us” (or “µs”), “ms”, “s”, “m”, “h”， 不传递默认设置永不过期 |

**返回数据：**

```
message ReplySignRawTx {    string txHex = 1;}
```

**参数说明：**

| 参数  | 类型   | 说明                 |
| :---- | :----- | :------------------- |
| txHex | string | 未签名的原始交易数据 |

### 1.5 构造多笔并发送不收手续费交易 CreateNoBlanaceTxs（平行链）

同1.1.4 一样，可以创建发送不需要手续费的交易，不同的是，1.1.4接口只能构造单笔免手续费交易，而本接口可以创建多笔免手续费交易

**调用接口**

```
rpc CreateNoBalanceTxs(NoBalanceTxs) returns (ReplySignRawTx) {}
```

**参数：**

```
message NoBalanceTxs {
    repeated string txHexs  = 1;
    string          payAddr = 2;
    string          privkey = 3;
    string          expire  = 4;
}
```

**参数说明：**

| 参数    | 类型     | 是否必填 | 说明                                                         |
| :------ | :------- | :------- | :----------------------------------------------------------- |
| txHexs  | []string | 是       | 未签名的原始交易数据                                         |
| payAddr | string   | 是       | 用于付费的地址，这个地址要在主链上存在，并且里面有比特元用于支付手续费, payAddr与privkey可以只输入其一，如果使用payAddr则依赖钱包中存储的私钥签名 |
| privkey | string   | 否       | 对应于payAddr的私钥。如果payAddr已经导入到平行链，可以只传地址 |
| expire  | string   | 否       | 过期时间可输入如”300s”，”-1.5h”或者”2h45m”的字符串，有效时间单位为”ns”, “us” (or “µs”), “ms”, “s”, “m”, “h”， 不传递默认设置永不过期 |

**返回数据：**

```
message ReplySignRawTx {    string txHex = 1;}
```

**参数说明：**

| 参数  | 类型   | 说明                 |
| :---- | :----- | :------------------- |
| txHex | string | 未签名的原始交易数据 |

### 1.6 重写交易 ReWriteRawTx

支持对原始交易或交易组参数重写

**调用接口**

```
rpc ReWriteTx(ReWriteRawTx)returns(UnsignTx){}
```

**参数：**

```
message ReWriteRawTx {
    string tx = 1;
    // bytes  execer = 2;
    string to     = 3;
    string expire = 4;
    int64  fee    = 5;
    int32  index  = 6;
}
```

**参数说明：**

| 参数   | 类型   | 是否必填 | 说明                                                         |
| :----- | :----- | :------- | :----------------------------------------------------------- |
| to     | string | 否       | 重写交易的目的地址，只有单笔交易生效，交易组不生效           |
| fee    | int64  | 否       | 重写交易的费用，交易组只会修改第一笔交易的费用               |
| tx     | string | 是       | 需要重写的原始交易数据                                       |
| expire | string | 否       | 过期时间可输入如”300ms”，”-1.5h”或者”2h45m”的字符串，有效时间单位为”ns”, “us” (or “µs”), “ms”, “s”, “m”, “h” |
| index  | int32  | 否       | 若是交易组，则为要重写的交易序号，从1开始，小于等于0则为交易组内全部交易 |

**返回数据：**

```
message UnsignTx {    bytes data = 1;}
```

**参数说明：**

| 参数 | 类型  | 说明                         |
| :--- | :---- | :--------------------------- |
| data | bytes | 重写之后交易的十六进制字符串 |

### 1.7 错误信息

| code            | output                         | description                       |
| :-------------- | :----------------------------- | :-------------------------------- |
| txExistErr      | transaction exists             | 该交易已存在mempool中             |
| lowFeeErr       | low transaction fee            | 交易费过低                        |
| manyTxErr       | you have too many transactions | 同一账户在mempool中有超过10笔交易 |
| signErr         | wrong signature                | 签名错误                          |
| lowBalanceErr   | low balance                    | 余额不足                          |
| bigMsgErr       | message too big                | 消息过大                          |
| expireErr       | message expired                | 消息过期                          |
| loadAccountsErr | loadacconts error              | 匹配账户错误                      |
| emptyTxErr      | empty transaction              | 交易为空                          |
| dupTxErr        | duplicated transaction         | 重复交易                          |
| memNotReadyErr  | mempool not ready              | mempool未启动                     |
| memFullErr      | mempool is full                | mempool已满                       |

## 2 根据哈希查询交易信息 QueryTransaction

**调用接口**

```
rpc QueryTransaction(ReqHash) returns (TransactionDetail) {}
```

**参数：**

```
message ReqHash {
    bytes hash    = 1;
    bool  upgrade = 2;
}
```

**参数说明：**

| 参数    | 类型  | 是否必填 | 说明     |
| :------ | :---- | :------- | :------- |
| hash    | bytes | 是       | 交易哈希 |
| upgrade | bool  | 否       | 是否升级 |

**返回数据：**

```
message TransactionDetail {
    Transaction tx            = 1;
    ReceiptData receipt       = 2;
    repeated bytes proofs     = 3;
    int64          height     = 4;
    int64          index      = 5;
    int64          blocktime  = 6;
    int64          amount     = 7;
    string         fromaddr   = 8;
    string         actionName = 9;
    repeated Asset assets     = 10;
    repeated TxProof txProofs = 11;
    bytes            fullHash = 12;
}
message ReceiptData {
    int32    ty              = 1;
    repeated ReceiptLog logs = 3;
}
```

**参数说明：**

| 参数       | 类型        | 说明                                                         |
| :--------- | :---------- | :----------------------------------------------------------- |
| tx         | Transaction | 交易基本信息                                                 |
| receipt    | ReceiptData | 交易执行结果信息                                             |
| actionName | string      | 操作名称，不同的执行器可能会有不同的值，如coins（transfer，withdraw，genesis），ticket（genesis，open，close，miner） |
| receipt.Ty | int32       | receipt.ty == 1 表示执行失败；receipt.ty == 2 表示执行成功   |

## 3 根据地址获取交易信息 GetTransactionByAddr

**调用接口**

```
rpc GetTransactionByAddr(ReqAddr) returns (ReplyTxInfos) {}
```

**参数：**

```
message ReqAddr {
    string addr = 1;
    //表示取所有/from/to/其他的hash列表
    int32 flag      = 2;
    int32 count     = 3;
    int32 direction = 4;
    // height start
    int64 height    = 5;
    int64 index     = 6;
    int64 heightEnd = 7;
}
```

**参数说明：**

| 参数      | 类型   | 是否必填 | 说明                                                         |
| :-------- | :----- | :------- | :----------------------------------------------------------- |
| addr      | string | 是       | 要查询的账户地址                                             |
| count     | int32  | 是       | 返回的数据条数                                               |
| direction | int32  | 是       | 查询的方向；0：正向查询，区块高度从低到高；-1：反向查询；    |
| flag      | int32  | 否       | 交易类型；0：所有涉及到addr的交易； 1：addr作为发送方； 2：addr作为接收方； |
| height    | int64  | 否       | 交易所在的block高度，-1：表示从最新的开始向后取；大于等于0的值，从具体的高度+具体index开始取 |
| index     | int64  | 否       | 交易所在block中的索引，取值0—100000                          |

**返回数据：**

```
message ReplyTxInfos {
    repeated ReplyTxInfo txInfos = 1;
}
message ReplyTxInfo {
    bytes    hash         = 1;
    int64    height       = 2;
    int64    index        = 3;
    repeated Asset assets = 4;
}
message Asset {
    string exec   = 1;
    string symbol = 2;
    int64  amount = 3;
}
```

**参数说明：**

| 参数           | 类型          | 说明                                                      |
| :------------- | :------------ | :-------------------------------------------------------- |
| txInfos        | []ReplyTxInfo | 交易数组；包含交易的哈希、高度、以及资产信息；            |
| txInfos.hash   | bytes         | 交易 id，可以通过接口 QueryTransaction 获取具体的交易信息 |
| txInfos.assets | []Asset       | 资产信息， 列出交易相关的资产。 可能整个数组 为 null      |

## 4 根据哈希数组批量获取交易信息 GetTransactionByHashes

**调用接口**

```
rpc GetTransactionByHashes(ReqHashes) returns (TransactionDetails) {}
```

**参数：**

```
message ReqHashes {
    repeated bytes hashes = 1;
}
```

**参数说明：**

| 参数   | 类型    | 是否必填 | 说明       |
| :----- | :------ | :------- | :--------- |
| hashes | []bytes | 是       | 交易ID列表 |

**返回数据：**

```
message TransactionDetails {
    repeated TransactionDetail txs = 1;
}
message TransactionDetail {
    Transaction tx            = 1;
    ReceiptData receipt       = 2;
    repeated bytes proofs     = 3;
    int64          height     = 4;
    int64          index      = 5;
    int64          blocktime  = 6;
    int64          amount     = 7;
    string         fromaddr   = 8;
    string         actionName = 9;
    repeated Asset assets     = 10;
    repeated TxProof txProofs = 11;
    bytes            fullHash = 12;
}
```

**参数说明：**

| 参数 | 类型                | 说明                                          |
| :--- | :------------------ | :-------------------------------------------- |
| txs  | []TransactionDetail | 单个交易详情信息，请参考 QueryTransaction接口 |

## 3 根据哈希获取交易的字符串 GetHexTxByHash

**调用接口**

```
rpc GetHexTxByHash(ReqHash) returns (HexTx) {}
```

**参数：**

```
message ReqHash {
    bytes hash    = 1;
    bool  upgrade = 2;
}
```

**参数说明：**

| 参数 | 类型  | 是否必填 | 说明     |
| :--- | :---- | :------- | :------- |
| hash | bytes | 是       | 交易哈希 |

**返回数据：**

```
message HexTx {    string tx = 1;}
```

**参数说明：**

| 参数 | 类型   | 说明                       |
| :--- | :----- | :------------------------- |
| tx   | string | 交易对象的十六进制编码数据 |

## 4 获取地址相关摘要信息 GetAddrOverview

**调用接口**

```
rpc GetAddrOverview(ReqAddr) returns (AddrOverview) {}
```

**参数：**

```
message ReqAddr {
    string addr = 1;
    //表示取所有/from/to/其他的hash列表
    int32 flag      = 2;
    int32 count     = 3;
    int32 direction = 4;
    int64 height    = 5;
    int64 index     = 6;
}
```

**参数说明：**

| 参数      | 类型   | 是否必填 | 说明                                                         |
| :-------- | :----- | :------- | :----------------------------------------------------------- |
| addr      | string | 是       | 要查询的账户地址                                             |
| count     | int32  | 是       | 返回的数据条数                                               |
| direction | int32  | 是       | 查询的方向；0：正向查询，区块高度从低到高；-1：反向查询；    |
| flag      | int32  | 否       | 交易类型；0：所有涉及到addr的交易； 1：addr作为发送方； 2：addr作为接收方； |
| height    | int64  | 否       | 交易所在的block高度，-1：表示从最新的开始向后取；大于等于0的值，从具体的高度+具体index开始取 |
| index     | int64  | 否       | 交易所在block中的索引，取值0—100000                          |

> 注意：本接口目前只是用到了addr参数，其它参数均无意义；

**返回数据：**

```
message AddrOverview {
    int64 reciver = 1;
    int64 balance = 2;
    int64 txCount = 3;
}
```

**参数说明：**

| 参数    | 类型  | 说明           |
| :------ | :---- | :------------- |
| reciver | int64 | 一共接收的金额 |
| txCount | int64 | 交易量计数     |
| balance | int64 | 当前余额       |

## 7 将合约名转成实际地址 ConvertExectoAddr

**调用接口**

```
rpc ConvertExectoAddr(ReqString) returns (ReplyString) {}
```

**参数：**

```
message ReqString {
    string data = 1;
}
```

**参数说明：**

| 参数 | 类型   | 是否必填 | 说明                                                         |
| :--- | :----- | :------- | :----------------------------------------------------------- |
| data | string | 是       | 执行器名称，如果需要往执行器中转币这样的操作，需要调用些接口将执行器名转成实际地址 |

**返回数据：**

```
message ReplyString {
    string data = 1;
}
```

**参数说明：**

| 参数 | 类型   | 说明                 |
| :--- | :----- | :------------------- |
| data | string | 转换生成的地址字符串 |

## 5 构造交易组 CreateRawTxGroup

**调用接口**

```
rpc CreateRawTxGroup(CreateTransactionGroup) returns (UnsignTx) {}
```

**参数：**

```
message CreateTransactionGroup {
    repeated string txs = 1;
}
```

**参数说明：**

| 参数 | 类型     | 是否必填 | 说明                 |
| :--- | :------- | :------- | :------------------- |
| txs  | []string | 是       | 十六进制格式交易数组 |

**返回数据：**

```
message UnsignTx {
    bytes data = 1;
}
```

**参数说明：**

| 参数 | 类型  | 说明                       |
| :--- | :---- | :------------------------- |
| data | bytes | 交易组对象的十六进制字符串 |

## 6 获取单元交易费率 GetProperFee

**调用接口**

```
rpc GetProperFee(ReqProperFee) returns (ReplyProperFee) {}
```

**参数：**

```
message ReqProperFee {
    int32 txCount = 1;
    int32 txSize  = 2;
}
```

**参数说明：**

| 参数    | 类型  | 是否必填 | 说明                                    |
| :------ | :---- | :------- | :-------------------------------------- |
| txCount | int32 | 否       | 预发送的交易个数,单个交易发送默认空即可 |
| txSize  | int32 | 否       | 预发送交易的大小, 单位Byte, 字节        |

- **通常采用默认传递即可**
- 对于阶梯交易费模式,在**同时发送多笔交易**时, 可以指定将要发送的个数和总大小(字节)进行前瞻性估计

**返回数据：**

```
message ReplyProperFee {
    int64 properFee = 1;
}
```

**参数说明：**

| 参数      | 类型  | 说明                                   |
| :-------- | :---- | :------------------------------------- |
| properFee | int64 | 每KB交易大小所需交易费, 单位1/108的BTY |

**设置说明：**

- 手动设置
  上述接口返回的是单元大小的交易费率, 即每KB所需的交易费，
  同时交易需要在签名前设置交易费，需要预估签名数据所占用大小，普通交易公私钥签名可预设为300字节

  ```
  //参考代码
  txFee := int64((txSizeKB+300)/1000+1) * properFee
  ```

- 自动设置
  bityuan 6.2.1+版本, 以下接口支持自动设置合适交易费

  - Chain33.SendToAddress
  - Chain33.SignRawTx(**交易费参数保留未设置或为0**)

- 失败处理
  在实际应用中, 仍然可能出现交易费过低导致失败情况, 建议代码中增加出错处理,
  如失败时将交易费倍数递增（需要重新签名且注意设置上限，通常是0.1）, 或者等待一段时间继续尝试
  相关错误码:

  > “ErrTxFeeTooLow” 交易手续费太低
