# 🤝🏻 合作-API：🕹集成GMGN Solana交易API

✅ 若通过审核，GMGN会将API Key发送至你填写的邮箱\
❌ 若未通过审核，GMGN将不会发送任何信息通知

### 鉴权

* GMGN为我们的专业用户提供 SOL/BSC/Base/ETH 交易API，能否通过的唯一标准是你在GMGN是否有足够的交易量。审核将在提交后24小时内完成
* 访问GMGN Trade Api需申请使用权限，请填写以下google表单申请，<https://forms.gle/CWABDLRe8twvygvy5>
* 审核通过后GMGN会分配一个Api Key至你的邮箱，访问接口需将Api Key带入header中，header name:  `x-route-key`
* 每个Api Key限频5s调用一次

## 1.**查询路由接入点(支持防夹)：**

```javascript
https://gmgn.ai/defi/router/v1/sol/tx/get_swap_route?token_in_address=${inputToken}&token_out_address=${outputToken}&in_amount=${amount}&from_address=${fromAddress}&slippage=${slippage}
```

**请求方式： GET**

**API Key: 申请通过后发放至邮箱**

**输入参数：**

<table><thead><tr><th width="230">参数名</th><th width="126">类型</th><th>说明</th></tr></thead><tbody><tr><td>token_in_address</td><td>string</td><td>兑换要花的币，输入代币地址，例如： So11111111111111111111111111111111111111112</td></tr><tr><td>token_out_address</td><td>string</td><td>兑换目标币地址，例如：7EYnhQoR9YM3N7UoaKRoA44Uy8JeaZV3qyouov87awMs</td></tr><tr><td>in_amount</td><td>string</td><td>最小单位lamports，100000000=0.1SOL</td></tr><tr><td>from_address</td><td>string</td><td>发起交易钱包地址，例如：2kpJ5QRh16aRQ4oLZ5LnucHFDAZtEFz6omqWWMzDSNrx</td></tr><tr><td>slippage</td><td>float</td><td>滑点，%之上的数值，比如10表示10%</td></tr><tr><td>swap_mode</td><td>string</td><td>ExactIn或者ExactOut，默认不传则是ExactIn</td></tr><tr><td>fee</td><td>float</td><td>网络和节点优先费。例如0.006, 单位SOL。GMGN会自动分配节点贿赂小费和网络优先费。 如果开防夹，fee需要至少 0.002</td></tr><tr><td>is_anti_mev</td><td>bool</td><td>可选，防夹交易时设为true</td></tr><tr><td>partner</td><td>string</td><td>可选，合作伙伴来源名称</td></tr></tbody></table>

返回参数：

<table><thead><tr><th width="129">参数名</th><th width="229">类型</th><th>说明</th></tr></thead><tbody><tr><td>code</td><td>int</td><td>错误码，0</td></tr><tr><td>msg</td><td>string</td><td>结果描述，success</td></tr><tr><td>data</td><td>Object</td><td><p>{</p><p>     quote: {</p><p>       inputMint: 'So11111111111111111111111111111111111111112',</p><p>       inAmount: '50000000',</p><p>       outputMint: '7EYnhQoR9YM3N7UoaKRoA44Uy8JeaZV3qyouov87awMs',</p><p>       outAmount: '77920478752',</p><p>       otherAmountThreshold: '77530876359',</p><p>       swapMode: 'ExactIn',</p><p>       slippageBps: 50,</p><p>       platformFee: null,</p><p>       priceImpactPct: '0',</p><p>       routePlan: [Array],</p><p>       contextSlot: 240893434,</p><p>       timeTaken: 0.04250061</p><p>     },</p><p>     raw_tx: {</p><p>       swapTransaction: 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAIDxoVIz70VFGJdL5OoCKmFca4NP2MXOcNEiFO6y6JoFUNGWxA6mmc96B1sKHdcYHTM1M1QYNOOSahRPMddzM8XdRsp8RcOMdvjkujt1otIW/R6tIhAHlyC8kUDXj133RK+Ye1I/p3Gy551653GdDPX3KX0D4dWPXyZvsBemb3XlwHyQ+0ezK8pEPmQbXwZ8Tz3O52/YACoT20v0iE4A7D1bTgIWD7ScmJ4Koq6/mSOQVxqfFkoUZ5qTF4TbXDFfH1U+qMakgEp96ZVF6SJaXlGdV6w0mXkHJVedqOxh5rfjqMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAAR51VvyMcBu7nTFbs5oFQf9sbLeo/SOUQKxzaJWvBOPBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKlcnp1fowmGSs19gRjTJjE83nuG3xjhl5JKAxhv/p89eoKX5UT/REBqE7f6ZMLP+j7G4V34k14Ax0Fw3q7wE/N9jJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+Fm0P/on9df2SnTAmx8pWHneSwmrNt/J3VFLMhqns4zl6PSRqslFk9OGKukg94vbePj7SOGor/mX5COZhlMmlOYbCAgABQI6UwcACAAJAyBOAAAAAAAADQYABQAnBwoBAQcCAAUMAgAAAIDw+gIAAAAACgEFARENBgACAAsHCgEBCTMKDAAFAwYCJwsJCQ4JIAoMHAMdAR4aHxsoIiMYDAEEFxkWFQokJiUgCgwSBhMEFBAPESEuwSCbM0HWnIEGAwAAABEBZAABGWQBAhEAZAIDgPD6AgAAAAAgJmwkEgAAADIAAAoDBQAAAQkDegmW2ZAMszm+4Eneq5orFPHiSoriEDigyGyG9FUTd+oGpqilp6GiAp+jq4YFkrWa2UlUnmJKVsvmeovDYVI3GhRB1TB5/repN9MFSEVDREcFQkxGSUuC/JDNwibla4b9UHQsSYKI/HmR9edfsxbKg36RCgUpKAaNi5CGiogCkY4=',</p><p>       lastValidBlockHeight: 221852977,</p><p>       prioritizationFeeLamports: 9601,</p><p>       recentBlockhash: 'HThJomQ74BKBYYfFewg9m5MrRwAsHjrpuTwEcohxpAEW'</p><p>     }</p><p>   }</p></td></tr></tbody></table>

返回data.quote字段说明：

| 参数名                  | 类型     | 说明                                                  |
| -------------------- | ------ | --------------------------------------------------- |
| inputMint            | string | 输入币种的地址，So11111111111111111111111111111111111111112 |
| inAmount             | string | 输入币种数量，最小单位lamports                                 |
| outputMint           | string | 输出币种地址，7EYnhQoR9YM3N7UoaKRoA44Uy8JeaZV3qyouov87awMs |
| outputAmount         | string | 路由计算输出理论值，最小单位lamports                              |
| otherAmountThreshold | string | 计算进滑点的值，最小单位lamports                                |
| swapMode             | string | ExactIn或者ExactOut                                   |
| slippageBps          | 50     | 滑点Bps值,10000为分母上面的值                                 |
| platformFee          | string | 平台费，null                                            |
| priceImpact          | string | 价格影响比例，0                                            |
| routePlan            | Array  | 路由步骤                                                |
| contextSlot          | int    | slot数                                               |
| timeTaken            | float  | 寻路花费时间，秒                                            |

上面的routePlan 类型说明：

<figure><img src="https://1305672697-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FivH0y4hvvQWQlOnFKiAg%2Fuploads%2FRoJdSe578NyA1z3hch1O%2Fimage.png?alt=media&#x26;token=07c5eadb-1c16-4243-886b-0317d015d333" alt=""><figcaption></figcaption></figure>

返回data.raw\_tx字段说明：

<table data-header-hidden><thead><tr><th width="266"></th><th width="153"></th><th></th></tr></thead><tbody><tr><td>参数名</td><td>类型</td><td>说明</td></tr><tr><td>swapTransaction</td><td>string</td><td>base64编码的待签名tx</td></tr><tr><td>lastValidBlockHeight</td><td>int</td><td>打包待签名tx时的区块高度，221852977</td></tr><tr><td>recentBlockhash</td><td>string</td><td>打包待签名tx时的最新上链的块hash，HThJomQ74BKBYYfFewg9m5MrRwAsHjrpuTwEcohxpAEW</td></tr><tr><td>prioritizationFeeLamports</td><td>int</td><td>推荐优先费用，最小单位lamports，9601</td></tr></tbody></table>

## 2. 提交交易接入点

```javascript
https://gmgn.ai/txproxy/v1/send_transaction
```

**请求方式：POST**

**拿到路由接口返回后，需要用base64解码并且使用`VersionedTransaction`反序列化，并用本地钱包签名，签名之后用bs64 encode之后post给接口：**

`const swapTransactionBuf = Buffer.from(route.data.raw_tx.swapTransaction, 'base64') const transaction = VersionedTransaction.deserialize(swapTransactionBuf) transaction.sign([wallet.payer]) const signedTx = Buffer.from(transaction.serialize()).toString("base64")`

请求参数：

<table data-header-hidden><thead><tr><th width="163.63018798828125"></th><th width="163.140625"></th><th></th></tr></thead><tbody><tr><td><strong>参数名</strong></td><td><strong>类型</strong></td><td><strong>说明</strong></td></tr><tr><td>chain</td><td>string</td><td>目前仅支持SOL链</td></tr><tr><td>signedTx</td><td>string</td><td>签名后的交易，使用base64 encode之后的字串</td></tr><tr><td>isAntiMev</td><td>bool</td><td>是否防夾</td></tr></tbody></table>

返回字段：

<table data-header-hidden><thead><tr><th width="159.0625"></th><th width="164.76171875"></th><th></th></tr></thead><tbody><tr><td><strong>参数名</strong></td><td><strong>类型</strong></td><td><strong>说明</strong></td></tr><tr><td>code</td><td>int</td><td>错误码，0</td></tr><tr><td>msg</td><td>string</td><td>错误信息，success</td></tr><tr><td>data</td><td>Object</td><td><p>{<br>"hash": "2WN388zpPEy5zZR1uDGRqYbWotHFWo2Y6atAwfLGwUvDEi8LGk93X9S5pimNMv4uQgpJNf6SFQbZF83XbvgTuikj"<br>"resArr": [<br>{<br>"hash": "2WN388zpPEy5zZR1uDGRqYbWotHFWo2Y6atAwfLGwUvDEi8LGk93X9S5pimNMv4uQgpJNf6SFQbZF83XbvgTuikj",<br>"err": null<br>},<br>{<br>"hash": "2WN388zpPEy5zZR1uDGRqYbWotHFWo2Y6atAwfLGwUvDEi8LGk93X9S5pimNMv4uQgpJNf6SFQbZF83XbvgTuikj",<br>"err": null<br>}<br>]<br>}</p><p> </p></td></tr></tbody></table>

## 3. 查询交易状态接入点：

```javascript
https://gmgn.ai/defi/router/v1/sol/tx/get_transaction_status?hash=${hash}&last_valid_height
```

**请求方式：GET**

**请求参数：**

<table><thead><tr><th width="199">参数名</th><th width="150">类型</th><th>说明</th></tr></thead><tbody><tr><td>hash</td><td>string</td><td>提交完交易后返回的hash，例如3Qr9Kb8XfQiTa2E4butFRdVgWb9jTorcQaCPx3zx9fETyZhnffVdjagGU7PkwJVX8X9Js4xvUjybCaNjvFGozoLR</td></tr><tr><td>last_valid_height</td><td>int</td><td>路由接口返回的待签名交易打包时的块高度，例如221852977</td></tr></tbody></table>

**返回字段：**

<table><thead><tr><th width="199">参数名</th><th width="154">类型</th><th>说明</th></tr></thead><tbody><tr><td>code</td><td>int</td><td>0</td></tr><tr><td>msg</td><td>string</td><td>success</td></tr><tr><td>data</td><td>Object</td><td>{ success: true, expired: false }</td></tr></tbody></table>

**data字段说明：**

success： 是否成功上链， true则为成功上链。

failed: 是否上链且失败， true则为上链且失败。

expired：是否过期失效。

如expired=true， success=false则表示该交易已经过期，需要重新提交。一般一个交易60秒失效。

如success=true则表示成功上链。

**Example code:**

```
import { Wallet } from '@project-serum/anchor'
import { Connection, Keypair, VersionedTransaction,LAMPORTS_PER_SOL } from '@solana/web3.js'
import bs58 from 'bs58';
import fetch from 'node-fetch'
import sleep from './util/sleep.js'
const inputToken = 'So11111111111111111111111111111111111111112'
const outputToken = '7EYnhQoR9YM3N7UoaKRoA44Uy8JeaZV3qyouov87awMs'
const amount = '50000000'
const fromAddress = '2kpJ5QRh16aRQ4oLZ5LnucHFDAZtEFz6omqWWMzDSNrx'
const slippage = 0.5
// GMGN API 域名
const API_HOST = 'https://gmgn.ai'
async function main() {
  // 钱包初始化，如果用Phantom则忽略该步骤
  const wallet = new Wallet(Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY || '')))
  console.log(`wallet address: ${wallet.publicKey.toString()}`)
  // 获取quote以及待签名交易
  const quoteUrl = `${API_HOST}/defi/router/v1/sol/tx/get_swap_route?token_in_address=${inputToken}&token_out_address=${outputToken}&in_amount=${amount}&from_address=${fromAddress}&slippage=${slippage}`
  let route = await fetch(quoteUrl)
  route = await route.json()
  console.log(route)
  // 签名交易
  const swapTransactionBuf = Buffer.from(route.data.raw_tx.swapTransaction, 'base64')
  const transaction = VersionedTransaction.deserialize(swapTransactionBuf)
  transaction.sign([wallet.payer])
  const signedTx = Buffer.from(transaction.serialize()).toString('base64')
  console.log(signedTx)
  // 提交交易
  let res = await fetch(`${API_HOST}/txproxy/v1/send_transaction`,
    {
      method: 'POST',
      headers: {'content-type': 'application/json'},
      body: JSON.stringify(
        {
          "chain": "sol",
          "signedTx": signedTx
        }
      )
    })
  res = await res.json()
  console.log(res)
  // 查询tx状态
  // 如果上链成功，则success返回true
  // 如果没上链，60秒就会返回expired=true
  while (true) {
    const hash =  res.data.hash
    const lastValidBlockHeight = route.data.raw_tx.lastValidBlockHeight
    const statusUrl = `${API_HOST}/defi/router/v1/sol/tx/get_transaction_status?hash=${hash}&last_valid_height=${lastValidBlockHeight}`
    let status = await fetch(statusUrl)
    status = await status.json()
    console.log(status)
    if (status && (status.data.success === true || status.data.expired === true))
      break
    await sleep(1000)
  }
}
main()

```
