Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@omni-bridge/solana",
"@omni-bridge/btc",
"@omni-bridge/starknet",
"@omni-bridge/aptos",
"@omni-bridge/sdk"
]
],
Expand Down
8 changes: 8 additions & 0 deletions .changeset/curly-eyes-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@omni-bridge/core": minor
"@omni-bridge/near": minor
"@omni-bridge/aptos": minor
"@omni-bridge/sdk": minor
---

Add Aptos chain support: new `@omni-bridge/aptos` package with `createAptosBuilder` (init_transfer, log_metadata, deploy_token, fin_transfer entry-function payloads), event helpers for MPC proof construction, `aptos:` OmniAddress prefix, `ChainKind.Aptos`, and NEAR storage borsh schema support
98 changes: 98 additions & 0 deletions .github/prompts/pr-review.prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
You are reviewing a TypeScript pull request for **bridge-sdk-js** — the Omni Bridge SDK: a bun monorepo of `@omni-bridge/*` packages that validate cross-chain transfers and build **unsigned** transactions for the [Omni Bridge](https://github.com/Near-One/omni-bridge) protocol across NEAR, EVM chains (Ethereum, Arbitrum, Base, BNB, Polygon, Abstract), Solana, Fogo, Starknet, Aptos, Bitcoin, and Zcash. Consumers sign and broadcast what the SDK returns, so correctness here means: **every encoded byte matches the target chain's on-chain contract, amounts survive decimal normalization, and adding or changing a chain is wired through every layer so nothing is silently mis-routed.**

**IMPORTANT - CONTEXT AWARENESS:**
- Review any existing PR comments and discussions provided alongside this prompt before giving feedback
- Do not duplicate points already raised in existing discussions
- If a resolved thread addressed an issue, do not re-raise it
- You have read access to the checked-out repository — use `Read`, `Grep`, and `Glob` to verify how changes interact with surrounding code, look up referenced types/functions/tests, and consult [CLAUDE.md] for project structure, key concepts (transaction builder pattern, factory per chain, OmniAddress system, decimal normalization), and conventions
- Use `gh pr diff` for the full diff and `gh pr view` for PR metadata

PRIORITY CHECKS (report only if found):

1. Wire-format fidelity (the cardinal sins of this codebase)
- Borsh discriminants are POSITIONAL: the `ChainKind` enum in `packages/core/src/types.ts` and the `OmniAddressSchema` `b.enum` in `packages/near/src/storage.ts` must match the declaration order of Rust `omni_types` exactly (`b.nativeEnum`/`b.enum` serialize the position, not the value). New variants are append-only; reordering or inserting silently corrupts every `fin_transfer`/`deploy_token`/`bind_token` payload. `packages/near/tests/chain-kind-schema.test.ts` must lock any new discriminant
- Per-chain encodings must match the on-chain contract and bridge-sdk-rs byte-for-byte: EVM calldata (viem ABI, native-token `value` semantics), Solana instruction data + PDA seeds (seeds come from the program IDL — never modified), Starknet calldata (Cairo `ByteArray`, u256 low/high word order, `Option` variant tags), Aptos entry-function args (canonical zero-padded 64-hex addresses, u64/u128 as decimal strings, `vector<u8>` as `number[]` — a hex STRING gets UTF-8-encoded by the ts-sdk, `Option` None as `null`), NEAR borsh args. Verify argument ORDER, integer widths, and nonce handling
- 65-byte MPC signatures split correctly (`r||s` + `v`); per-chain signature encodings (Starknet felts vs Aptos rs/v) not interchanged
- Decimal normalization: amounts that don't survive source→destination decimal conversion are silent fund loss — `validateTransferAmount()` must guard every new path

2. Chain wiring exhaustiveness
- A new or changed chain must thread through ALL of: `packages/core/src/types.ts` (`ChainKind`, `OmniAddress` union, `ChainPrefix`), `utils/address.ts` (both prefix maps), `config.ts` (addresses; optional key + clear error if not yet deployed), `bridge.ts` (`chainKindToApiChain`, `getContractAddress`), `api.ts` (`ChainSchema` z.enum, `TransactionSchema` variant + its refine list), `utils/token.ts` (token prefix maps), `packages/near/src/storage.ts` (borsh schema arm + `parseOmniAddress` case), the sdk umbrella (`packages/sdk` index/package.json/tsconfig), root `tsconfig.json` references, `.changeset/config.json` fixed group, and the chain enumerations in README.md and `docs/` (incl. the type code blocks in `docs/reference/core.mdx`)
- The `Record<ChainKind, …>` maps and `never`-default switches are compile-enforced — but the zod enums, borsh schema order, token maps, and docs are NOT; check those by hand
- Chain-classification gating (`isEvmChain`, UTXO-only paths, SVM-only paths) must match the chain's real nature; a chain folded into the wrong arm is a silent bug

3. Backend API contract
- Zod schemas in `packages/core/src/api.ts` must match the bridge indexer endpoint's responses field-for-field (names, types, optionality, chain-name casing like `"HlEvm"`); a schema mismatch makes `BridgeAPI` throw on valid backend data

4. Untrusted input robustness
- Event parsers and RPC-response handling consume attacker-influenced data (recipient strings, token metadata, on-chain event fields): validate before trusting — strict decimal-integer parsing (bare `BigInt()` accepts hex/signed/empty), shape-check hashes/addresses before interpolating into URLs, fail fast on missing fields instead of returning `undefined`
- bigint edge cases (max u64/u128, zero, truncation), short-form vs zero-padded address handling, lowercase normalization

5. Security
- The SDK never holds private keys (transaction-builder pattern) — flag anything that accepts, logs, or embeds key material or secrets
- Hardcoded credentials, or RPC URLs with embedded tokens, in source or committed config

6. Public API & release hygiene
- Unsigned-transaction types must stay structurally compatible with their stated consumer libraries (viem/ethers v6, @solana/web3.js, starknet.js, @aptos-labs/ts-sdk, near shims) — a field type change can break consumers without failing this repo's build
- Changes to public API need a hand-written changeset (`.changeset/*.md`) with the right bump level; new packages must join the changesets fixed group and the sdk umbrella re-exports (watch for `export *` name collisions across packages)
- Per [CLAUDE.md] Workflow Rules: reference docs, package READMEs, and guide snippets must stay in sync with exports — flag doc code blocks that no longer compile or show APIs that don't exist

7. Logic & code quality
- Logic flaws, missing edge cases (empty inputs, `undefined` under `exactOptionalPropertyTypes`/`noUncheckedIndexedAccess`), unhandled promise rejections, missing error context on fetch failures
- CI gates on `bun run build`, `lint` (Biome), and `test` (Vitest) — flag code that would fail them: missing `.js` import extensions, Node.js APIs in `src/` (`Buffer`, `crypto`, `fs` — use `Uint8Array`/`TextEncoder`/`@noble/hashes`), formatting drift
- New chain packages should follow the established template (`packages/starknet`, `packages/aptos`): builder factory + encoding + events modules, exact-payload unit tests against Rust-SDK ground-truth vectors; flag gratuitous divergence

REVIEW STYLE:
- List only issues that should block the merge
- Use bullet points, be direct and specific
- Provide code suggestions for fixes when helpful
- Do NOT comment on style, formatting, naming, or documentation unless it causes a bug
- Do NOT restate what the diff already shows
- If no critical issues found: approve with a one-line summary
- Sign off with: ✅ (approved) or ⚠️ (issues found)

REQUIRED OUTPUT STRUCTURE:

The review body must follow this layout:

```
## Pull request overview

<2–4 sentence narrative summary of what this PR does and why.>

**Changes:**
- <bullet list of substantive changes — group related edits>

### Reviewed changes

<details>
<summary>Per-file summary</summary>

| File | Description |
| ---- | ----------- |
| path/to/file.ts | What changed in this file |
| ... | ... |

</details>

### Findings

**Blocking** (must fix before merge):
- `path/to/file.ts:LINE` — <description and concrete suggested fix>

**Non-blocking** (nits, follow-ups, suggestions):
- `path/to/file.ts:LINE` — <description>

<Omit a category if empty.>

<End with one of:>
✅ Approved
⚠️ Issues found
```

Anchor every finding with a `file:line` reference so reviewers can jump to the location.

Consult the repository's [CLAUDE.md] for project-specific conventions (AGENTS.md points to the same file).
Don't try to use `gh pr review` you don't have permissions for that and it will fail.
Please always use `gh pr comment` to post your review instead.

[CLAUDE.md]: ../../CLAUDE.md
91 changes: 91 additions & 0 deletions .github/scripts/fetch-pr-comments.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env bash
set -euo pipefail

# Fetches PR comments via GraphQL and formats them for Claude review.
#
# Required env vars: GH_TOKEN, PR_NUMBER, REPO_OWNER, REPO_NAME
# Outputs: /tmp/pr_comments_context.txt

QUERY='query($owner: String!, $repo: String!, $prNumber: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $prNumber) {
comments(first: 100) {
totalCount
nodes {
author { login }
body
createdAt
}
}
reviewThreads(first: 100) {
totalCount
nodes {
isResolved
isOutdated
path
line
comments(first: 50) {
nodes {
author { login }
body
createdAt
diffHunk
}
}
}
}
reviews(first: 50) {
totalCount
nodes {
author { login }
body
state
createdAt
}
}
}
}
}'

# Execute GraphQL query and check for errors
if ! COMMENTS_JSON=$(gh api graphql \
-f query="$QUERY" \
-f owner="$REPO_OWNER" \
-f repo="$REPO_NAME" \
-F prNumber="$PR_NUMBER"); then
echo "Warning: Failed to fetch PR comments. Proceeding without comment context."
echo "⚠️ Unable to fetch existing comments due to API error." > /tmp/pr_comments_context.txt
exit 0
fi

# Project the relevant fields to JSON context for Claude, truncating long diff
# hunks to keep the token budget bounded. The LLM reads JSON directly, so no
# prose formatting is needed.
# Write to file instead of env var to avoid E2BIG on large PRs
if [ -n "$COMMENTS_JSON" ]; then
echo "$COMMENTS_JSON" > /tmp/pr_comments_json.txt
if ! jq '
# Truncate a diff hunk to ~$max chars at line boundaries (never mid-line).
def truncate_hunk($max):
if (length <= $max) then .
else
(reduce (split("\n")[]) as $line ({acc: [], count: 0, done: false};
if .done then .
elif ((.count + ($line | length) + 1) > $max and (.acc | length) > 0)
then .done = true
else {acc: (.acc + [$line]), count: (.count + ($line | length) + 1), done: false}
end)
| .acc | join("\n")) + "\n... (truncated)"
end;
(.data.repository.pullRequest // {}) | {
comments: [(.comments.nodes // [])[] | {author: .author.login, body, createdAt}],
reviews: [(.reviews.nodes // [])[] | select((.body // "") != "") | {author: .author.login, state, body, createdAt}],
threads: [(.reviewThreads.nodes // [])[] | {path, line, isResolved, isOutdated,
comments: [(.comments.nodes // [])[] | {author: .author.login, body, createdAt, diffHunk: ((.diffHunk // "") | truncate_hunk(500))}]}]
}' /tmp/pr_comments_json.txt > /tmp/pr_comments_context.txt; then
echo "⚠️ Unable to parse comment data." > /tmp/pr_comments_context.txt
fi
else
echo "⚠️ No comments data to process." > /tmp/pr_comments_context.txt
exit 0
fi
73 changes: 73 additions & 0 deletions .github/workflows/claude-pr-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Claude Code Review

on:
pull_request:
types: [opened, ready_for_review] # When PR is ready for review (not draft)
issue_comment:
types: [created] # Listen for @claude mentions in PR comments

jobs:
claude-review:
# Run if: (PR opened/ready AND not draft) OR @claude review in PR comment
if: |
(github.event_name == 'pull_request' && !github.event.pull_request.draft) ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
(contains(github.event.comment.body, '@claude review') ||
contains(github.event.comment.body, '@claude code review')))

runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
with:
fetch-depth: 1
persist-credentials: false

- name: Fetch PR Comments Context
id: fetch-comments
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
run: .github/scripts/fetch-pr-comments.sh

- name: Build review prompt
id: build-prompt
run: |
DELIM="PROMPT_EOF_$(uuidgen)"
{
echo "REVIEW_PROMPT<<$DELIM"
echo '<pr_context>'
echo "REPO: ${{ github.repository }}"
echo "PR NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}"
echo 'LANGUAGE: TypeScript'
echo '</pr_context>'
echo ''
echo '<existing_discussions>'
cat /tmp/pr_comments_context.txt
echo '</existing_discussions>'
echo ''
echo '<review_instructions>'
cat .github/prompts/pr-review.prompt.md
echo '</review_instructions>'
echo "$DELIM"
} >> "$GITHUB_ENV"

- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@2cc1ac1331eac7a6a96d716dd204dd2888d0fcd2 # main @ Claude Code 2.1.128 / Agent SDK 0.2.128 (needed for --effort xhigh)
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API }}
prompt: ${{ env.REVIEW_PROMPT }}
claude_args: >-
--model claude-opus-4-8
--effort xhigh
--allowed-tools "Read,Grep,Glob,Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"
10 changes: 9 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ packages/
├── evm/ # @omni-bridge/evm - EVM transaction builder (viem-based)
├── near/ # @omni-bridge/near - NEAR transaction builder + shims
├── solana/ # @omni-bridge/solana - Solana instruction builder (Anchor-based)
├── starknet/ # @omni-bridge/starknet - Starknet transaction builder
├── aptos/ # @omni-bridge/aptos - Aptos entry-function payload builder
├── btc/ # @omni-bridge/btc - Bitcoin/Zcash UTXO operations
└── sdk/ # @omni-bridge/sdk - Umbrella re-export of all packages
```
Expand All @@ -43,17 +45,21 @@ packages/
- `createEvmBuilder({ network, chain })` → EVM transaction building
- `createNearBuilder({ network })` → NEAR transaction building
- `createSolanaBuilder({ network, connection? })` → Solana instruction building
- `createStarknetBuilder({ network, bridgeAddress? })` → Starknet Call[] building
- `createAptosBuilder({ network, bridgeAddress? })` → Aptos entry-function payloads
- `createBtcBuilder({ network, chain })` → Bitcoin/Zcash UTXO operations

**Unsigned Transaction Types**: SDK returns library-agnostic plain objects:

- `EvmUnsignedTransaction` → Compatible with viem and ethers v6 directly
- `NearUnsignedTransaction` → Use shims: `toNearKitTransaction()` or `sendWithNearApiJs()`
- `TransactionInstruction[]` → Native @solana/web3.js instructions
- `Call[]` → Native starknet.js calls
- `AptosFunctionPayload` → Compatible with @aptos-labs/ts-sdk `InputEntryFunctionData`
- `BtcWithdrawalPlan` → UTXO inputs/outputs for signing

**OmniAddress System**: Cross-chain addresses use chain prefixes:
`eth:0x...`, `near:account.near`, `sol:...`, `base:0x...`, `arb:0x...`, `btc:...`, `zcash:...`
`eth:0x...`, `near:account.near`, `sol:...`, `base:0x...`, `arb:0x...`, `strk:0x...`, `aptos:0x...`, `btc:...`, `zcash:...`

### Transfer Flow

Expand All @@ -71,6 +77,8 @@ packages/
- `packages/near/src/builder.ts` - NEAR transaction builder
- `packages/near/src/shims.ts` - near-kit and near-api-js conversion helpers
- `packages/solana/src/builder.ts` - Solana instruction builder
- `packages/starknet/src/builder.ts` - Starknet transaction builder
- `packages/aptos/src/builder.ts` - Aptos payload builder
- `packages/btc/src/builder.ts` - Bitcoin/Zcash UTXO builder

## Testing Patterns
Expand Down
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
![Status](https://img.shields.io/badge/Status-Beta-blue)
![License](https://img.shields.io/badge/License-MIT-green)

TypeScript SDK for cross-chain token transfers via the [Omni Bridge](https://github.com/Near-one/omni-bridge) protocol. Transfer tokens between Ethereum, NEAR, Solana, Base, Arbitrum, Polygon, BNB Chain, Abstract, Starknet, Fogo, Bitcoin, and Zcash.
TypeScript SDK for cross-chain token transfers via the [Omni Bridge](https://github.com/Near-one/omni-bridge) protocol. Transfer tokens between Ethereum, NEAR, Solana, Base, Arbitrum, Polygon, BNB Chain, Abstract, Starknet, Aptos, Fogo, Bitcoin, and Zcash.

## Install

Expand Down Expand Up @@ -52,25 +52,31 @@ The SDK uses **OmniAddress** format — a chain prefix followed by the native ad
eth:0x1234... → Ethereum
base:0x1234... → Base
arb:0x1234... → Arbitrum
pol:0x1234... → Polygon
near:alice.near → NEAR
sol:ABC123... → Solana
fogo:ABC123... → Fogo
abs:0x1234... → Abstract
strk:0x1234... → Starknet
aptos:0x1234... → Aptos
btc:bc1q... → Bitcoin
zcash:t1abc... → Zcash
```

This makes it unambiguous which chain an address belongs to, which is essential for cross-chain operations.

## Packages

| Package | Description |
| --------------------- | ---------------------------------------------- |
| `@omni-bridge/core` | Validation, types, configuration, API client |
| `@omni-bridge/evm` | Ethereum, Base, Arbitrum, Polygon, BNB Chain, Abstract |
| `@omni-bridge/near` | NEAR Protocol |
| `@omni-bridge/solana` | Solana |
| `@omni-bridge/btc` | Bitcoin, Zcash |
| `@omni-bridge/sdk` | Umbrella package (re-exports all of the above) |
| Package | Description |
| ----------------------- | ---------------------------------------------- |
| `@omni-bridge/core` | Validation, types, configuration, API client |
| `@omni-bridge/evm` | Ethereum, Base, Arbitrum, Polygon, BNB Chain, Abstract |
| `@omni-bridge/near` | NEAR Protocol |
| `@omni-bridge/solana` | Solana, Fogo |
| `@omni-bridge/starknet` | Starknet |
| `@omni-bridge/aptos` | Aptos |
| `@omni-bridge/btc` | Bitcoin, Zcash |
| `@omni-bridge/sdk` | Umbrella package (re-exports all of the above) |

Install `@omni-bridge/sdk` for everything, or pick individual packages to minimize bundle size.

Expand Down
Loading
Loading