# Gale Bridge Widget 合作夥伴整合指南

適用版本：`@galebridge/widget@0.1.0`。更新日期：2026 年 10 月 15 日。

將 Gale Bridge 跨鏈操作介面嵌入合作夥伴網站。宿主應用程式提供頁面容器與使用者的錢包 Provider；Widget 負責資產選擇、報價、授權提示、交易提交與訂單追蹤。npm 和 CDN 使用相同的 SDK API，選擇其中一種載入方式即可。

| 資源 | 網址 |
| --- | --- |
| npm 套件 | [@galebridge/widget](https://www.npmjs.com/package/@galebridge/widget) |
| CDN SDK | `https://galebridge.net/widget/v1/widget.js` |
| 託管 Widget 頁面 | [https://galebridge.net/widget](https://galebridge.net/widget) |
| 可用網路與代幣 | [Integration Networks API](https://galebridge.net/api/v1/integration/networks) |

## 整合前準備

- 正式頁面使用 HTTPS；本機開發使用 `http://localhost` 或 `http://127.0.0.1`，不要以 `file://` 直接開啟 HTML。
- 掛載前先建立 DOM 容器。建議寬度為 `100%`、最大寬度約 `500px`；SDK 會依內容調整 iframe 高度。
- 使用支援 BigInt、`crypto.randomUUID()` 的現代瀏覽器。React / Next.js 應在用戶端元件掛載後建立 Widget。
- 嵌入式交易使用宿主提供的錢包適配器。EVM 使用 EIP-1193 Provider；TRON 使用 TronLink 或相容 Provider。建立 Widget 本身不會要求連接錢包或簽章。
- 將 `your-project-web` 換成穩定的專案識別碼。同一宿主來源下的獨立 Widget 應使用不同識別碼，以區分待處理交易的復原記錄。`clientId` 為公開標記，不代表身分驗證或佣金歸屬。

未提供錢包適配器時，Widget 仍可載入，並提供開啟獨立頁面的連結；在 iframe 內交易需要對應生態的適配器。以下範例使用英文介面；文件語言與 Widget 介面語言各自獨立。

## 方式一 npm 整合

安裝固定版本：

```bash
npm install @galebridge/widget@0.1.0
```

在頁面加入容器與狀態提示：

```html
<div id="bridge" style="width:100%;max-width:500px;margin:auto"></div>
<p id="bridge-status" role="status" aria-live="polite"></p>
```

將以下程式碼加入 Vite、Webpack 等建置工具的用戶端入口：

```ts
import { createWidget, createEvmAdapter, type EVMProvider } from '@galebridge/widget'

const provider = (window as Window & { ethereum?: EVMProvider }).ethereum
const showStatus = (text: string) => {
  const element = document.querySelector('#bridge-status')
  if (element) element.textContent = text
}

const widget = createWidget({
  container: '#bridge',
  clientId: 'your-project-web',
  theme: 'light',
  lang: 'en',
  wallets: provider ? { evm: createEvmAdapter(provider) } : {},
})

widget.on('error', ({ code, message }) => {
  showStatus(message)
  console.error('[Gale Widget]', code, message)
})
widget.on('transactionSent', ({ txHash }) => {
  showStatus('Source transaction submitted; waiting for completion: ' + txHash)
})
widget.on('completed', ({ txHash, order }) => {
  showStatus('Bridge order completed: ' + txHash)
  console.log('Order details', order)
})
void widget.ready.catch((error: Error) => showStatus(error.message))

// Call when the SPA page or component unmounts:
// widget.destroy()
```

範例使用瀏覽器注入的 EVM 錢包。若應用程式已有錢包連接器，請將其選定的 EIP-1193 Provider 傳入 `createEvmAdapter(provider)`，讓宿主與 Widget 使用同一個錢包。此 TypeScript 必須經建置工具編譯，不應直接當作一般瀏覽器腳本載入。

### React 與 Next.js 範例

`provider` 由宿主的錢包連接模組提供，請維持穩定的參照。未提供時，使用者可開啟獨立頁面。使用 Next.js App Router 時，保留檔案開頭的 `'use client'`。

```tsx
'use client'

import { useEffect, useRef, useState } from 'react'
import { createWidget, createEvmAdapter, type EVMProvider } from '@galebridge/widget'

export function GaleBridge({ provider }: { provider?: EVMProvider }) {
  const container = useRef<HTMLDivElement>(null)
  const [status, setStatus] = useState('')

  useEffect(() => {
    if (!container.current) return
    setStatus('')
    const widget = createWidget({
      container: container.current,
      clientId: 'your-project-web',
      lang: 'en',
      theme: 'light',
      wallets: provider ? { evm: createEvmAdapter(provider) } : {},
    })
    widget.on('error', ({ message }) => setStatus(message))
    widget.on('transactionSent', ({ txHash }) => setStatus('Transaction submitted: ' + txHash))
    widget.on('completed', ({ txHash }) => setStatus('Bridge order completed: ' + txHash))
    void widget.ready.catch((error: Error) => setStatus(error.message))

    return () => widget.destroy()
  }, [provider])

  return (
    <section>
      <div ref={container} style={{ width: '100%', maxWidth: 500 }} />
      <p role="status" aria-live="polite">{status}</p>
    </section>
  )
}
```

上述元件會在 `provider` 改變時重新建立 Widget。簽章或追蹤訂單期間，應避免主動更換 Provider 或卸載元件；卸載不會取消已提交的鏈上交易。

## 方式二 CDN 整合

不需安裝 npm 套件。將以下內容儲存為 `cdn.html`，並透過 HTTP(S) 存取。SDK 以全域變數 `GaleBridge` 提供 API，請先載入腳本，再呼叫 `createWidget`。

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Gale Bridge integration example</title>
  </head>
  <body>
    <main style="max-width:500px;margin:24px auto;padding:0 12px">
      <div id="bridge"></div>
      <p id="bridge-status" role="status" aria-live="polite"></p>
    </main>
    <script src="https://galebridge.net/widget/v1/widget.js"></script>
    <script>
      const status = document.getElementById('bridge-status')
      if (!window.GaleBridge) {
        status.textContent = 'SDK failed to load. Check the network and CSP settings.'
      } else {
        const provider = window.ethereum
        const widget = GaleBridge.createWidget({
          container: '#bridge',
          clientId: 'your-project-web',
          lang: 'en',
          theme: 'light',
          wallets: provider ? { evm: GaleBridge.createEvmAdapter(provider) } : {},
        })
        widget.on('error', ({ code, message }) => {
          status.textContent = message
          console.error('[Gale Widget]', code, message)
        })
        widget.on('transactionSent', ({ txHash }) => {
          status.textContent = 'Source transaction submitted; waiting for completion: ' + txHash
        })
        widget.on('completed', ({ txHash, order }) => {
          status.textContent = 'Bridge order completed: ' + txHash
          console.log('Order details', order)
        })
        widget.ready.catch((error) => { status.textContent = error.message })
        // Call widget.destroy() when leaving this SPA page.
      }
    </script>
  </body>
</html>
```

`/widget/v1/widget.js` 為持續維護的正式網址，不是固定 npm 版本的不可變網址。如需固定 SDK 版本，請使用 npm 並鎖定 `0.1.0`。固定 SDK 版本不會固定託管頁面、可用資產或後端路由。

### 設定 Widget 語言

託管 Widget 預設為英文。如需繁體中文，可在建立時使用既有的 `zh` 值，或於閒置時更新：

```ts
widget.update({ lang: 'zh' }) // Traditional Chinese
// widget.update({ lang: 'en' }) // English
```

使用者也可在 Widget 頂部切換語言，追蹤待處理交易時亦可使用。此操作僅改變顯示，不會發送或取消交易。

## 錢包整合

### EVM 錢包

```ts
import { createEvmAdapter } from '@galebridge/widget'

// provider is the EIP-1193 Provider selected by the host app.
widget.setWallet('evm', createEvmAdapter(provider))
```

透過 `setWallet` 更換適配器後，使用者需按 Widget 的連接按鈕重新連接。EVM 適配器可切換至錢包已設定的網路，不會自動新增未知網路；提交前會再次檢查帳戶與網路。授權使用本次轉帳的準確額度。若代幣要求先將既有授權額度歸零，合作夥伴應提供符合其要求的自訂適配器。

### TRON 錢包

在宿主注入 TronLink 或相容 TRON Provider 後，保留 getter，讓適配器可於連接後讀取更新的 `tronWeb`：

```js
// npm: import createTronAdapter from @galebridge/widget.
// CDN: use const { createTronAdapter } = GaleBridge.
const provider = window.tronLink ?? window.tron ?? window.okxwallet?.tronLink
if (provider?.request) {
  widget.setWallet('tron', createTronAdapter({
    request: provider.request.bind(provider),
    get tronWeb() { return provider.tronWeb ?? window.tronWeb },
  }))
}
```

內建 TRON 適配器要求主網。若舊版 Provider 不支援網路查詢，僅接受節點來源為 `https://api.trongrid.io`；其他舊版自訂 RPC 需使用能驗證網路的自訂適配器。授權與橋呼叫的 fee limit 分別為 100 TRX 與 150 TRX，這是上限，實際費用請以錢包確認頁為準。

CDN 錢包函式與 npm API 相同，包括 `GaleBridge.createEvmAdapter` 和 `GaleBridge.createTronAdapter`。0.1.0 版未內建 WalletConnect 或行動錢包深層連結。請在合作夥伴環境驗證預計支援的錢包版本及行動錢包瀏覽器。

## 設定參數

除 `container` 外，所有參數皆為選填。鏈 ID、金額與地址使用字串；代幣地址使用 Networks API 回傳的 `address`，不要填入資料庫內部 ID。

| 參數 | 說明 |
| --- | --- |
| `container` | 必填，CSS 選擇器或 HTMLElement；掛載前容器必須已存在 |
| `widgetUrl` | 預設 `https://galebridge.net/widget`，應指定受信任的託管網址 |
| `clientId` | 穩定的專案／Widget 識別碼；省略時共用預設復原分組 |
| `wallets` | `{ evm, tron }` 適配器，可透過 `setWallet` 後續設定 |
| `theme` | `light` 或 `dark`，預設為淺色 |
| `lang` | `en`（英文）或 `zh`（繁體中文），預設英文；Widget 也提供語言選單，文件語言另外選擇 |
| `accentColor` | 六位十六進位色碼，例如 `#245fef` |
| `fromChainId`、`toChainId` | 來源鏈與目標鏈的十進位 ID，例如 `"1"`、`"137"` |
| `fromTokenAddress`、`toTokenAddress` | 對應網路的代幣地址；原生幣使用 API 回傳的佔位地址 |
| `amount` | 預填來源金額，以人類可讀的代幣單位表示，例如 `"10.5"`；不要傳入空字串或浮點運算後的近似值 |
| `receiver` | 預填目標地址；可由使用者輸入。欄位空白且與錢包同生態時，連接後可自動填入目前錢包地址 |
| `lockDestination` | 預設為 false。設為 true 時需同時提供目標鏈與代幣；已提供的 `receiver` 也會鎖定 |
| `allowedChains` | 兩側可選的鏈 ID 清單，例如 `["1", "137"]` |
| `allowedTokens` | 兩側可選的 `{ chainId, address }` 清單 |
| `slippage` | 小數字串：`"0.05"` 表示 5%，亦為預設值；範圍大於 0 且不超過 0.5。設定後介面不可修改；不構成最低到帳保證 |
| `protocol` | `all`、`v1` 或 `v2`，預設 `all`；實際報價決定可用路由 |

TRON Base58 地址須保留大小寫。網路、資產及路由可能變動，請以即時 API 與報價為準。預填不等於鎖定：不可用的預填資產可能改選其他可用資產；鎖定目標不可用時會阻止提交。`allowedChains` 與 `allowedTokens` 僅限制選擇，不會啟用後端未支援的資產。

### 範例 鎖定目標資產

以下設定將目標鎖定為 Ethereum DAI，使用者仍可選擇來源資產並輸入收款地址。若提供真實的 `receiver`，該地址也會鎖定。上線前請透過 Networks API 確認資產仍可用。

```ts
const destinationConfig = {
  toChainId: '1',
  toTokenAddress: '0x6b175474e89094c44da98b954eedeac495271d0f',
  lockDestination: true,
}

// On creation: createWidget({ container: '#bridge', ...destinationConfig, wallets })
// Or update while no transaction is in progress:
widget.update(destinationConfig)
```

## 事件與生命週期

請立即註冊事件監聽，再等待 `widget.ready`。就緒表示宿主與 iframe 已完成握手，不表示資產已載入、錢包已連接或已有可執行報價。

| 事件 | 資料與意義 |
| --- | --- |
| `ready` | `{ version: 1 }`，`1` 為通訊協定版本 |
| `needConnect` | `{ ecosystem }`，正在要求連接對應錢包 |
| `connected` | `{ address, chainId, ecosystem }`，錢包連接成功 |
| `transactionSent` | `{ txHash, chainId }`，來源交易已提交，不代表目標結算完成 |
| `statusChanged` | `{ txHash, status, order? }`，訂單狀態變更，也可能回報 `source_failed` |
| `completed` | `{ txHash, order }`，已完成訂單與本次交易相符；`txHash` 仍為來源交易雜湊 |
| `error` | `{ code, message }`，初始化、設定或交易錯誤 |

```ts
const off = widget.on('statusChanged', ({ txHash, status }) => {
  console.log(txHash, status)
})
off() // Remove this listener

widget.update({ theme: 'dark', lang: 'en', accentColor: '#245fef' })
// Recreate the widget to change container or widgetUrl.
// Invalid configuration can throw; a locked update emits an error event.

widget.destroy() // Remove the iframe, listeners and SDK timers; safe to call again
```

Widget 語言選單僅改變顯示，不會清除表單、錢包連接或待處理交易。閒置時僅更新 SDK 的 `lang`、`theme` 或 `accentColor` 也會保留表單。更改轉帳設定會重設表單選擇、錢包連接狀態與報價；未明確設定的欄位（包括金額）可能恢復預設值。簽章與訂單追蹤期間，Widget 會以 `CONFIG_LOCKED` 拒絕 `update`。`destroy()` 不會取消鏈上交易；重新建立後，可從儲存的待處理記錄恢復追蹤。復原功能使用 localStorage，第三方儲存被封鎖時可能阻止簽章，並提示開啟獨立頁面。

`completed` 為介面通知，重新整理或掛載後可能再次觸發。用於入帳、交付等業務時，合作夥伴應在伺服器端獨立驗證訂單及實際結算，並依交易或訂單處理重複通知，不應僅憑瀏覽器事件入帳。

## CSP 與部署

若宿主使用 CSP，請擴充既有原則，允許：

- `frame-src` 包含 `https://galebridge.net`。
- CDN 載入方式的 `script-src` 包含 `https://galebridge.net`。內嵌範例腳本需使用核准的 nonce／hash，或移至外部腳本。

Widget 呼叫託管來源的 API；宿主不需為 iframe 請求額外提供 API 網址或憑證。請使用 SDK 嵌入，並保留其 sandbox 設定，以維持握手與獨立頁面連結正常運作。

## 常見問題

| 現象 | 處理方式 |
| --- | --- |
| `Widget container not found` | 檢查選擇器，並在容器建立後掛載 |
| `INIT_TIMEOUT` 或空白畫面 | 使用 HTTP(S)，檢查 SDK／Widget 連線，以及宿主的腳本與 iframe CSP |
| iframe 內無法連接錢包 | 提供對應生態適配器，確認 Provider 是使用者實際選擇的錢包 |
| EVM 無法切換網路 | 先在錢包新增網路，再重新連接並取得新報價 |
| `INVALID_CONFIG`／`CONFIG_LOCKED` | 檢查格式與鎖定目標的必要欄位；等待目前交易結束後再更新 |
| 暫時找不到訂單 | 來源索引可能尚未完成，請繼續追蹤，不要因索引延遲再次發送 |
| 錢包逾時或交易結果不確定 | 先檢查錢包歷史與鏈上記錄，再以既有雜湊恢復追蹤；不要自動重送 |
| 第三方儲存不可用 | 使用 Widget 的獨立頁面入口繼續操作或復原 |

## 功能範圍與驗收

0.1.0 版提供 npm／CDN 載入、EVM／TRON 適配器、主題與語言、資產篩選、目標鎖定、訂單追蹤及復原。目標金額為估算值，`slippage` 不提供可強制執行的最低到帳保證；顯示費用不含錢包／網路 Gas。此版本未提供合作夥伴分潤、認證歸因或強制網域白名單。

上線前請驗證窄螢幕、錢包連接與切換網路、報價及授權、來源交易提交、最終結算、重新整理復原與元件卸載。請先約定測試方案；真實授權與交易應由具權限的錢包持有人確認。

## 範例檔案

- `examples/widget/npm.ts`：供頁面容器與建置工具使用的 npm 用戶端入口。
- `examples/widget/cdn.html`：透過 HTTP(S) 提供的完整 CDN 頁面。
- `examples/widget/react.tsx`：React／Next.js 用戶端元件。

範例連接正式 Widget，錢包操作須由使用者確認。
