Skip to content
Merged
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
26 changes: 21 additions & 5 deletions .github/workflows/push_docker_image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ name: push docker image

on:
push:
branches:
- "main"
pull_request:

jobs:
build_and_push:
Expand All @@ -12,14 +11,22 @@ jobs:
matrix:
docker:
- repo: planetariumhq/9c-bridge
if: github.ref_type == 'branch'
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: set image tag
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "IMAGE_TAG=pr-${{ github.event.pull_request.number }}-git-${{ github.sha }}" >> "$GITHUB_ENV"
else
echo "IMAGE_TAG=git-${{ github.sha }}" >> "$GITHUB_ENV"
fi

- name: login
if: github.event_name == 'push'
run: |
docker login \
--username '${{ secrets.DOCKER_USERNAME }}' \
Expand All @@ -28,9 +35,18 @@ jobs:
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: build-and-push
- name: build (pull request)
if: github.event_name == 'pull_request'
run: |
docker buildx build . \
--platform linux/amd64 \
--tag ${{ matrix.docker.repo }}:${{ env.IMAGE_TAG }} \
--load

- name: build-and-push (push)
if: github.event_name == 'push'
run: |
docker buildx build . \
--platform linux/arm64/v8,linux/amd64 \
--tag ${{ matrix.docker.repo }}:git-${{ github.sha }} \
--tag ${{ matrix.docker.repo }}:${{ env.IMAGE_TAG }} \
--push
23 changes: 23 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,29 @@ yarn codegen
yarn start
```

## Ad-hoc upstream transfer (enqueue to DB)

This project includes an ad-hoc script that **creates an upstream `transfer_asset` transaction and enqueues it into DB** as a synthetic `RequestTransaction` + `ResponseTransaction` pair. The bridge process will pick it up and stage it via the existing periodic staging loop.

### Usage

```bash
# Note: pass args after `--` when using yarn scripts
yarn adhoc:upstream-transfer -- --to <RECIPIENT_ADDRESS_HEX> --amount <DECIMAL_AMOUNT> --decimals 18

# Optional memo
yarn adhoc:upstream-transfer -- --to <RECIPIENT_ADDRESS_HEX> --amount 12.34 --decimals 18 --memo "hello"
```

### Notes / Safety

- The script **does NOT stage immediately**; it only enqueues to DB (`enqueue_only`).
- If the bridge (`yarn start`) is running, it will stage within ~5 seconds (via `stageTransactionFromDB()`).
- The script requires that the bridge has scanned at least one block for the upstream network already.
- It intentionally reuses the latest scanned `Block.index` for the synthetic request to avoid breaking the sync cursor.
- `--amount` is a decimal string; the script converts it to raw value using `--decimals`.
- If `--amount` has more fractional digits than `--decimals`, it fails.

## Code Style

This project uses Biome as a code formatter:
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"private": true,
"scripts": {
"compile": "prisma generate; graphql-codegen; tsc",
"adhoc:upstream-transfer": "npx tsx ./src/scripts/adhoc-upstream-transfer.ts",
"test": "jest",
"start": "npx tsx ./src/index.ts",
"codegen": "prisma generate; graphql-codegen"
},
Expand Down
81 changes: 81 additions & 0 deletions src/scripts/__tests__/adhoc-upstream-transfer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/// <reference types="jest" />

import {
makeAdhocId,
parseArgs,
parseDecimalToRawValue,
} from "../adhoc-upstream-transfer";

describe("adhoc-upstream-transfer helpers", () => {
test("makeAdhocId prefixes adhoc:", () => {
const id = makeAdhocId();
expect(id.startsWith("adhoc:")).toBe(true);
expect(id.length).toBeGreaterThan("adhoc:".length);
});

describe("parseArgs", () => {
test("parses required args", () => {
const args = parseArgs([
"--to",
"abcdef",
"--amount",
"12.34",
"--decimals",
"18",
]);
expect(args).toEqual({
to: "abcdef",
amount: "12.34",
decimals: 18,
});
});

test("parses optional memo", () => {
const args = parseArgs([
"--to",
"abcdef",
"--amount",
"1",
"--decimals",
"0",
"--memo",
"hello",
]);
expect(args.memo).toBe("hello");
});

test("throws on missing value", () => {
expect(() => parseArgs(["--to", "abc", "--amount"])).toThrow(
/Missing value/,
);
});

test("throws on non --key token", () => {
expect(() => parseArgs(["to", "abc"])).toThrow(/Invalid argument/);
});
});

describe("parseDecimalToRawValue", () => {
test("converts integer amounts", () => {
expect(parseDecimalToRawValue("1", 18)).toBe(10n ** 18n);
expect(parseDecimalToRawValue("0", 18)).toBe(0n);
});

test("converts fractional amounts at 18 decimals", () => {
expect(parseDecimalToRawValue("0.000000000000000001", 18)).toBe(1n);
expect(parseDecimalToRawValue("12.34", 2)).toBe(1234n);
});

test("rejects too many fractional digits", () => {
expect(() => parseDecimalToRawValue("0.001", 2)).toThrow(
/more fractional digits/,
);
});

test("rejects negative", () => {
expect(() => parseDecimalToRawValue("-1", 18)).toThrow(
/non-negative/,
);
});
});
});
Loading