@gibs/bridge-sdk 1.13.1 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +295 -18
- package/abis/package.json +6 -0
- package/bridge-limits/package.json +6 -0
- package/chain-info/package.json +6 -0
- package/chain-names/package.json +6 -0
- package/chains/package.json +6 -0
- package/claims/package.json +6 -0
- package/config/package.json +6 -0
- package/consolidated-quote/package.json +6 -0
- package/destination-calldata/package.json +6 -0
- package/dist/chain-info.d.ts +1 -1
- package/dist/chain-info.js +11 -12
- package/dist/chains.d.ts +25 -0
- package/dist/chains.js +55 -20
- package/dist/cjs/abis.d.ts +1 -0
- package/dist/cjs/abis.js +19 -0
- package/dist/cjs/bridge-limits.d.ts +103 -0
- package/dist/cjs/bridge-limits.js +89 -0
- package/dist/cjs/chain-info.d.ts +169 -0
- package/dist/cjs/chain-info.js +452 -0
- package/dist/cjs/chain-names.d.ts +160 -0
- package/dist/cjs/chain-names.js +207 -0
- package/dist/cjs/chains.d.ts +90 -0
- package/dist/cjs/chains.js +357 -0
- package/dist/cjs/claims.d.ts +198 -0
- package/dist/cjs/claims.js +182 -0
- package/dist/cjs/config.d.ts +255 -0
- package/dist/cjs/config.js +569 -0
- package/dist/cjs/consolidated-quote.d.ts +648 -0
- package/dist/cjs/consolidated-quote.js +1048 -0
- package/dist/cjs/destination-calldata.d.ts +118 -0
- package/dist/cjs/destination-calldata.js +156 -0
- package/dist/cjs/ecosystems.d.ts +217 -0
- package/dist/cjs/ecosystems.js +298 -0
- package/dist/cjs/execution.d.ts +359 -0
- package/dist/cjs/execution.js +495 -0
- package/dist/cjs/fee-type.d.ts +10 -0
- package/dist/cjs/fee-type.js +10 -0
- package/dist/cjs/hyperliquid-execution.d.ts +152 -0
- package/dist/cjs/hyperliquid-execution.js +175 -0
- package/dist/cjs/image-links.d.ts +117 -0
- package/dist/cjs/image-links.js +153 -0
- package/dist/cjs/index.d.ts +24 -0
- package/dist/cjs/index.js +40 -0
- package/dist/cjs/lifi-execution-guard.d.ts +210 -0
- package/dist/cjs/lifi-execution-guard.js +409 -0
- package/dist/cjs/near-intents-conformance.d.ts +85 -0
- package/dist/cjs/near-intents-conformance.js +337 -0
- package/dist/cjs/near-intents-route.d.ts +47 -0
- package/dist/cjs/near-intents-route.js +303 -0
- package/dist/cjs/near-intents.d.ts +524 -0
- package/dist/cjs/near-intents.js +1071 -0
- package/dist/cjs/origin-deposit.d.ts +326 -0
- package/dist/cjs/origin-deposit.js +82 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/pre-quote.d.ts +198 -0
- package/dist/cjs/pre-quote.js +180 -0
- package/dist/cjs/prices.d.ts +117 -0
- package/dist/cjs/prices.js +224 -0
- package/dist/cjs/providers.d.ts +238 -0
- package/dist/cjs/providers.js +171 -0
- package/dist/cjs/quote-fees.d.ts +102 -0
- package/dist/cjs/quote-fees.js +201 -0
- package/dist/cjs/referral.d.ts +85 -0
- package/dist/cjs/referral.js +85 -0
- package/dist/cjs/relay-currencies.d.ts +47 -0
- package/dist/cjs/relay-currencies.js +95 -0
- package/dist/cjs/relay-depository.d.ts +2 -0
- package/dist/cjs/relay-depository.js +85 -0
- package/dist/cjs/relay-quote.d.ts +301 -0
- package/dist/cjs/relay-quote.js +996 -0
- package/dist/cjs/route-steps.d.ts +138 -0
- package/dist/cjs/route-steps.js +310 -0
- package/dist/cjs/routing/amounts.d.ts +80 -0
- package/dist/cjs/routing/amounts.js +126 -0
- package/dist/cjs/routing/candidate-route.d.ts +43 -0
- package/dist/cjs/routing/candidate-route.js +50 -0
- package/dist/cjs/routing/candidate.d.ts +237 -0
- package/dist/cjs/routing/candidate.js +100 -0
- package/dist/cjs/routing/carrier-provider-key.d.ts +31 -0
- package/dist/cjs/routing/carrier-provider-key.js +42 -0
- package/dist/cjs/routing/carriers/ask-answer.d.ts +77 -0
- package/dist/cjs/routing/carriers/ask-answer.js +2 -0
- package/dist/cjs/routing/carriers/omnibridge.d.ts +137 -0
- package/dist/cjs/routing/carriers/omnibridge.js +459 -0
- package/dist/cjs/routing/carriers/pulsex-paths.d.ts +80 -0
- package/dist/cjs/routing/carriers/pulsex-paths.js +97 -0
- package/dist/cjs/routing/carriers/pulsex-split.d.ts +175 -0
- package/dist/cjs/routing/carriers/pulsex-split.js +193 -0
- package/dist/cjs/routing/carriers/pulsex.d.ts +184 -0
- package/dist/cjs/routing/carriers/pulsex.js +615 -0
- package/dist/cjs/routing/carriers/wrap.d.ts +25 -0
- package/dist/cjs/routing/carriers/wrap.js +119 -0
- package/dist/cjs/routing/composed-route-quality.d.ts +125 -0
- package/dist/cjs/routing/composed-route-quality.js +144 -0
- package/dist/cjs/routing/entry-chain-anchor.d.ts +88 -0
- package/dist/cjs/routing/entry-chain-anchor.js +59 -0
- package/dist/cjs/routing/format.d.ts +31 -0
- package/dist/cjs/routing/format.js +64 -0
- package/dist/cjs/routing/index.d.ts +42 -0
- package/dist/cjs/routing/index.js +142 -0
- package/dist/cjs/routing/producers.d.ts +770 -0
- package/dist/cjs/routing/producers.js +1617 -0
- package/dist/cjs/routing/providers.d.ts +31 -0
- package/dist/cjs/routing/providers.js +48 -0
- package/dist/cjs/routing/route-data.d.ts +397 -0
- package/dist/cjs/routing/route-data.js +2 -0
- package/dist/cjs/routing/search/compose.d.ts +168 -0
- package/dist/cjs/routing/search/compose.js +104 -0
- package/dist/cjs/routing/search/graph.d.ts +668 -0
- package/dist/cjs/routing/search/graph.js +461 -0
- package/dist/cjs/routing/search/plan-route-data.d.ts +285 -0
- package/dist/cjs/routing/search/plan-route-data.js +871 -0
- package/dist/cjs/routing/search/score.d.ts +356 -0
- package/dist/cjs/routing/search/score.js +445 -0
- package/dist/cjs/routing/swap-transactions.d.ts +95 -0
- package/dist/cjs/routing/swap-transactions.js +133 -0
- package/dist/cjs/routing/usd-value.d.ts +17 -0
- package/dist/cjs/routing/usd-value.js +29 -0
- package/dist/cjs/settings.d.ts +785 -0
- package/dist/cjs/settings.js +1074 -0
- package/dist/cjs/swap-venues.d.ts +43 -0
- package/dist/cjs/swap-venues.js +29 -0
- package/dist/cjs/ton-connect.d.ts +98 -0
- package/dist/cjs/ton-connect.js +206 -0
- package/dist/cjs/ton-execution.d.ts +7 -0
- package/dist/cjs/ton-execution.js +23 -0
- package/dist/cjs/ton.d.ts +37 -0
- package/dist/cjs/ton.js +83 -0
- package/dist/cjs/tron-execution.d.ts +146 -0
- package/dist/cjs/tron-execution.js +248 -0
- package/dist/cjs/types.d.ts +51 -0
- package/dist/cjs/types.js +2 -0
- package/dist/claims.d.ts +198 -0
- package/dist/claims.js +140 -0
- package/dist/config.d.ts +42 -3
- package/dist/config.js +87 -21
- package/dist/consolidated-quote.d.ts +109 -15
- package/dist/consolidated-quote.js +310 -52
- package/dist/execution.d.ts +359 -0
- package/dist/execution.js +455 -0
- package/dist/hyperliquid-execution.js +2 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/lifi-execution-guard.d.ts +13 -2
- package/dist/lifi-execution-guard.js +4 -0
- package/dist/near-intents.js +2 -3
- package/dist/pre-quote.d.ts +6 -6
- package/dist/pre-quote.js +10 -14
- package/dist/prices.d.ts +10 -0
- package/dist/prices.js +52 -1
- package/dist/providers.d.ts +17 -0
- package/dist/providers.js +6 -0
- package/dist/referral.d.ts +12 -4
- package/dist/referral.js +12 -4
- package/dist/relay-depository.d.ts +2 -0
- package/dist/relay-depository.js +82 -0
- package/dist/relay-quote.js +2 -3
- package/dist/routing/candidate.d.ts +3 -3
- package/dist/routing/candidate.js +3 -3
- package/dist/routing/carrier-provider-key.d.ts +31 -0
- package/dist/routing/carrier-provider-key.js +37 -0
- package/dist/routing/carriers/ask-answer.d.ts +17 -0
- package/dist/routing/carriers/omnibridge.js +23 -21
- package/dist/routing/carriers/pulsex-paths.d.ts +80 -0
- package/dist/routing/carriers/pulsex-paths.js +92 -0
- package/dist/routing/carriers/pulsex-split.d.ts +175 -0
- package/dist/routing/carriers/pulsex-split.js +185 -0
- package/dist/routing/carriers/pulsex.d.ts +81 -46
- package/dist/routing/carriers/pulsex.js +267 -148
- package/dist/routing/composed-route-quality.d.ts +28 -6
- package/dist/routing/composed-route-quality.js +36 -5
- package/dist/routing/entry-chain-anchor.d.ts +1 -1
- package/dist/routing/entry-chain-anchor.js +1 -1
- package/dist/routing/index.d.ts +11 -3
- package/dist/routing/index.js +8 -2
- package/dist/routing/producers.js +1 -1
- package/dist/routing/providers.d.ts +5 -0
- package/dist/routing/providers.js +5 -0
- package/dist/routing/route-data.d.ts +47 -5
- package/dist/routing/search/graph.d.ts +15 -0
- package/dist/routing/search/plan-route-data.d.ts +42 -1
- package/dist/routing/search/plan-route-data.js +68 -9
- package/dist/routing/search/score.d.ts +3 -0
- package/dist/routing/search/score.js +11 -3
- package/dist/routing/swap-transactions.d.ts +95 -0
- package/dist/routing/swap-transactions.js +127 -0
- package/dist/settings.d.ts +94 -1
- package/dist/settings.js +95 -3
- package/dist/swap-venues.d.ts +43 -0
- package/dist/swap-venues.js +24 -0
- package/dist/ton-connect.d.ts +98 -0
- package/dist/ton-connect.js +200 -0
- package/dist/ton-execution.d.ts +4 -114
- package/dist/ton-execution.js +4 -244
- package/dist/ton.d.ts +37 -0
- package/dist/ton.js +65 -0
- package/docs/examples/example-a-ether-to-pulse.ts +300 -0
- package/docs/examples/example-b-token-pulsechain-to-ethereum.ts +270 -0
- package/docs/examples/shared.ts +261 -0
- package/docs/examples/tsconfig.json +18 -0
- package/docs/integration-guide.md +617 -0
- package/ecosystems/package.json +6 -0
- package/execution/package.json +6 -0
- package/fee-type/package.json +6 -0
- package/hyperliquid-execution/package.json +6 -0
- package/image-links/package.json +6 -0
- package/lifi-execution-guard/package.json +6 -0
- package/llms.txt +75 -10
- package/near-intents/package.json +6 -0
- package/near-intents-route/package.json +6 -0
- package/origin-deposit/package.json +6 -0
- package/package.json +817 -10
- package/pre-quote/package.json +6 -0
- package/prices/package.json +6 -0
- package/providers/package.json +6 -0
- package/quote-fees/package.json +6 -0
- package/referral/package.json +6 -0
- package/relay-currencies/package.json +6 -0
- package/relay-quote/package.json +6 -0
- package/route-steps/package.json +6 -0
- package/routing/package.json +6 -0
- package/settings/package.json +6 -0
- package/ton/package.json +6 -0
- package/ton-connect/package.json +6 -0
- package/ton-execution/package.json +6 -0
- package/tron-execution/package.json +6 -0
- package/types/package.json +6 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
# Integrate Gibs into a wallet or app
|
|
2
|
+
|
|
3
|
+
This guide shows you how to add Gibs Finance bridge routes to your wallet, app or script. You search for routes, plan the transactions, send them with your own signer, track the transfer, and claim it when the bridge needs a claim.
|
|
4
|
+
|
|
5
|
+
The guide covers the `@gibs/*` packages at version **1.16.0**. Check npm for the current version before you install. Never install 1.12.0 or older: those versions do not load.
|
|
6
|
+
|
|
7
|
+
The packages never sign and never hold funds. Your wallet signs every transaction. A mobile wallet such as Internet Money uses the packages this way, and so can any other app.
|
|
8
|
+
|
|
9
|
+
Terms used once and then kept:
|
|
10
|
+
|
|
11
|
+
- **API** means application programming interface. **HTTP** means Hypertext Transfer Protocol. **JSON** means JavaScript Object Notation.
|
|
12
|
+
- An **RPC endpoint** is a node's remote procedure call address, for example `https://your-node.example`.
|
|
13
|
+
- A **section** is one wallet signature in a route. A route with a crossing and then a swap has two sections.
|
|
14
|
+
- A **crossing** is one omnibridge transfer between PulseChain and Ethereum, or between PulseChain and BNB Smart Chain.
|
|
15
|
+
- **Delivery** means that a paid service submits the last transaction of a crossing on the destination chain, so the user does not have to come back and claim.
|
|
16
|
+
|
|
17
|
+
## Contents
|
|
18
|
+
|
|
19
|
+
1. [What the packages do](#1-what-the-packages-do)
|
|
20
|
+
2. [Install and set up](#2-install-and-set-up)
|
|
21
|
+
3. [Search for routes](#3-search-for-routes)
|
|
22
|
+
4. [Plan the transactions](#4-plan-the-transactions)
|
|
23
|
+
5. [Track a transfer](#5-track-a-transfer)
|
|
24
|
+
6. [Claim a transfer](#6-claim-a-transfer)
|
|
25
|
+
7. [Defaults you replace](#7-defaults-you-replace)
|
|
26
|
+
8. [The public Gibs services](#8-the-public-gibs-services)
|
|
27
|
+
9. [Example A: ETH on Ethereum to PLS on PulseChain](#9-example-a-eth-on-ethereum-to-pls-on-pulsechain)
|
|
28
|
+
10. [Example B: an ERC-20 token from PulseChain to Ethereum, delivery off](#10-example-b-an-erc-20-token-from-pulsechain-to-ethereum-delivery-off)
|
|
29
|
+
11. [Fees and attribution](#11-fees-and-attribution)
|
|
30
|
+
12. [Known limits](#12-known-limits)
|
|
31
|
+
|
|
32
|
+
The two examples are complete TypeScript files. They typecheck against the published packages:
|
|
33
|
+
|
|
34
|
+
| File | What it holds |
|
|
35
|
+
|---|---|
|
|
36
|
+
| [`examples/shared.ts`](examples/shared.ts) | The signer interface, a web3.js v4 adapter (in a comment), viem clients, the allowance and balance readers, the sender, the status poller |
|
|
37
|
+
| [`examples/example-a-ether-to-pulse.ts`](examples/example-a-ether-to-pulse.ts) | Example A |
|
|
38
|
+
| [`examples/example-b-token-pulsechain-to-ethereum.ts`](examples/example-b-token-pulsechain-to-ethereum.ts) | Example B |
|
|
39
|
+
| [`examples/tsconfig.json`](examples/tsconfig.json) | Strict compiler settings with `moduleResolution: "Bundler"` |
|
|
40
|
+
|
|
41
|
+
The files ship inside `@gibs/bridge-sdk`, in `node_modules/@gibs/bridge-sdk/docs/examples/`. To check them, install the packages and `typescript`, copy the folder into your project, and run `npx tsc -p examples/tsconfig.json`. The examples never ran against a live chain.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 1. What the packages do
|
|
46
|
+
|
|
47
|
+
| Step | Function | Package |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| Find and rank routes | `searchBridgeRoutes` | `@gibs/quotes/search` |
|
|
50
|
+
| Get the live quote an aggregator section signs | `fetchLiveSectionQuote`, `fetchLiveQuote` | `@gibs/quotes/search` |
|
|
51
|
+
| Turn a route into transactions | `planCandidateExecution` | `@gibs/bridge-sdk/execution` |
|
|
52
|
+
| Read a fresh PulseX swap quote | `readPulseXSectionQuote` | `@gibs/bridge-sdk/execution` |
|
|
53
|
+
| Read how a token maps across the bridge | `tokenBridgeInfo` | `@gibs/bridge-sdk/chain-info` |
|
|
54
|
+
| Track a transfer | `fetchTransferStatus` | `@gibs/bridge-indexer` |
|
|
55
|
+
| Read the record a claim needs | `fetchClaimRecords` | `@gibs/bridge-indexer` |
|
|
56
|
+
| Build a claim | `buildClaimTransaction` | `@gibs/bridge-sdk/claims` |
|
|
57
|
+
|
|
58
|
+
An aggregator is a third-party bridge service. The packages ask three of them: LI.FI, Relay and NEAR Intents. The omnibridge is the PulseChain bridge to Ethereum and to BNB Smart Chain. PulseX is the exchange on PulseChain.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 2. Install and set up
|
|
63
|
+
|
|
64
|
+
### Packages
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
npm install @gibs/quotes @gibs/bridge-sdk @gibs/bridge-indexer viem
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`@gibs/quotes` pulls in `@gibs/bridge-sdk`, `@gibs/common` and `@gibs/abis`. `@gibs/bridge-indexer` pulls in `@gibs/bridge-client` and `graphql-request`. Every package needs Node 20 or later when it runs on Node.
|
|
71
|
+
|
|
72
|
+
### viem
|
|
73
|
+
|
|
74
|
+
The peer range is `>=2.35.0 <3`. The packages are tested against exactly 2.35.0, the floor. Keep one copy of viem in your bundle.
|
|
75
|
+
|
|
76
|
+
### Module formats
|
|
77
|
+
|
|
78
|
+
Every package ships two builds: ECMAScript modules in `dist/` and CommonJS in `dist/cjs/`. The `exports` map has `react-native`, `import` and `require` conditions. Any bundler that reads `exports` picks the right build.
|
|
79
|
+
|
|
80
|
+
### React Native 0.77
|
|
81
|
+
|
|
82
|
+
The packages work in React Native 0.77 and later with no Metro setting. They are tested in a React Native 0.77.3 app with viem 2.35.0 and the template's own `metro.config.js`, `babel.config.js` and `jest.config.js`. Every public entry point is bundled for iOS and Android, compiled with the Hermes compiler, and loaded under jest.
|
|
83
|
+
|
|
84
|
+
Metro in React Native 0.77 ignores `exports`. So every package also ships one folder per subpath, such as `@gibs/bridge-sdk/execution/package.json`. Its `main` and `react-native` fields point at the CommonJS build. You import the subpath as usual.
|
|
85
|
+
|
|
86
|
+
You do **not** need:
|
|
87
|
+
|
|
88
|
+
- a `resolveRequest` hook in `metro.config.js`;
|
|
89
|
+
- `transformIgnorePatterns` in your jest configuration. The React Native jest preset loads the CommonJS build untransformed;
|
|
90
|
+
- `unstable_enablePackageExports`. Do not turn it on for these packages. They do not need it, and it can break other libraries in your app, such as web3.js.
|
|
91
|
+
|
|
92
|
+
### React Native's own URL is enough
|
|
93
|
+
|
|
94
|
+
You need no URL polyfill. React Native 0.77's own `URL` supports only `href` and `toString()`. Its other getters throw "not implemented", and its `searchParams` is always empty. Its `URLSearchParams` can only append and serialize. No package reads either class. The packages build and read request addresses with `@gibs/common/url`, which needs only `encodeURIComponent` and `decodeURIComponent`.
|
|
95
|
+
|
|
96
|
+
The tests load every public entry point under React Native's own `URL` and `URLSearchParams`. If your app installs a URL polyfill for other code, the packages still work.
|
|
97
|
+
|
|
98
|
+
### TON
|
|
99
|
+
|
|
100
|
+
TON is The Open Network. Most wallets never load its code:
|
|
101
|
+
|
|
102
|
+
- `@gibs/bridge-sdk/ton-connect` needs no TON library and no `Buffer`.
|
|
103
|
+
- `@gibs/bridge-sdk/ton` needs `@ton/core`, a global `Buffer` and `expo-crypto`. Install `buffer` and set `globalThis.Buffer` before the import. Without `expo-crypto`, Metro cannot bundle it.
|
|
104
|
+
- npm still installs `@ton/core` and `@ton/crypto`, because they are regular dependencies of `@gibs/bridge-sdk`. Metro bundles them only when you import `@gibs/bridge-sdk/ton`.
|
|
105
|
+
|
|
106
|
+
A wallet that bridges only between Ethereum-style chains never pays for TON.
|
|
107
|
+
|
|
108
|
+
### Import narrow subpaths
|
|
109
|
+
|
|
110
|
+
Metro does not drop unused exports. Import `@gibs/bridge-sdk/execution`, not `@gibs/bridge-sdk`. See [the bundle-size note](#bundle-size-with-exports-turned-on).
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 3. Search for routes
|
|
115
|
+
|
|
116
|
+
`searchBridgeRoutes` in `@gibs/quotes/search` does the whole search in one call. It loads the provider lists, the omnibridge pairs, prices, gas prices and balances. Then it runs the route engine and decides which routes it may serve. `loadBridgeablePairs` in the same subpath lists the pairs the omnibridge carries.
|
|
117
|
+
|
|
118
|
+
It takes:
|
|
119
|
+
|
|
120
|
+
- `origin` and `destination`: the chain, the token, the amount, the sending account and the recipient;
|
|
121
|
+
- `clientForChain`: your viem client for each chain, or `null` for a chain you have no endpoint for. The search skips what it cannot read;
|
|
122
|
+
- `fetch`, an `AbortSignal`, and `timeouts`;
|
|
123
|
+
- your keys and your attribution. See [Fees and attribution](#11-fees-and-attribution).
|
|
124
|
+
|
|
125
|
+
A cancelled search rejects with `RouteSearchCancelledError`.
|
|
126
|
+
|
|
127
|
+
**PulseChain never reaches an aggregator.** PulseChain is chain 369, and its testnet is chain 943. No request that names either chain goes to LI.FI, Relay or NEAR Intents. Three layers refuse it, the last one at the wire.
|
|
128
|
+
|
|
129
|
+
**Your LI.FI key.** Pass `lifiApiKey`. It travels only as the `x-lifi-api-key` header, on every LI.FI request the search makes: the chain list, the token lists, the prices and every quote. It never enters a URL, a body or a log, and no other provider receives it. The search refuses any request that would carry it in a URL or a body.
|
|
130
|
+
|
|
131
|
+
LI.FI advises against shipping a key in client code. If you keep the key on a server, point `lifiApiOrigin` at your own proxy, and let the proxy add the header. You can combine the two. Without either, LI.FI answers at its lower keyless rate limit.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 4. Plan the transactions
|
|
136
|
+
|
|
137
|
+
`planCandidateExecution` in `@gibs/bridge-sdk/execution` turns a served route into an ordered list of `PlannedTx`, one entry per transaction:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
type PlannedTx = {
|
|
141
|
+
readonly chainId: number;
|
|
142
|
+
readonly kind: 'approve' | 'bridge' | 'swap' | 'wrap' | 'unwrap' | 'claim';
|
|
143
|
+
readonly to: Hex;
|
|
144
|
+
readonly data: Hex;
|
|
145
|
+
readonly value: bigint;
|
|
146
|
+
readonly gasLimit: bigint | null; // null: the wallet estimates
|
|
147
|
+
readonly waitForReceipt: boolean; // true on every transaction except the last
|
|
148
|
+
};
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
What the plan guarantees:
|
|
152
|
+
|
|
153
|
+
- **Approvals are exact.** The plan approves only the amount a transaction spends. It skips an approval that the current allowance covers. It resets a short, non-zero allowance to zero first.
|
|
154
|
+
- **Spenders are checked.** The plan approves only these spenders: the omnibridge mediator and the PulseX router from the package configuration, the LI.FI Diamond (with the LI.FI execution guard), and the Relay depository (`relayDepositoryByChainId`). The plan refuses a quote that names any other spender or deposit target.
|
|
155
|
+
- **Only live quotes are signed.** An aggregator section needs the live quote asked for your wallet, in `sections[i].aggregator`. The route's own display quote is never signed. Without a live quote, the plan refuses with `live-quote-required`.
|
|
156
|
+
- **Deposits match the request amount.** The LI.FI guard checks the decoded swap amount. Relay must call `depositNative` or `depositErc20` with the requested token, value and amount. NEAR Intents must pay exactly the requested amount to the quote's own deposit address. The plan refuses a quote that disagrees with the request, with `amount-mismatch` or `recipient-mismatch`.
|
|
157
|
+
- **Delivery is your explicit choice.** Each crossing takes `delivery: { shouldDeliver: false }` or `{ shouldDeliver: true, fee }`. There is no default.
|
|
158
|
+
|
|
159
|
+
### Plan one section at a time
|
|
160
|
+
|
|
161
|
+
A route with two sections has a crossing, and then something on the far chain. The second section must spend what actually arrived, not what the search predicted. So plan each section when it is about to run:
|
|
162
|
+
|
|
163
|
+
- Pass `firstSectionIndex`. Then `sections[0]` plans that section. Refusal indices and delivery indices stay absolute.
|
|
164
|
+
- Plan section 1 after section 0's crossing arrived, with the amount that arrived. Every check above still applies to the later section.
|
|
165
|
+
|
|
166
|
+
### A fresh swap floor
|
|
167
|
+
|
|
168
|
+
A PulseX section takes `swap.quotedAmountOut`, a fresh quote of its output. The slippage floor then comes from this quote, not from the search-time figure. `readPulseXSectionQuote({ hop, amountIn, client })` reads it along the exact path the swap signs, on the hop's own router. Pass a fresh `swap.deadline` too.
|
|
169
|
+
|
|
170
|
+
### The live quote
|
|
171
|
+
|
|
172
|
+
`fetchLiveSectionQuote` in `@gibs/quotes/search` builds the request from the section itself, and asks the section's own provider. It returns `{ quote, request }`, which is exactly `sections[i].aggregator`.
|
|
173
|
+
|
|
174
|
+
- It handles three shapes: a direct last section, a section that another section follows (it lands in your own wallet), and a section that fuses with an omnibridge entry.
|
|
175
|
+
- For the fused shape, it reads the token mapping through your `clientForChain`. It shrinks the entry amount when the cost of the entry call would make the spend exceed `amountIn`. It asks at most three times. It never returns a quote that spends more than `amountIn`.
|
|
176
|
+
- It takes your `fetch`, `keys` (`lifiApiKey`, `relayApiKey`, `nearIntentsApiKey`, each sent only as its own header), `attribution`, `lifiApiOrigin` and `signal`.
|
|
177
|
+
|
|
178
|
+
`fetchLiveQuote` is the lower-level call: one provider, and one `ConsolidatedQuoteRequest` you built yourself. Both calls send through the same wire guard as the search. No request that names PulseChain, and no request that carries a key outside a header, ever leaves.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 5. Track a transfer
|
|
183
|
+
|
|
184
|
+
`fetchTransferStatus` in `@gibs/bridge-indexer` takes `{ chainId, txHash }` for the transaction that started a transfer. It returns:
|
|
185
|
+
|
|
186
|
+
- `state`: one of `sent`, `confirmed-not-final`, `validators-confirming`, `ready-delivery-service`, `ready-your-turn`, `delivered`, `entry-reached`, `stranded`, `failed` and `unknown`. `transferStateNames` holds the words to show.
|
|
187
|
+
- `claimable`: true in both ready states.
|
|
188
|
+
- `destinationTxHash`: the transaction that settled the destination side.
|
|
189
|
+
- `aggregatorStatus`: the aggregator's own answer, for a route that starts with an aggregator.
|
|
190
|
+
|
|
191
|
+
Every option is optional: `fetch`, `endpoint`, `signal`, `aggregator`, `aggregatorFinalLeg` and `originClient`. The function works around every known indexer defect: it lowercases hex filters, and it avoids the one-way joins and the relations that are always null.
|
|
192
|
+
|
|
193
|
+
What you pass:
|
|
194
|
+
|
|
195
|
+
- `originClient`, your viem client on the origin chain. Without it, a transaction that the indexer has not recorded yet is `unknown`, not `sent`.
|
|
196
|
+
- `aggregator`, for a route that starts with an aggregator. Relay's status endpoint answers 400 without your Relay key.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 6. Claim a transfer
|
|
201
|
+
|
|
202
|
+
A crossing out of PulseChain with delivery off ends in the `ready-your-turn` state. The user then claims the tokens on the destination chain, and pays the gas there.
|
|
203
|
+
|
|
204
|
+
### Read the record
|
|
205
|
+
|
|
206
|
+
`fetchClaimRecords` in `@gibs/bridge-indexer` returns the bridge requests a claim is built from, oldest first, in the shape `buildClaimTransaction` takes:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
const records = await fetchClaimRecords({ source: { chainId: 369, txHash } });
|
|
210
|
+
// or: fetchClaimRecords({ messageHash })
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
It asks for exactly the claim fields plus `messageHash`. It lowercases every hash, reads `delivered` as a flag, and never asks for a relation that the indexer leaves null. It makes no RPC read and writes no log. It takes `fetch`, `endpoint` and `signal`, like `fetchTransferStatus`. An empty list means that the indexer has not recorded the request yet.
|
|
214
|
+
|
|
215
|
+
It is a separate call, not a field of `fetchTransferStatus`, for three reasons:
|
|
216
|
+
|
|
217
|
+
- You poll the status often, and signatures and `encodedData` are large.
|
|
218
|
+
- One transaction can start several requests. The status reports the slowest, but a claim is per request.
|
|
219
|
+
- A claim can start from a message hash, as in a history list, with no status call at all.
|
|
220
|
+
|
|
221
|
+
### Build the claim
|
|
222
|
+
|
|
223
|
+
`buildClaimTransaction` in `@gibs/bridge-sdk/claims` takes the record and a `via` choice: `{ kind: 'direct' }` or `{ kind: 'router', destinationRouter, validator }`. It returns `{ kind: 'claim', transaction }`, where the transaction is a `PlannedTx` of kind `claim` on the destination chain. Or it returns a typed refusal:
|
|
224
|
+
|
|
225
|
+
| Refusal | Meaning | What your app does |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| `wrong-type` | The request is not a signature request. The validators execute it themselves. | Nothing to claim. |
|
|
228
|
+
| `already-delivered` | The release already ran. | Show "delivered". |
|
|
229
|
+
| `not-signed-yet` | The validators have not finished. | Wait, then ask again. |
|
|
230
|
+
| `signatures-incomplete` | The indexer stored fewer signatures than the bridge needs. Carries `stored` and `needed`. | Stop. Waiting does not fix it. Report it to Gibs Finance. |
|
|
231
|
+
| `malformed-request` | A field has the wrong shape. Carries `field`. | Stop. Report it. |
|
|
232
|
+
|
|
233
|
+
See [the submitSignature case](#the-submitsignature-case) for the cause of `signatures-incomplete`.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 7. Defaults you replace
|
|
238
|
+
|
|
239
|
+
The packages send nothing on your behalf, and they use no private service unless you ask:
|
|
240
|
+
|
|
241
|
+
- **RPC endpoints.** `chainsMetadata` carries only public endpoints. A shared demo key, `vk_demo`, lives only in `demoRpcUrls` and `demoRpcUrlsFor` (`@gibs/bridge-sdk/chains`). Every reader takes your own clients. Use your own nodes in production.
|
|
242
|
+
- **No default LI.FI integrator.** No `integrator`, `referrer`, `referral` or fee is sent unless you pass one.
|
|
243
|
+
- **No fee or referral unless you ask.** `@gibs/bridge-sdk/referral` holds Gibs Finance's own values. No builder reads them. Do not pass them: they pay Gibs Finance.
|
|
244
|
+
- **NEAR Intents `User-Agent`.** The request builders (`nearIntentsRequestHeaders`) name Gibs Finance by default. `searchBridgeRoutes` sends none unless you pass `nearIntentsUserAgent`. Name your own app.
|
|
245
|
+
- **Clients built for you.** `fetchBridgeTransactions` and `@gibs/bridge-client` still build their own clients on the demo endpoints. Do not use them in production. The examples do not.
|
|
246
|
+
|
|
247
|
+
The `@gibs/bridge-sdk` README has a section, "Which fees apply, and how you control each one".
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## 8. The public Gibs services
|
|
252
|
+
|
|
253
|
+
### The quote service
|
|
254
|
+
|
|
255
|
+
`https://quotes.gibs.finance` answers display quotes for anyone, including a native app that sends no `Origin` header:
|
|
256
|
+
|
|
257
|
+
- `POST https://quotes.gibs.finance/v1/display-quotes` with the body `{ "asks": [ ... ] }`, 1 to 24 asks.
|
|
258
|
+
- It shares one limit per client (60 a minute, bursts of 20) and one provider budget with every other caller.
|
|
259
|
+
|
|
260
|
+
A display quote shows what to expect. It is never the transaction to sign. The forwarding routes of the service carry Gibs Finance's provider keys and return calldata that you could sign. They accept only Gibs Finance's own sites, and answer 403 to your app.
|
|
261
|
+
|
|
262
|
+
So execute routes through `searchBridgeRoutes` and `planCandidateExecution`, with your own LI.FI and Relay keys or with none. Use the quote service for display only, and do not depend on it for signing. To run your own quote service, use `@gibs/quotes`.
|
|
263
|
+
|
|
264
|
+
### The indexer
|
|
265
|
+
|
|
266
|
+
`@gibs/bridge-indexer` reads `https://next-indexer.gibs.finance` by default. The Gibs Finance interface reads the same indexer, so you share its load and its upgrades. The production indexer at `https://indexer.gibs.finance` can lag the schema, and a query there can fail with an unknown field.
|
|
267
|
+
|
|
268
|
+
- Set the endpoint once at startup with `setIndexerEndpoint(...)`, or pass `endpoint` to each `fetchTransferStatus` and `fetchClaimRecords` call. Both default to `indexerEndpoint()`, so one `setIndexerEndpoint` covers both.
|
|
269
|
+
- The indexer has known data defects until a re-index runs. `fetchTransferStatus` and `fetchClaimRecords` work around them. If you query the indexer yourself, follow the rules in the `@gibs/bridge-indexer` README: lowercase every hex filter, read `delivered` and not `delivery`, and read `Completion` and `Delivery` by `messageHash`.
|
|
270
|
+
|
|
271
|
+
### The signed release list
|
|
272
|
+
|
|
273
|
+
`https://ipfs.gibs.finance/version.json` lists every interface release, with its content id and channel. `https://ipfs.gibs.finance/version.json.sig` is a detached Ed25519 signature over the exact bytes of that file. The public keys are in [public-keys.json](https://github.com/gibsfinance/interface-releases/blob/HEAD/public-keys.json). To link your users to the Gibs Finance interface, read the latest release from this list, and check the signature over the raw bytes first.
|
|
274
|
+
|
|
275
|
+
### Token and chain images
|
|
276
|
+
|
|
277
|
+
`https://gib.show` serves token and chain logos and token lists. It needs no key. `@gibs/bridge-sdk/image-links` builds its addresses.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## 9. Example A: ETH on Ethereum to PLS on PulseChain
|
|
282
|
+
|
|
283
|
+
The full file is [`examples/example-a-ether-to-pulse.ts`](examples/example-a-ether-to-pulse.ts). The helpers are in [`examples/shared.ts`](examples/shared.ts). This section walks through the steps.
|
|
284
|
+
|
|
285
|
+
### The signer
|
|
286
|
+
|
|
287
|
+
The packages do not depend on web3.js or on any wallet library. The examples expect this interface:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
export type WalletTransactionRequest = {
|
|
291
|
+
readonly chainId: number;
|
|
292
|
+
readonly from: Hex;
|
|
293
|
+
readonly to: Hex;
|
|
294
|
+
readonly data: Hex;
|
|
295
|
+
readonly value: bigint;
|
|
296
|
+
readonly gas?: bigint; // set only when the plan names a gas limit
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export type WalletSigner = {
|
|
300
|
+
readonly address: Hex;
|
|
301
|
+
/** Broadcasts on `request.chainId` and resolves with the hash, before mining. */
|
|
302
|
+
readonly sendTransaction: (request: WalletTransactionRequest) => Promise<Hex>;
|
|
303
|
+
};
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Here is a web3.js v4 adapter, with one `Web3` instance per chain. In web3.js v4, `web3.eth.sendTransaction` returns a PromiEvent. Awaiting it gives the mined receipt. The `transactionHash` event fires first, and this signer resolves with it:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
const signer: WalletSigner = {
|
|
310
|
+
address: account,
|
|
311
|
+
sendTransaction: (request) =>
|
|
312
|
+
new Promise((resolve, reject) => {
|
|
313
|
+
const web3 = web3ByChainId.get(request.chainId);
|
|
314
|
+
if (web3 === undefined) return reject(new Error(`no web3 for chain ${request.chainId}`));
|
|
315
|
+
web3.eth
|
|
316
|
+
.sendTransaction({
|
|
317
|
+
from: request.from,
|
|
318
|
+
to: request.to,
|
|
319
|
+
data: request.data,
|
|
320
|
+
value: request.value,
|
|
321
|
+
...(request.gas === undefined ? {} : { gas: request.gas }),
|
|
322
|
+
})
|
|
323
|
+
.on('transactionHash', (hash) => resolve(hash as Hex))
|
|
324
|
+
.on('error', reject);
|
|
325
|
+
}),
|
|
326
|
+
};
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
This adapter is not compiled with the examples, because they do not install web3.js. The examples wait for receipts through the viem clients, and only where the plan says `waitForReceipt`.
|
|
330
|
+
|
|
331
|
+
### Step 1: one viem client per chain, from your own RPC endpoints
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
const clientForChain = createClientLookup(
|
|
335
|
+
new Map([
|
|
336
|
+
[1, 'https://your-ethereum-node.example'],
|
|
337
|
+
[369, 'https://your-pulsechain-node.example'],
|
|
338
|
+
]),
|
|
339
|
+
);
|
|
340
|
+
const requireClient = requireClientWith(clientForChain);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
`createClientLookup` calls viem's `createPublicClient` with `http(url)` for each chain. It answers `null` for a chain that you have no endpoint for.
|
|
344
|
+
|
|
345
|
+
### Step 2: search
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
const search = await searchBridgeRoutes({
|
|
349
|
+
origin: {
|
|
350
|
+
chainId: 1,
|
|
351
|
+
token: { address: zeroAddress, decimals: 18, symbol: 'ETH' },
|
|
352
|
+
amount: amountWei,
|
|
353
|
+
account: signer.address,
|
|
354
|
+
},
|
|
355
|
+
destination: {
|
|
356
|
+
chainId: 369,
|
|
357
|
+
token: { address: zeroAddress, decimals: 18, symbol: 'PLS' },
|
|
358
|
+
recipient: signer.address,
|
|
359
|
+
},
|
|
360
|
+
clientForChain,
|
|
361
|
+
fetch: (input, init) => globalThis.fetch(input, init),
|
|
362
|
+
signal,
|
|
363
|
+
timeouts: { requestMs: 15_000, searchMs: 60_000 },
|
|
364
|
+
nearIntentsUserAgent: 'your-wallet/1.0',
|
|
365
|
+
});
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
The native coin is the zero address on both sides.
|
|
369
|
+
|
|
370
|
+
### Step 3: pick a route
|
|
371
|
+
|
|
372
|
+
`search.routes` holds the routes the search may serve, one per provider, best first. The best is `search.routes[0]`. When the list is empty, `search.priceImpactRefusal` says whether price impact is the reason, and `search.refusals` lists every refusal.
|
|
373
|
+
|
|
374
|
+
A served route has `candidate` (what you plan), `plan` (the engine's hops and signature groups), `route` (display data) and `routeSignatureCount`. For ETH to PLS, the usual best route is a crossing into PulseChain and then a PulseX swap: two sections.
|
|
375
|
+
|
|
376
|
+
### Step 4: build ONE section's inputs, from what it spends now
|
|
377
|
+
|
|
378
|
+
The example plans and sends one section at a time. The first hop of a section names its carrier. The example builds one `SectionExecutionInputs` for it:
|
|
379
|
+
|
|
380
|
+
- **Omnibridge** (`'pulsechain'` or `'tokensex'`): read the token mapping with `tokenBridgeInfo`, then state the delivery choice. When the crossing enters PulseChain, the bridge executes it itself. So `{ shouldDeliver: false }` is the only accepted choice, and the plan refuses `{ shouldDeliver: true, ... }` with `delivery-not-offered`.
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
const assetLink = await tokenBridgeInfo({
|
|
384
|
+
bridgeKey: [carrier, toChain(from.chainId), toChain(to.chainId)],
|
|
385
|
+
assetIn: search.resolveToken(from),
|
|
386
|
+
isProd: true,
|
|
387
|
+
fromChainClient: requireClient(from.chainId),
|
|
388
|
+
toChainClient: requireClient(to.chainId),
|
|
389
|
+
});
|
|
390
|
+
const inputs = {
|
|
391
|
+
amountIn,
|
|
392
|
+
recipient,
|
|
393
|
+
omnibridge: { isProd: true, assetLink, delivery: { shouldDeliver: false }, unwrap: false },
|
|
394
|
+
};
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
- **PulseX**: read a fresh quote just before signing, for the amount that arrived. Then set a slippage floor below it, and a deadline that starts now.
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
const quotedAmountOut = await readPulseXSectionQuote({ hop: firstHop, amountIn, client: requireClient(369) });
|
|
401
|
+
const inputs = { amountIn, recipient, swap: { slippageBasisPoints: 100n, deadline: deadlineIn(20 * 60), quotedAmountOut } };
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
- **Wrap**: `{ amountIn, recipient }` only.
|
|
405
|
+
- **Any aggregator**: the live quote, asked now for this wallet, this recipient and this amount.
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
const live = await fetchLiveSectionQuote({
|
|
409
|
+
plan: served.plan,
|
|
410
|
+
sectionIndex,
|
|
411
|
+
resolveToken: search.resolveToken,
|
|
412
|
+
amountIn,
|
|
413
|
+
account: signer.address,
|
|
414
|
+
recipient,
|
|
415
|
+
clientForChain, // reads the token mapping of a section that fuses with an omnibridge entry
|
|
416
|
+
fetch: (input, init) => globalThis.fetch(input, init),
|
|
417
|
+
keys: { lifiApiKey, relayApiKey },
|
|
418
|
+
signal,
|
|
419
|
+
});
|
|
420
|
+
if (live.kind !== 'quoted') throw new Error(`no live quote (${live.kind})`);
|
|
421
|
+
const inputs = { amountIn, recipient, aggregator: { quote: live.quote, request: live.request } };
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
The first section spends `amountWei`. Each later section spends what actually arrived from the section before it. The example reads the next section's token balance on its chain before the section runs, and again after the crossing is delivered. The difference is what arrived.
|
|
425
|
+
|
|
426
|
+
### Step 5: plan that section alone
|
|
427
|
+
|
|
428
|
+
```ts
|
|
429
|
+
const plan = await planCandidateExecution({
|
|
430
|
+
candidate: served.candidate,
|
|
431
|
+
account: signer.address,
|
|
432
|
+
readAllowance: allowanceReaderWith(requireClient), // (chainId, token, owner, spender) => Promise<bigint>
|
|
433
|
+
resolveToken: search.resolveToken,
|
|
434
|
+
firstSectionIndex: sectionIndex,
|
|
435
|
+
sections: [inputs],
|
|
436
|
+
});
|
|
437
|
+
if (plan.kind === 'refused') {
|
|
438
|
+
// plan.sectionIndex (absolute) and plan.refusal.reason say which section and why.
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Show `plan.deliveries` and the fees before the user signs. For the usual route, the two sections plan to:
|
|
443
|
+
|
|
444
|
+
| Section | Order | Chain | Kind | Waits for receipt |
|
|
445
|
+
|---|---|---|---|---|
|
|
446
|
+
| 0 | 1 | Ethereum | `bridge` (`wrapAndRelayTokens`, value = the ether) | no (last of its plan) |
|
|
447
|
+
| 1 | 1 | PulseChain | `approve` (wrapped ether from Ethereum, to the PulseX router, exact amount) | yes |
|
|
448
|
+
| 1 | 2 | PulseChain | `swap` | no |
|
|
449
|
+
|
|
450
|
+
Native ether needs no approval.
|
|
451
|
+
|
|
452
|
+
### Step 6: send, then wait for arrival before the next section
|
|
453
|
+
|
|
454
|
+
`sendPlannedTransactions` (in `shared.ts`) sends each transaction of the section in order. It broadcasts through your signer, waits for the receipt where `waitForReceipt` is true, and throws on a revert. The example then waits for the last receipt of the section.
|
|
455
|
+
|
|
456
|
+
A receipt is not arrival. It only says that the crossing started. So before the example plans the next section, it polls `fetchTransferStatus` on the crossing transaction until the state is `delivered`. Then it measures what arrived.
|
|
457
|
+
|
|
458
|
+
### Step 7: poll until delivered
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
const status = await waitForTransferStatus({
|
|
462
|
+
source: { chainId: 1, txHash: crossingHash },
|
|
463
|
+
statusOptions: { signal, originClient: requireClient(1) },
|
|
464
|
+
isDone: (answer) => answer.state === 'delivered',
|
|
465
|
+
intervalMs: 15_000,
|
|
466
|
+
});
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
`waitForTransferStatus` throws `TransferEndedError` on `failed` or `stranded`. If the route started with an aggregator, the example adds `aggregator` to the status options: `{ provider: 'lifi' }`, `{ provider: 'relay', requestId: quote.quoteId, apiKey }` or `{ provider: 'near-intents', depositAddress: quote.quoteId }`. A route that ends with a swap on PulseChain is done when the receipt of that swap succeeds.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## 10. Example B: an ERC-20 token from PulseChain to Ethereum, delivery off
|
|
474
|
+
|
|
475
|
+
The full file is [`examples/example-b-token-pulsechain-to-ethereum.ts`](examples/example-b-token-pulsechain-to-ethereum.ts). The token is PulseX (PLSX), which is native to PulseChain.
|
|
476
|
+
|
|
477
|
+
### Step 1: map the token and search
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
const assetLink = await tokenBridgeInfo({
|
|
481
|
+
bridgeKey: [Providers.PULSECHAIN, Chains.PLS, Chains.ETH],
|
|
482
|
+
assetIn: plsx,
|
|
483
|
+
isProd: true,
|
|
484
|
+
fromChainClient: pulsechainClient,
|
|
485
|
+
toChainClient: ethereumClient,
|
|
486
|
+
});
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
`assetLink.assetOutAddress` is the token's address on Ethereum. A `null` value is a normal answer: the token has not crossed before, and the first transfer creates it. The example stops there, because the search needs a destination address.
|
|
490
|
+
|
|
491
|
+
Then the example calls `searchBridgeRoutes` with PulseChain as the origin and Ethereum as the destination. It picks the served route that has one section whose hop is the omnibridge (`plan.hops[0].edge.carrier === Providers.PULSECHAIN`).
|
|
492
|
+
|
|
493
|
+
### Step 2: plan with delivery off
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
const plan = await planCandidateExecution({
|
|
497
|
+
candidate: crossing.candidate,
|
|
498
|
+
account: signer.address,
|
|
499
|
+
readAllowance: allowanceReaderWith(requireClient),
|
|
500
|
+
resolveToken: search.resolveToken,
|
|
501
|
+
sections: [
|
|
502
|
+
{
|
|
503
|
+
amountIn: amount,
|
|
504
|
+
recipient: signer.address,
|
|
505
|
+
omnibridge: { isProd: true, assetLink, delivery: { shouldDeliver: false }, unwrap: false },
|
|
506
|
+
},
|
|
507
|
+
],
|
|
508
|
+
});
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
The plan is `approve` and then `bridge` (`relayTokens`), both on PulseChain. The approval is for the exact amount, to the PulseChain mediator. The plan skips it when the allowance covers the amount. `plan.deliveries` is `[{ legIndex: 0, status: 'not-requested' }]`.
|
|
512
|
+
|
|
513
|
+
With delivery off, nobody is paid to release the tokens on Ethereum. The user claims them there and pays the Ethereum gas. Leaving PulseChain over Ethereum also costs the bridge fee. Read it with `loadBridgeFees`.
|
|
514
|
+
|
|
515
|
+
### Step 3: poll until claimable
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
const ready = await waitForTransferStatus({
|
|
519
|
+
source: { chainId: 369, txHash: bridgeHash },
|
|
520
|
+
statusOptions: { signal, originClient: pulsechainClient },
|
|
521
|
+
isDone: (status) => status.claimable || status.state === 'delivered',
|
|
522
|
+
intervalMs: 30_000,
|
|
523
|
+
});
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
With delivery off, the ready state is `ready-your-turn`. If the state is already `delivered`, somebody else released the tokens, and there is nothing to claim.
|
|
527
|
+
|
|
528
|
+
### Step 4: build the claim, and handle every refusal
|
|
529
|
+
|
|
530
|
+
The example reads the record with `fetchClaimRecords`. The function lowercases the hash for you, and asks for exactly the fields a claim reads:
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
const records = await fetchClaimRecords({ source: { chainId: 369, txHash: bridgeHash }, signal });
|
|
534
|
+
const [request] = records; // a plain relayTokens starts exactly one request
|
|
535
|
+
const claim = buildClaimTransaction({ request, via: { kind: 'direct' } });
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
The example stops with an error unless the transaction started exactly one request.
|
|
539
|
+
|
|
540
|
+
`via: { kind: 'direct' }` sends the claim straight to the Ethereum bridge and pays no tip. That is correct with delivery off. The `router` choice is for a request that carries a delivery fee.
|
|
541
|
+
|
|
542
|
+
The example maps each refusal to an action in an exhaustive `switch`. A new refusal reason in a later version fails the typecheck:
|
|
543
|
+
|
|
544
|
+
- `already-delivered`: done. Poll until `delivered` to get `destinationTxHash`.
|
|
545
|
+
- `not-signed-yet`: wait 30 seconds and read the record again. This can happen when the status and the record are read a moment apart.
|
|
546
|
+
- `signatures-incomplete`, `wrong-type`, `malformed-request`: stop with a `ClaimRefusedError`. Do not retry.
|
|
547
|
+
|
|
548
|
+
### Step 5: send the claim on Ethereum
|
|
549
|
+
|
|
550
|
+
`claim.transaction.chainId` is 1. The example sends it through the same signer, waits for the receipt, and polls `fetchTransferStatus` until `delivered`. `destinationTxHash` is then the transaction of the claim.
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## 11. Fees and attribution
|
|
555
|
+
|
|
556
|
+
**Nothing is added for you.** No builder, search or plan adds a fee, a payout address or a Gibs Finance label unless you pass it. Every fee below is an on-chain setting you read, a charge of a provider that you show, or a fee that you chose.
|
|
557
|
+
|
|
558
|
+
| Fee | When it applies | How your app sees it | How your app controls it |
|
|
559
|
+
|---|---|---|---|
|
|
560
|
+
| Bridge fee | Every omnibridge crossing. It is an on-chain rate for each direction. At the time of writing: 0% entering PulseChain over Ethereum, 0.3% leaving it; 2.97% each way over BNB Smart Chain. | `candidate.omnibridgeFeeAmount` on the route. `loadBridgeFees` (`@gibs/bridge-sdk/chain-info`) for the rate, then `bridgeCost`. | It cannot be turned off. Show it. |
|
|
561
|
+
| Delivery fee | Only when a crossing out of PulseChain has `shouldDeliver: true`. | `plan.deliveries[i].maximumFee` and `feeToken`. `deliveryFee` in `@gibs/bridge-sdk/settings`. | Choose `{ shouldDeliver: false }` (the user claims and pays gas), or set the fee terms yourself in `QuotedDeliveryFee`. |
|
|
562
|
+
| Provider fees (gas, solver, protocol) | Every aggregator section. | The `fees` of the live quote (`QuoteFee[]`). A fee with `includedInFromAmount: false` is paid **in addition to** `fromAmount`. | Not yours to control. Show them. |
|
|
563
|
+
| NEAR Intents keyless surcharge | Every NEAR Intents quote without a partner key: 25 basis points. | The NEAR Intents parser reports it as a charge. | Get a partner key and pass `nearIntentsApiKey`. |
|
|
564
|
+
| PulseX swap | Every PulseX section. The pool fee and the price impact are inside the quoted output. | The quoted output of the hop, and `minAmountOut` in the swap calldata. | `swap.slippageBasisPoints` sets the floor. |
|
|
565
|
+
| Aggregator cushion | A route that enters the omnibridge through an aggregator asks for an exact output. The amount that the aggregator does not need stays in the wallet on the entry chain. | Compare the `fromAmount` of the live quote with what arrives. | By design. Tell the user that it is theirs. |
|
|
566
|
+
| Your LI.FI fee | Only when you pass `lifiFee` and your own `lifiIntegrator`. | The `fee` field of the LI.FI request. | Leave it out to charge nothing. A positive fee without your integrator throws. |
|
|
567
|
+
| Your Relay fee | Only when you pass `relayAppFee: { recipient, basisPoints }`. | The `appFees` field of the Relay request. | Leave it out to charge nothing. |
|
|
568
|
+
| Gas | Every transaction. | `search.gasCostFor` and `search.isGasless` on the search result. | Your wallet's gas settings. `gasLimit: null` means that you estimate. |
|
|
569
|
+
|
|
570
|
+
Bridge fees are a rate per 1e18. Aggregator referral fees are in basis points.
|
|
571
|
+
|
|
572
|
+
**Your attribution.** Pass it in one place for the search, and in the same fields of the `ConsolidatedQuoteRequest` for the live quote:
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
await searchBridgeRoutes({
|
|
576
|
+
// ...
|
|
577
|
+
attribution: {
|
|
578
|
+
lifiIntegrator: 'your-wallet',
|
|
579
|
+
lifiFee: 0.001, // optional: a tenth of a percent, paid to your LI.FI integrator account
|
|
580
|
+
relayReferrer: 'your-wallet', // Relay then requires your key
|
|
581
|
+
relayAppFee: { recipient: '0xYourFeeWallet', basisPoints: 10n }, // optional
|
|
582
|
+
},
|
|
583
|
+
relayApiKey: yourRelayKey,
|
|
584
|
+
nearIntentsApiKey: yourNearKey,
|
|
585
|
+
nearIntentsUserAgent: 'your-wallet/1.0',
|
|
586
|
+
});
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
Each attribution field is either left out (nothing is sent) or your own string. Do not pass anything from `@gibs/bridge-sdk/referral`. Those values pay Gibs Finance.
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## 12. Known limits
|
|
594
|
+
|
|
595
|
+
### The submitSignature case
|
|
596
|
+
|
|
597
|
+
The indexer stores a validator's signature only when the validator calls `submitSignature` directly on the home bridge. The indexer decodes the input of the transaction, not the event. A validator that signs through a multisig wallet, a proxy or a batching contract still emits `SignedForUserRequest`. The indexer counts that signature in `confirmedSignatures`, but it does not store it in `signatures`. `CollectedSignatures` still sets `finishedSigning` to true.
|
|
598
|
+
|
|
599
|
+
So the record says "signed" with too few signatures, and a claim built from it reverts. `buildClaimTransaction` catches this case and refuses with `signatures-incomplete`, with `stored` and `needed`. Waiting does not fix it, because the indexer never goes back for the missing signature. Tell the user that the transfer is safe but that your app cannot claim it yet, and report it to Gibs Finance. The indexer defect is not fixed in 1.16.0.
|
|
600
|
+
|
|
601
|
+
### Relay deposits must use `depositNative` or `depositErc20`
|
|
602
|
+
|
|
603
|
+
The plan accepts a Relay deposit only when it calls `depositNative` or `depositErc20` on the depository from `relayDepositoryByChainId`, with exactly the requested token, value and amount. The check is strict on purpose. Relay can change its deposit form, add a chain, or move a depository. Until a new package version updates the table, the plan then refuses those Relay routes with `deposit-shape` or `deposit-target-not-allowed`. The table was read from Relay's chains API on 2026-10-07. A refused section does not lose funds. Fall back to the next route in `search.routes`.
|
|
604
|
+
|
|
605
|
+
### Backward-solved routes
|
|
606
|
+
|
|
607
|
+
A route that enters the omnibridge through an aggregator is solved backwards. The omnibridge entry call needs an exact amount, so the provider is asked for an exact *output*, and it works out the input. The request then names no input of yours. So `aggregatorOriginCalls` cannot compare the deposit with a figure that the user chose. It can only check that the quote, the approval and the deposit agree with each other.
|
|
608
|
+
|
|
609
|
+
Before the user signs, show the `fromAmount` of the live quote, and compare it with what the user expects. Refuse it in your own code if it is outside your tolerance.
|
|
610
|
+
|
|
611
|
+
### Bundle size with exports turned on
|
|
612
|
+
|
|
613
|
+
React Native 0.77's Metro ignores `exports`, and the packages load through the subpath folders and the CommonJS build. With `exports` turned on (the default from React Native 0.79), a bundle of every wallet entry point grew from 1.75 MB to 2.69 MB in our measurements on version 1.15.0. The full cause is not traced. With `exports` on, Metro gives `import` statements the ECMAScript module build and `require` calls the CommonJS build. Code that is reached both ways can then be bundled twice. Measure your own bundle before you turn exports on.
|
|
614
|
+
|
|
615
|
+
### A token that never crossed
|
|
616
|
+
|
|
617
|
+
A token that was never bridged has no address on the far side yet. `tokenBridgeInfo` then returns `assetOutAddress: null`. This is normal, not an error. The first transfer creates the token on the far side.
|