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
6 changes: 5 additions & 1 deletion .github/workflows/test-ts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ jobs:
run: npm test
working-directory: sdk/ts

- name: Build demo
- name: Build EVM demo
run: npm --workspace @yellow-org/evm-deposit-demo run build
working-directory: sdk/ts

- name: Build Solana demo
run: npm --workspace @yellow-org/solana-deposit-demo run build
working-directory: sdk/ts
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build lint test generate devnet devnet-evm devnet-down ts-deps integration
.PHONY: build lint test generate devnet devnet-evm devnet-sol devnet-down ts-deps integration

build:
go build ./...
Expand Down Expand Up @@ -27,14 +27,19 @@ devnet-evm:
docker compose -f devnet/docker-compose.yml up -d anvil
go run ./devnet/wait --networks anvil

devnet-sol:
docker compose -f devnet/docker-compose.yml up -d solana
go run ./devnet/wait --networks solana

devnet-down:
docker compose -f devnet/docker-compose.yml down -v

ts-deps:
npm --prefix sdk/ts ci

# Blockchain flow tests against the devnet. Go tests cover deposit + withdrawal
# per chain; the TS suite covers EVM deposits. See devnet/README.md.
# per chain; the TS suite covers EVM and Solana deposits. See devnet/README.md.
integration: ts-deps
go test -tags integration ./pkg/blockchain/... -v
npm --prefix sdk/ts run test:integration:evm
npm --prefix sdk/ts run test:integration:sol
23 changes: 18 additions & 5 deletions devnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ withdrawal test runs the whole *k-of-n* quorum in-process — it holds N local
`sign.KeySigner`s and drives `Pack → Validate → Sign → Merge → Submit →
VerifyExecution` itself, so no p2p mesh is needed.

The TypeScript EVM SDK integration test lives under `sdk/ts/test` and runs
through the same `make integration` target.
The TypeScript SDK integration tests live under `sdk/ts/test` and run through
the same `make integration` target.

## Run

```sh
make devnet # anvil + bitcoind + rippled + solana-test-validator; blocks until all answer RPC
npm --prefix sdk/ts ci
make integration # Go blockchain integrations + TS EVM integration
make integration # Go blockchain integrations + TS EVM and Solana integration
make devnet-down
```

Expand Down Expand Up @@ -47,8 +47,20 @@ wallet, the XRPL genesis master).
authority), deposits native SOL, then runs the quorum withdrawal. The Config
PDA is a singleton, so the signer set is **fixed** across runs and only the
withdrawalID is fresh — re-runs stay clean without a validator restart. The
validator image is multi-arch (no `platform:` pin — the Agave validator needs
AVX, which isn't emulable on Apple silicon).
TypeScript Solana integration test creates and funds local signers, submits
native SOL and SPL deposits, and verifies each returned transaction reference.
The validator image is multi-arch (no `platform:` pin — the Agave validator
needs AVX, which isn't emulable on Apple silicon).

For focused local iteration:

```sh
make devnet-evm
npm --prefix sdk/ts run test:integration:evm

make devnet-sol
npm --prefix sdk/ts run test:integration:sol
```

## Optional overrides

Expand All @@ -59,6 +71,7 @@ Defaults target the devnet; override the endpoints if pointing elsewhere:
| `EVM_RPC_URL` / `EVM_DEPLOYER_KEY` | `http://127.0.0.1:8545` / anvil account 0 |
| `BTC_RPC_URL` / `BTC_RPC_USER` / `BTC_RPC_PASS` | `http://127.0.0.1:18443` / `sdk` / `sdk` |
| `XRPL_RPC_URL` | `http://127.0.0.1:5005` |
| `SOL_RPC_URL` | `http://127.0.0.1:8899` |

## Notes

Expand Down
159 changes: 142 additions & 17 deletions sdk/ts/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
# Clearnet TypeScript SDK

TypeScript SDK for Clearnet integration. This package currently exposes
the EVM vault depositor, with support for native ETH deposits and ERC-20
deposits. Deposits credit a `destination` made of an account and an optional
ADR-015 opaque reference.
TypeScript SDK for Clearnet integration. This package currently exposes EVM and
Solana vault depositors. EVM supports native ETH and ERC-20 deposits. Solana
supports native SOL and SPL token deposits. Deposits credit a `destination` made
of an account and an optional ADR-015 opaque reference.

The package is ESM-first and uses `viem` for EVM clients and primitives.
The package is ESM-first. EVM callers use `viem` clients and primitives. Solana
callers provide an SDK-owned signer adapter around their wallet or local keypair.

## Install

```sh
npm install @yellow-org/clearnet-sdk viem
npm install @yellow-org/clearnet-sdk viem @solana/web3.js
```

For local development in this repository:
Expand All @@ -20,7 +21,7 @@ cd sdk/ts
npm ci
```

## Quick Start
## EVM Quick Start

Native ETH deposits use `EVM_NATIVE_ASSET`, which is the EVM zero address. Amounts
must be `bigint` values in base units.
Expand Down Expand Up @@ -123,6 +124,71 @@ before submitting the custody `deposit(...)` transaction. A successful
If an ERC-20 approval fails before the deposit is submitted, `error.txRef` may
refer to the approval transaction.

## Solana Deposits

Solana deposits use `SolanaVaultDepositor`. The SDK builds the custody
instruction and delegates signing/broadcast to a caller-provided `SolanaSigner`.
The signer boundary is small so browser-wallet, Wallet Standard, and local
keypair adapters can live outside the core SDK.

```ts
import {
SOLANA_NATIVE_ASSET,
SolanaVaultDepositor,
} from "@yellow-org/clearnet-sdk";
import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import type { SolanaSigner } from "@yellow-org/clearnet-sdk";

const rpcUrl = "http://127.0.0.1:8899";
const keypair = Keypair.generate();
const connection = new Connection(rpcUrl, "confirmed");

const airdrop = await connection.requestAirdrop(
keypair.publicKey,
LAMPORTS_PER_SOL,
);
await connection.confirmTransaction(airdrop, "confirmed");

const signer: SolanaSigner = {
publicKey: keypair.publicKey.toBase58(),
async signAndSend(transaction) {
return sendAndConfirmTransaction(connection, transaction, [keypair], {
commitment: "confirmed",
preflightCommitment: "confirmed",
});
},
};

const depositor = new SolanaVaultDepositor({
rpcUrl,
signer,
commitment: "confirmed",
});

const ref = await depositor.submitDeposit({
destination: {
account: "00000000000000000000000000000000000000a1",
ref: "0x3333333333333333333333333333333333333333333333333333333333333333",
},
asset: SOLANA_NATIVE_ASSET,
amount: 100_000_000n,
});

console.log(ref.raw); // Solana base58 signature
console.log(ref.hash); // 0x + sha256(signature bytes)
console.log(await depositor.verifyDeposit(ref, 0));
```

Native asset aliases are `SOL`, `sol`, `native`, and an empty string. For SPL
deposits, pass the mint public key as `asset` and the amount in token base units.
The SDK does not mint tokens or create token accounts. SPL callers must ensure
the depositor ATA and vault ATA exist before submitting the deposit.

## Deposit References

Pass `destination.ref` to attach a 32-byte opaque sub-account reference to the
Expand All @@ -139,8 +205,9 @@ const ref = await depositor.submitDeposit({
});
```

For EVM, the reference is passed to `Custody.deposit(...)` as `bytes32`. The SDK
does not interpret it.
For EVM, the reference is passed to `Custody.deposit(...)` as `bytes32`. For
Solana, it is encoded into `deposit_sol` or `deposit_spl` as `[u8; 32]`. The SDK
does not interpret it. Omitted references are sent as 32 zero bytes.

## Verify A Deposit

Expand All @@ -157,7 +224,9 @@ const status = await depositor.verifyDeposit(ref, 1);
| `absent` | The transaction is unknown or has a reverted receipt. |

`minConfirmations` accepts a non-negative safe integer `number` or a non-negative
`bigint`.
`bigint`. EVM treats it as an inclusive receipt confirmation count. Solana maps
it onto the commitment ladder: `0` accepts `confirmed`; `>= 1` requires
`finalized`.

## API Reference

Expand Down Expand Up @@ -208,6 +277,34 @@ type TxRef = {

For EVM, `hash` and `raw` are both the transaction hash.

### `SolanaVaultDepositor`

```ts
new SolanaVaultDepositor(config: SolanaDepositorConfig)
```

Config fields:

| Field | Type | Notes |
|---|---|---|
| `rpcUrl` | `string` | Used for signature-status verification and commitment waits. |
| `signer` | `SolanaSigner` | Provides `publicKey` and `signAndSend(transaction)`. |
| `programId` | `string` | Optional. Must be the default custody program ID in this version. |
| `commitment` | `"processed" \| "confirmed" \| "finalized"` | Optional; defaults to `finalized`. |
| `receiptTimeoutMs` | `number` | Optional default timeout for commitment waits. |

Solana input fields:

| Field | Type | Notes |
|---|---|---|
| `destination.account` | `string` | 20-byte Clearnet account as hex, optional `0x`, or URI-like value whose final path segment is that hex. |
| `destination.ref` | `` `0x${string}` \| undefined `` | Optional 32-byte opaque reference. |
| `asset` | `string` | Native alias (`SOL`, `sol`, `native`, or empty string) or SPL mint public key. |
| `amount` | `bigint` | Positive base-unit amount that fits in `uint64`. |

For Solana, `TxRef.raw` is the base58 signature and `TxRef.hash` is `0x` plus
the SHA-256 digest of the signature bytes.

### `verifyDeposit(ref, minConfirmations)`

Returns `Promise<"absent" | "pending" | "confirmed">`.
Expand All @@ -221,6 +318,7 @@ npm run typecheck
npm test
npm run build
npm --workspace @yellow-org/evm-deposit-demo run build
npm --workspace @yellow-org/solana-deposit-demo run build
```

Run the EVM integration test against local Anvil:
Expand All @@ -239,7 +337,26 @@ make devnet-down
The integration test deploys fresh `Custody` and `MockERC20` contracts on each
run.

To run the repository integration suite, including this TS EVM integration test:
Run the Solana integration test against the local validator:

```sh
# From the repository root:
make devnet-sol

# From sdk/ts:
npm run test:integration:sol

# From the repository root:
make devnet-down
```

The Solana devnet preloads the custody program at
`98eVpih8X9CAcgU9bzNB9V7VtkRrnFZUmqzEnsq7cfmg`. The integration test creates
and funds local signers, creates SPL token accounts needed for the test, submits
native SOL and SPL deposits, and verifies each returned transaction reference.

To run the repository integration suite, including the TS EVM and Solana
integration tests:

```sh
# From the repository root:
Expand All @@ -254,9 +371,10 @@ Start the browser demo from `sdk/ts`:

```sh
npm run demo:evm
npm run demo:sol
```

The demo expects:
The EVM demo expects:

- an EIP-1193 wallet, such as MetaMask
- an RPC URL and chain ID that match the wallet's selected network
Expand All @@ -266,19 +384,26 @@ The demo expects:
`make devnet-evm` starts Anvil on `http://127.0.0.1:8545` with chain ID `31337`,
but it does not predeploy `Custody` for the browser demo.

The Solana demo discovers wallets through Wallet Standard, uses
`solana:signTransaction`, and broadcasts the signed transaction through the
configured RPC URL. The selected wallet chain must be one the wallet advertises,
such as `solana:localnet` for a local validator. The local devnet preloads the
custody program, but the wallet must be funded and SPL token accounts must
already exist for SPL deposits.

## Troubleshooting

Errors thrown by the SDK use `ClearnetSdkError` with a stable `code`.

| Code | Common cause |
|---|---|
| `INVALID_ADDRESS` | `account`, `asset`, `custodyAddress`, or `walletAccount` is not a valid EVM address. |
| `INVALID_AMOUNT` | `amount` is not a positive `bigint` or does not fit in `uint256`. |
| `INVALID_ADDRESS` | EVM address, Solana public key, Solana mint, program ID, or Clearnet account is invalid. |
| `INVALID_AMOUNT` | `amount` is not a positive `bigint` or exceeds the chain limit (`uint256` for EVM, `uint64` for Solana). |
| `INVALID_CONFIRMATIONS` | `minConfirmations` is negative, fractional, or an unsafe number. |
| `INVALID_REFERENCE` | `destination.ref` is not a 32-byte hex value. |
| `INVALID_TX_REF` | `ref.hash` is missing or is not a 32-byte EVM transaction hash. |
| `MISSING_WALLET_ACCOUNT` | The wallet account is missing or does not match `walletClient.account`. |
| `CHAIN_MISMATCH` | The public RPC or wallet chain does not match `chainId`. |
| `INVALID_TX_REF` | `ref.hash` is not bytes32, or Solana `ref.raw` is not a 64-byte signature. |
| `MISSING_WALLET_ACCOUNT` | The EVM wallet account is missing/mismatched, or the Solana signer is missing. |
| `CHAIN_MISMATCH` | EVM only: the public RPC or wallet chain does not match `chainId`. |
| `TX_REVERTED` | A submitted approval or deposit transaction reverted. |
| `RECEIPT_TIMEOUT` | Waiting for a receipt timed out or was aborted. |
| `RPC_ERROR` | The public RPC or wallet provider returned an unexpected error. |
Expand Down
Loading
Loading