Skip to content

Add injective-cctp skill#9

Open
ckhbtc wants to merge 8 commits into
masterfrom
add-injective-cctp
Open

Add injective-cctp skill#9
ckhbtc wants to merge 8 commits into
masterfrom
add-injective-cctp

Conversation

@ckhbtc

@ckhbtc ckhbtc commented May 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a new skill, injective-cctp, for moving native USDC into and out of Injective EVM via Circle's Cross-Chain Transfer Protocol V2. Burns USDC on the source chain, polls Circle's iris-api for the attestation, and submits the mint on the destination chain — no relayer, no aggregator, no custodian.

Same flow as the wallet UI live at https://usdc.inj.so (source) — this is the agent/script flavor of the same logic.

Coverage

Direction Source Destination
Deposit Ethereum / Arbitrum / Base / OP Mainnet / Polygon / Avalanche Injective EVM (chain id 1776, CCTP domain 29)
Withdraw Injective EVM any of the above

Standard CCTP V2 transfer only — Injective doesn't support Fast Transfer per Circle's supported-chains list.

Layout

skills/injective-cctp/
├── SKILL.md                  # frontmatter + 5-step flow + runbook
├── README.md                 # repo-browse blurb
├── scripts/
│   ├── package.json          # viem ^2.21
│   ├── cctp.mjs              # CLI entrypoint (--from, --to, --amount, --resume)
│   ├── chains.mjs            # 7 chain configs (CCTP V2 contracts, USDC, RPCs)
│   ├── abis.mjs              # minimal V2 + ERC-20 ABIs
│   └── .gitignore
└── references/
    ├── architecture.md       # deeper CCTP V2 protocol notes
    └── domains.md            # Circle's chain reference

Quick start

cd skills/injective-cctp/scripts
npm install
export CCTP_PRIVATE_KEY=0x...
node cctp.mjs --from ethereum --amount 100                  # deposit to Injective
node cctp.mjs --from injective --to base --amount 50        # withdraw to Base
node cctp.mjs --resume 0xdead... --from arbitrum            # finish a stuck transfer

Test plan

  • node --check passes on all three .mjs files
  • node cctp.mjs --help renders without errors
  • npm install resolves cleanly (only viem, transitive deps)
  • One real Ethereum → Injective transfer end-to-end (recommend reviewer try this with a small amount before merging)
  • One real Injective → Base (or other) transfer end-to-end

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • New Claude Code skill to bridge native USDC between Injective EVM and Ethereum via Circle CCTP V2, with a headless CLI (start/resume transfers), attestation polling, and stuck-transfer recovery.
  • Documentation

    • Comprehensive end-to-end guides, protocol architecture, chain/domain reference, operational constraints (EVM/mainnet/standard-transfer), CLI quick-start and examples, and wallet UI link.
  • Chores

    • Added ignore rules for local dev artifacts.

Headless CCTP V2 transfer CLI for moving native USDC to and from
Injective EVM. Covers both directions (deposit and withdraw) across
the 6 V2-enabled EVM chains (Ethereum, Arbitrum, Base, OP, Polygon,
Avalanche) with Injective EVM as the other side.

The skill walks the standard 5-step CCTP flow — approve, depositForBurn,
attestation poll against iris-api, switch, receiveMessage — and includes
a --resume flag so a stuck transfer can be picked up by anyone holding
the burn tx hash. No relayer, no aggregator, no custodian.

Layout:
  skills/injective-cctp/
    SKILL.md                       — frontmatter + flow + runbook
    README.md                      — repo-browse blurb
    scripts/                       — runnable Node CLI (viem)
      package.json
      cctp.mjs                     — entrypoint
      chains.mjs                   — chain configs (V2 contracts, USDC, RPCs)
      abis.mjs                     — minimal V2 + ERC-20 ABIs
      .gitignore                   — node_modules, .env, *.log
    references/
      architecture.md              — deeper CCTP V2 protocol notes
      domains.md                   — Circle's chain reference

Same flow as the wallet UI at https://usdc.inj.so
(source: github.com/ckhbtc/usdc-widget) — this is the agent/script
flavor of the same logic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Rate limit exceeded

@ckhbtc has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 21 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ede9ed20-f65d-49b1-b232-4eb2fb8f7ca4

📥 Commits

Reviewing files that changed from the base of the PR and between 7fedddf and 3fb55df.

📒 Files selected for processing (4)
  • skills/injective-cctp/SKILL.md
  • skills/injective-cctp/references/domains.md
  • skills/injective-cctp/scripts/cctp.mjs
  • skills/injective-cctp/scripts/chains.mjs
📝 Walkthrough

Walkthrough

This PR adds the injective-cctp skill: CCTP V2 documentation and chain reference data, minimal contract ABIs, a chains registry and viem helper, a headless CLI (cctp.mjs) that approves, burns, polls Circle attestation, and submits receiveMessage mints (with resume support), plus package and docs.

Changes

CCTP V2 Bridge Implementation

Layer / File(s) Summary
Protocol & Architecture Documentation
references/architecture.md
CCTP V2 burn-and-mint spec with five-step flow (approve, depositForBurn, attest via iris-api polling, switch chain, receiveMessage), polling states, failure modes, and references.
Domains & Reference Data
references/domains.md
Mainnet deterministic V2 contract addresses, domain ↔ EVM mappings, mainnet/testnet tables, capabilities matrix, and instructions for adding chains.
Configuration & Chain Registry
scripts/chains.mjs
CCTP_V2 bundle, ATTESTATION_API, STANDARD_FINALITY/MAX_FEE, ZERO_BYTES32, CHAINS registry with per-chain metadata and viemChain() helper.
Contract ABIs
scripts/abis.mjs
Exports TOKEN_MESSENGER_V2_ABI (depositForBurn), MESSAGE_TRANSMITTER_V2_ABI (receiveMessage, MessageSent), and ERC20_ABI (approve, allowance, balanceOf, decimals).
CLI Orchestration
scripts/cctp.mjs
Headless CLI: arg parsing and validation, route enforcement (one side must be injective), amount/recipient parsing and mintRecipient padding, allowance check and conditional approve, depositForBurn burn, iris-api attestation polling (backoff, 30-min timeout), and receiveMessage mint submission. Supports --resume.
Package Configuration & Documentation
scripts/package.json, scripts/.gitignore, README.md, SKILL.md
Node package manifest with CLI binary and viem dependency, .gitignore additions, README quick-start, and SKILL.md full guide including stuck-transfer recovery, deterministic contract addresses, and operational constraints (EVM-only, standard transfers, native USDC only).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI
  participant SourceChain
  participant CircleAPI
  participant DestChain
  User->>CLI: run (key, --from, --to, amount / --resume)
  CLI->>SourceChain: approve() if needed -> depositForBurn (burn)
  SourceChain-->>CLI: burnTxHash
  CLI->>CircleAPI: poll attestation (iris-api)
  CircleAPI-->>CLI: attestation (complete)
  CLI->>DestChain: receiveMessage(message, attestation)
  DestChain-->>CLI: mintTxHash
  CLI-->>User: output burnHash, mintHash
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped a path from burn to mint,

pressed keys, polled logs, and watched the hint.
No keeper, just a message true—
burn, attest, mint — then time to chew 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add injective-cctp skill' accurately and concisely summarizes the primary change—introducing a new CCTP-based skill for bridging USDC between Injective and other chains.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-injective-cctp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@socket-security

socket-security Bot commented May 8, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedviem@​2.48.119810010098100

View full report

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@skills/injective-cctp/references/architecture.md`:
- Around line 28-30: Several fenced code blocks in architecture.md lack language
tags (triggering markdownlint MD040); update the fences for the shown snippets
so they include appropriate languages: add "solidity" to the
USDC.approve(TokenMessenger, amount) and the receiveMessage(bytes message, bytes
attestation) snippets, add "solidity" to the depositForBurn(...) signature
block, and add "http" to the GET https://iris-api.circle.com/... example; locate
these by the unique snippets USDC.approve(TokenMessenger, amount),
depositForBurn(...), GET
https://iris-api.circle.com/v2/messages/{srcDomain}?transactionHash={txHash},
and receiveMessage(bytes message, bytes attestation) and update the opening
triple-backticks to include the language identifier.

In `@skills/injective-cctp/scripts/cctp.mjs`:
- Around line 127-143: The polling code around the fetch(url) call swallows
non-OK HTTP responses and network errors; update the logic in the try/catch
around fetch + res.ok (the block that reads res, data, msg, status, lastStatus,
elapsed) to log non-2xx responses (include res.status and res.statusText) when
res.ok is false and to log the caught exception (e) in the catch block,
preserving the existing retry behavior; ensure the log messages include context
like "attestation poll" and the URL or elapsed time so operators can see 429/5xx
errors and network blips while still retrying.
- Around line 197-198: Validate the --resume value before assigning it to
burnHash: check args.resume is a hex string of 64 chars (optionally prefixed
with "0x"), normalize by adding "0x" if missing, and reject/exit with a clear
error if it fails validation so it is not passed to the attestation
URL/transactionHash param (used later where burnHash is sent to the Circle
iris-api and in the poll loop). Update the assignment around burnHash and any
code that constructs the attestation URL to use the validated/normalized hash.

In `@skills/injective-cctp/SKILL.md`:
- Around line 91-93: Add the fenced code block language identifier "http" to the
existing block containing the HTTP GET example (the block with "GET
https://iris-api.circle.com/v2/messages/{srcDomain}?transactionHash={burnTxHash}")
so the fence becomes ```http to satisfy markdownlint MD040; locate the block in
SKILL.md and update the opening backticks only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 26a5aada-0bd6-4f99-957e-07221e9d8399

📥 Commits

Reviewing files that changed from the base of the PR and between ba428ea and 86fa123.

📒 Files selected for processing (9)
  • skills/injective-cctp/README.md
  • skills/injective-cctp/SKILL.md
  • skills/injective-cctp/references/architecture.md
  • skills/injective-cctp/references/domains.md
  • skills/injective-cctp/scripts/.gitignore
  • skills/injective-cctp/scripts/abis.mjs
  • skills/injective-cctp/scripts/cctp.mjs
  • skills/injective-cctp/scripts/chains.mjs
  • skills/injective-cctp/scripts/package.json

Comment on lines +28 to +30
```
USDC.approve(TokenMessenger, amount)
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Specify fence languages for unlabeled code blocks.

At Line 28, Line 43, Line 67, and Line 93, fenced blocks are missing language tags, which can trigger markdownlint MD040.

Proposed fix
-```
+```solidity
 USDC.approve(TokenMessenger, amount)

- +solidity
depositForBurn(
uint256 amount,
uint32 destinationDomain, // Circle's domain id, NOT EVM chain id
bytes32 mintRecipient, // bytes32-padded destination address
address burnToken, // USDC contract on the source chain
bytes32 destinationCaller, // bytes32(0) = anyone can submit the mint
uint256 maxFee, // 0 for standard transfer
uint32 minFinalityThreshold // 2000 for standard, 1000 for fast
)


-```
+```http
GET https://iris-api.circle.com/v2/messages/{srcDomain}?transactionHash={txHash}

- +solidity
receiveMessage(bytes message, bytes attestation)

Also applies to: 43-53, 67-69, 93-95

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 28-28: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/injective-cctp/references/architecture.md` around lines 28 - 30,
Several fenced code blocks in architecture.md lack language tags (triggering
markdownlint MD040); update the fences for the shown snippets so they include
appropriate languages: add "solidity" to the USDC.approve(TokenMessenger,
amount) and the receiveMessage(bytes message, bytes attestation) snippets, add
"solidity" to the depositForBurn(...) signature block, and add "http" to the GET
https://iris-api.circle.com/... example; locate these by the unique snippets
USDC.approve(TokenMessenger, amount), depositForBurn(...), GET
https://iris-api.circle.com/v2/messages/{srcDomain}?transactionHash={txHash},
and receiveMessage(bytes message, bytes attestation) and update the opening
triple-backticks to include the language identifier.

Comment thread skills/injective-cctp/scripts/cctp.mjs Outdated
Comment thread skills/injective-cctp/scripts/cctp.mjs Outdated
Comment on lines +197 to +198
let burnHash = args.resume || null;

@coderabbitai coderabbitai Bot May 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

--resume hash is not validated before being used in the attestation URL.

args.resume is passed directly to Circle's iris-api as transactionHash. A mistyped or incorrectly formatted value (wrong length, non-hex, missing 0x) produces no early error — the poll loop runs silently for 30 minutes before timing out.

🛠️ Proposed fix — validate tx hash format at startup
   let burnHash = args.resume || null;

+  if (burnHash && !/^0x[0-9a-fA-F]{64}$/.test(burnHash)) {
+    err(`invalid --resume value: expected a 0x-prefixed 32-byte hex hash, got: ${burnHash}`);
+    process.exit(1);
+  }
+
   if (!burnHash) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let burnHash = args.resume || null;
let burnHash = args.resume || null;
if (burnHash && !/^0x[0-9a-fA-F]{64}$/.test(burnHash)) {
err(`invalid --resume value: expected a 0x-prefixed 32-byte hex hash, got: ${burnHash}`);
process.exit(1);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/injective-cctp/scripts/cctp.mjs` around lines 197 - 198, Validate the
--resume value before assigning it to burnHash: check args.resume is a hex
string of 64 chars (optionally prefixed with "0x"), normalize by adding "0x" if
missing, and reject/exit with a clear error if it fails validation so it is not
passed to the attestation URL/transactionHash param (used later where burnHash
is sent to the Circle iris-api and in the poll loop). Update the assignment
around burnHash and any code that constructs the attestation URL to use the
validated/normalized hash.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an example of the differentiated error handling that should be handled within the polling function.

see: #9 (comment)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh api repos/InjectiveLabs/agent-skills/pulls/9/comments --jq '.[] | select(.id == 3206142460) | {id: .id, body: .body, path: .path, line: .line}'

Length of output: 4030


@bguiz, understood — thanks for the clarification. The connection makes sense: if an invalid or mistyped --resume hash is supplied, Circle's iris-api will respond with a non-2xx status, and with the differentiated error handling from that comment in place (logging res.status for non-OK responses and e.message for network errors), the user would immediately see something like:

[cctp]   Circle API returned HTTP 404 — retrying (5s elapsed)

…rather than 30 minutes of silence. That's a reasonable design choice — surface the problem through meaningful API error feedback rather than a pre-flight regex check.

I'll withdraw the upfront format-validation suggestion in favour of the polling-level fix in r3206142460, which is the right layer to handle this.


✏️ Learnings added
Learnt from: bguiz
Repo: InjectiveLabs/agent-skills PR: 9
File: skills/injective-cctp/scripts/cctp.mjs:197-198
Timestamp: 2026-05-08T05:02:36.818Z
Learning: In `skills/injective-cctp/scripts/cctp.mjs` (InjectiveLabs/agent-skills), invalid `--resume` tx hash values should be surfaced through differentiated error handling inside the `pollAttestation` function (logging non-2xx HTTP responses and network errors) rather than upfront regex/format validation at startup. The Circle iris-api will return a non-2xx response for invalid hashes, which the improved poll loop will log clearly.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +91 to +93
```
GET https://iris-api.circle.com/v2/messages/{srcDomain}?transactionHash={burnTxHash}
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language identifier to the fenced code block at Line 91.

This block currently omits a fence language and can trigger markdownlint MD040.

Proposed fix
-   ```
+   ```http
    GET https://iris-api.circle.com/v2/messages/{srcDomain}?transactionHash={burnTxHash}
    ```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
GET https://iris-api.circle.com/v2/messages/{srcDomain}?transactionHash={burnTxHash}
```
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 91-91: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/injective-cctp/SKILL.md` around lines 91 - 93, Add the fenced code
block language identifier "http" to the existing block containing the HTTP GET
example (the block with "GET
https://iris-api.circle.com/v2/messages/{srcDomain}?transactionHash={burnTxHash}")
so the fence becomes ```http to satisfy markdownlint MD040; locate the block in
SKILL.md and update the opening backticks only.

| `TokenMinterV2` | `0xfd78EE919681417d192449715b2594ab58f5D002` |
| `MessageV2` | `0xec546b6B005471ECf012e5aF77FBeC07e0FD8f78` |

## Per-chain values used by this skill

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest including testnet values as well for this, as users will likely not want to try this with real funds initially. Also good for demo purposes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. added a testnet chain table to references/domains.md + a commented-out testnet section template in chains.mjs. held off hardcoding the testnet v2 contract addresses since they're a separate set from mainnet's deterministic ones – left a pointer to circle's docs instead. bf97753 / 196a119

| Ethereum, Avalanche, OP, Arbitrum, Base, Polygon | ✓ | ✓ | ✓ |
| Injective EVM | ✓ | — | — |

This skill only uses **standard transfer** so Fast/Forwarding are out of scope. Adding Fast for non-Injective routes is a small change in `cctp.mjs` (lower `minFinalityThreshold`, non-zero `maxFee`, plus a fee fetch from Circle's `/v2/burn/USDC/fees/{src}/{dst}` endpoint).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle does not support CCTP fast transfers for Injective, mainly because there is no need.

CCTP fast transfers on networks that support them are (ironically) slower than CCTP standard transfers on networks that do no support fast transfers.

If a network has a very fast block time and doesn't require multiple block confirmations for finality - e.g. ~600ms and instant finality for Injective, then there is essentially no need to speed things up further.

However for Ethereum, where block times are over 10s and requires multiple block confirmations, the wait time is over a minute, hence the need for CCTP fast transfer mode.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rewrote that section – frame is "feature unnecessary, by virtue of injective being fast enough already" instead of "missing feature". kept the bit about source-side fast transfer for inbound from ethereum since that wait IS still ~13min and worth doing. 2ab3ede / 3dc9d07


## Why chain ID ≠ domain ID

Circle's CCTP domains pre-date most L2s and use a separate, smaller integer space (currently 0-29). They have no relationship to EIP-155 chain IDs. The CLI never confuses them because each chain entry stores both fields explicitly — `id` is for viem's chain config (and EIP-712 signing), `domain` is for `depositForBurn`'s `destinationDomain` arg.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another reason is that different instances of the same network share the same domain ID, while maintaining separate chain IDs. E.g. Injective Testnet and Injective Mainnet are both 29.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added an explicit section explaining this. injective mainnet (1776) and testnet (1439) both = domain 29, with the ethereum/sepolia parallel called out for symmetry. be83cab / 0198f64

Comment thread skills/injective-cctp/scripts/cctp.mjs Outdated
Comment thread skills/injective-cctp/scripts/cctp.mjs Outdated
}

// ─── Main ─────────────────────────────────────────────────────────────────────
async function main() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this main function is a little unwield-y - I suggest splitting it into multiple functions, each of which handle 1 stage of the process (e.g. burn/ attestation polling/ mint), and then have the main function call them in sequence.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. split into setUp / approveIfNeeded / burn / pollAttestation / mint. main is just orchestration now and each stage is independently testable. 4bb5ff6 / af812ca

Comment thread skills/injective-cctp/scripts/cctp.mjs Outdated
Comment on lines +197 to +198
let burnHash = args.resume || null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an example of the differentiated error handling that should be handled within the polling function.

see: #9 (comment)

@@ -0,0 +1,280 @@
#!/usr/bin/env node

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than write this functionality ourselves, I think it would be better to instead use the bridge kit SDK maintained by circle: https://www.npmjs.com/package/@circle-fin/bridge-kit

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... however, the issue is that bridge kit has not been updated to include Injective configurations yet.
their devrel @huijing says that they already have a branch/ PR for this in the works, so it should be soon.
I'm planning to include this within https://docs.injective.network/developers-defi/usdc-cctp-tutorial (or as an additional page) when that happens.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noted. once @huijing's PR lands and bridge-kit ships injective configs, the burn / poll / mint stages collapse into a few SDK calls – the staging refactor i just did should make that swap surgical.


`scripts/cctp.mjs` accepts a `--resume <burnTxHash> --from <chain>` flag that does steps 2-4 automatically.

## Reference

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this page should be included as a reference: https://docs.injective.network/developers-defi/usdc-stablecoin

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added. it's the first link in a new "External docs" block at the top of "Reference," alongside the in-flight cctp tutorial page and circle's docs. f8633ad / 63aa190

ckhbtc and others added 7 commits May 8, 2026 12:36
The original framing read "Injective doesn't support Fast Transfer"
which sounds like a missing feature. Brendan's review pointed out the
actual reason: Injective's ~600ms blocks + instant finality already
beat what Fast Transfer offers on slower chains, so Fast Transfer is
*unnecessary*, not *unavailable*.

Replaced the matrix paragraph in domains.md with a "Why Injective
doesn't need Fast Transfer" section that explains finality vs Fast
mechanics, then updated the SKILL.md constraints bullet to match.
Capabilities matrix kept; the "—" cell now reads "(not needed; see
above)" so the framing matches.

Source-side Fast Transfer for inbound deposits from Ethereum still
makes sense (the wait is on Ethereum's finality, not Injective's) —
that path is documented as a small CLI extension but unwired today.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brendan flagged that the previous testnet framing missed an important
nuance: each chain family has ONE CCTP domain id, shared between its
mainnet and testnet instances. Concretely, Injective mainnet (chain id
1776) and Injective testnet (chain id 1439) both report CCTP domain 29.
The chain id is what tells JSON-RPC apart; the domain id is what
depositForBurn uses to route.

Added a dedicated section to references/domains.md citing Circle's
own quote and walking through the Ethereum + Injective example so the
distinction is unambiguous.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brendan asked to include testnet values so users can experiment without
risking real funds. Added across three places:

  • domains.md — split V2 contract addresses into separate (mainnet)
    and (testnet) sections; renamed the per-chain table to "Mainnet —
    chain reference" and added a parallel "Testnet — chain reference"
    table covering Sepolia/Fuji/Amoy/Injective-testnet with chain ids
    + domain ids.
  • chains.mjs — added a commented testnet-section template right
    before viemChain, with the chain id + domain id for each testnet
    so adding a key like `injective-testnet` is a copy-paste from the
    nearest mainnet entry.
  • SKILL.md — new constraint bullet noting the CLI ships mainnet
    only and pointing at the docs/template for adding testnet support.

Held back from hardcoding testnet V2 contract addresses since they're
a separate Circle deployment from the deterministic mainnet ones —
Circle updates them periodically and shipping stale ones is risky.
Pointer to developers.circle.com/cctp/references/contract-addresses
in three places so it's not missable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brendan suggested using @circle-fin/bridge-kit instead of writing the
V2 flow ourselves, then noted in a follow-up that bridge-kit doesn't
include Injective configurations yet — Circle DevRel (@huijing) has a
PR in progress and Brendan plans to make
docs.injective.network/developers-defi/usdc-cctp-tutorial the canonical
walkthrough when it lands.

Captured the migration plan in two places so it's not orphaned:

  • references/domains.md — new "Future: bridge-kit migration" section
    linking the npm package and the in-flight tutorial page, explaining
    that when Injective lands the CLI's burn/poll/mint stages collapse
    to a few SDK calls.
  • scripts/cctp.mjs — code comment right under the usage block calling
    out the deferred migration so the next person editing the file
    sees the plan immediately.

Not migrating yet because we'd block the skill on a Circle PR landing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brendan asked for docs.injective.network/developers-defi/usdc-stablecoin
to be cited as an authoritative chain-side USDC reference. Added an
"External docs" sub-section at the top of ## Reference covering:

  • Injective USDC stablecoin overview (chain-side primer)
  • Injective USDC + CCTP tutorial (the in-flight canonical walkthrough)
  • Circle CCTP docs (protocol authority)
  • Circle bridge-kit npm (cross-link to the migration plan)

Also clarified that the deterministic V2 contract addresses below are
mainnet-only — testnets use a separate set, per references/domains.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
main() was 100+ lines mixing arg parsing, validation, allowance check,
approve, burn, attestation poll, and mint into a single function.
Brendan suggested splitting it so each stage is independently
readable and replaceable (the bridge-kit migration in particular
will swap out 4 of these in one shot).

Extracted five stage functions:

  • setUp(argv)             — arg parse, env check, account/chain
                               validation, recipient/amount parsing.
                               Throws (instead of process.exit-ing) so
                               it's testable.
  • approveIfNeeded(ctx)    — allowance read + approve write, skipped
                               when allowance is already sufficient.
                               Returns the approve hash or null.
  • burn(ctx)               — depositForBurn + receipt wait, returns
                               the burn hash.
  • pollAttestation(d, h)   — unchanged in this commit; the next
                               commit rewrites its error handling.
  • mint(ctx)               — receiveMessage on the destination chain.

main() is now orchestration only — pulls the ctx from setUp, builds
the four viem clients, and calls the stages in sequence.

No behavior change in this commit. Verified with `node --check` and
`node cctp.mjs --help`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous pollAttestation wrapped the fetch in one try/catch that
swallowed every non-2xx response and every network failure alike.
Effect: a 30-min stalled poll produced zero output between the
initial "polling …" log and the eventual timeout throw — impossible
to tell from outside whether Circle was rate-limiting, the API was
down, or the network was just slow.

Rewrote with one branch per failure mode:

  404                Circle hasn't seen the burn yet (normal at start).
                     Log once per status transition, then keep polling.
  429                Rate-limited. Log, then back off 30s before next poll.
  5xx                Circle API problem. Log with status code, retry on the
                     normal 5s cadence (servers usually recover).
  other 4xx          Unexpected. Log status + statusText, retry.
  network error      DNS/connection-level failure. Log the error message
                     itself (not just "blip — retry"), retry.
  malformed JSON     Log the parse error, retry.
  pending complete   200 + status==='complete' + attestation !== 'PENDING'
                     → return.
  pending in-flight  Log only when the status string changes.

Now the operator gets a continuous stream of diagnostics during long
polls, can tell exactly which kind of failure they're seeing, and the
30-min timeout is the path of last resort instead of the only signal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ckhbtc
ckhbtc force-pushed the add-injective-cctp branch from 7fedddf to 3fb55df Compare May 8, 2026 11:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants