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
4 changes: 4 additions & 0 deletions .github/workflows/test-ts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,7 @@ jobs:
- name: Build Solana demo
run: npm --workspace @yellow-org/solana-deposit-demo run build
working-directory: sdk/ts

- name: Build XRPL demo
run: npm --workspace @yellow-org/xrpl-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-sol devnet-down ts-deps integration
.PHONY: build lint test generate devnet devnet-evm devnet-sol devnet-xrpl devnet-down ts-deps integration

build:
go build ./...
Expand Down Expand Up @@ -31,15 +31,20 @@ devnet-sol:
docker compose -f devnet/docker-compose.yml up -d solana
go run ./devnet/wait --networks solana

devnet-xrpl:
docker compose -f devnet/docker-compose.yml up -d rippled
go run ./devnet/wait --networks rippled

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 and Solana deposits. See devnet/README.md.
# per chain; the TS suite covers EVM, Solana, and XRPL 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
npm --prefix sdk/ts run test:integration:xrpl
146 changes: 145 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,145 @@
# clearnet-sdk
# clearnet-sdk

SDKs and shared protocol libraries for Clearnet integrations.

This repository currently contains:

- a Go module, `github.com/layer-3/clearnet-sdk`, with core protocol types,
signing helpers, p2p helpers, and blockchain adapters;
- a TypeScript package under `sdk/ts`, published/imported as
`@yellow-org/clearnet-sdk`;
- Docker-backed local devnet tooling for Go and TypeScript integration tests.

The Go SDK is the broader backend-facing SDK. The TypeScript SDK currently
focuses on browser and application deposit flows for EVM, Solana, and XRPL.

## Repository Layout

| Path | Purpose |
|---|---|
| `pkg/core` | Shared Clearnet data types, operations, transaction references, deposit destinations, and adapter interfaces. |
| `pkg/blockchain/evm` | Go EVM adapters for vault deposits, withdrawals, signer rotation, registry/faucet/token/fraud interactions, and generated contract bindings. |
| `pkg/blockchain/sol` | Go Solana custody adapter code, program bindings, deposits, withdrawals, and signer rotation. |
| `pkg/blockchain/xrpl` | Go XRPL deposits, withdrawals, signer rotation, ticket handling, and payment wire helpers. |
| `pkg/blockchain/btc` | Go Bitcoin vault deposit, withdrawal, rotation, consolidation, and RPC helpers. |
| `pkg/decimal` | Decimal amount type used by Go chain adapters. |
| `pkg/bls`, `pkg/eip712`, `pkg/sign` | Signature and digest helpers. |
| `pkg/p2p`, `pkg/receipt`, `pkg/log` | Supporting networking, receipt, and logging packages. |
| `sdk/ts` | TypeScript SDK package, tests, and browser demos. See `sdk/ts/README.md`. |
| `devnet` | Docker Compose local blockchain devnet and readiness probe. See `devnet/README.md`. |

## Go SDK

The Go module is rooted at this repository:

```sh
go get github.com/layer-3/clearnet-sdk
```

Common entry points:

- `pkg/core`: chain-neutral interfaces such as `VaultDepositor`,
`VaultWithdrawalFinalizer`, `SignerRotationFinalizer`, `TxRef`, and
`DepositDestination`.
- `pkg/blockchain/evm`: EVM custody vault flows and generated bindings.
- `pkg/blockchain/sol`: Solana custody vault flows.
- `pkg/blockchain/xrpl`: XRPL custody vault flows.
- `pkg/blockchain/btc`: Bitcoin custody vault flows.

Run the Go checks:

```sh
make build
make lint
make test
```

Generated Go files are committed. Regenerate them after changing generation
inputs:

```sh
make generate
```

## TypeScript SDK

The TypeScript package lives in `sdk/ts` and is ESM-first.

```sh
cd sdk/ts
npm ci
npm run typecheck
npm test
npm run build
```

Install from an application:

```sh
npm install @yellow-org/clearnet-sdk
```

The package currently exposes vault depositors for:

- EVM native ETH and ERC-20 deposits;
- Solana native SOL and SPL token deposits;
- XRPL native XRP and issued-currency deposits.

Read the package guide and API examples in `sdk/ts/README.md`.

## Browser Demos

The TypeScript package includes local demo apps for manual wallet testing:

```sh
npm --prefix sdk/ts run demo:evm
npm --prefix sdk/ts run demo:sol
npm --prefix sdk/ts run demo:xrpl
```

The demos expect a local or configured chain endpoint, funded wallet accounts,
and the chain-specific wallet/browser extension needed by the demo. They are
developer aids, not production app templates.

## Devnet And Integration Tests

The local devnet runs the chain nodes used by the integration suites:

```sh
make devnet
npm --prefix sdk/ts ci
make integration
make devnet-down
```

Focused targets are available when iterating on one chain:

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

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

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

`make integration` runs the Go blockchain integrations and the TypeScript EVM,
Solana, and XRPL integration tests. See `devnet/README.md` for ports,
provisioning behavior, and environment overrides.

## Development Notes

- Use `make test` for the Go race-enabled test suite.
- Use `npm --prefix sdk/ts test` for TypeScript unit tests.
- Use `npm --prefix sdk/ts audit --omit=dev --audit-level=moderate` when
checking runtime dependency advisories for the TypeScript package.
- Keep generated files and vendored chain artifacts in sync with their source
inputs.
- Keep public SDK documentation broad: this repository supports Clearnet
integration surfaces, not only custody-specific flows.

## License

MIT. See `LICENSE`.
11 changes: 9 additions & 2 deletions devnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ the same `make integration` target.
```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 and Solana integration
make integration # Go blockchain integrations + TS EVM, Solana, and XRPL integration
make devnet-down
```

Expand All @@ -39,7 +39,10 @@ wallet, the XRPL genesis master).
- **XRPL** — funds a fresh vault + depositor from the genesis master,
`SignerListSet`s the vault over fresh signer keys, `TicketCreate`s a ticket,
then deposits and runs the quorum withdrawal. Standalone rippled does not
auto-close ledgers, so the test calls `ledger_accept` after each submit.
auto-close ledgers, so the test calls `ledger_accept` after each submit. The
TypeScript XRPL integration test creates fresh accounts, submits native XRP
and issued-currency deposits, verifies each returned transaction reference,
and asserts the `ynet-account` memo carried the deposit destination.
- **Solana** — the validator preloads the custody program **upgradeable** at its
fixed id (`--upgradeable-program`), upgrade authority = the vendored
`devnet/sol-upgrade-authority.json`. The test airdrop-funds the authority +
Expand All @@ -60,6 +63,9 @@ npm --prefix sdk/ts run test:integration:evm

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

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

## Optional overrides
Expand All @@ -71,6 +77,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` |
| `XRPL_WS_URL` / `XRPL_ADMIN_RPC_URL` | `ws://127.0.0.1:6006` / `http://127.0.0.1:5005` |
| `SOL_RPC_URL` | `http://127.0.0.1:8899` |

## Notes
Expand Down
5 changes: 5 additions & 0 deletions devnet/rippled.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ protocol = peer
[node_size]
tiny

# Keep this local id away from public Xahau ids. GemWallet and other wallets
# know 21337/21338 as Xahau mainnet/testnet.
[network_id]
31337

[node_db]
type=NuDB
path=/var/lib/rippled/db/nudb
Expand Down
37 changes: 37 additions & 0 deletions pkg/blockchain/xrpl/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package xrpl

import (
"fmt"

"github.com/Peersyst/xrpl-go/xrpl/queries/server"
"github.com/Peersyst/xrpl-go/xrpl/rpc"
)

const xrplNetworkIDRequiredAbove = 1024

func newRPCClient(rpcURL string) (*rpc.Client, error) {
cfg, err := rpc.NewClientConfig(rpcURL)
if err != nil {
return nil, fmt.Errorf("xrpl: create rpc config: %w", err)
}
return rpc.NewClient(cfg), nil
}

func ensureNetworkID(client *rpc.Client) error {
if client.NetworkID != 0 {
return nil
}
info, err := client.GetServerInfo(&server.InfoRequest{})
if err != nil {
return fmt.Errorf("xrpl: server_info: %w", err)
}
networkID := info.Info.NetworkID
if networkID <= xrplNetworkIDRequiredAbove {
return nil
}
if networkID > uint(^uint32(0)) {
return fmt.Errorf("xrpl: network_id %d overflows uint32", networkID)
}
client.NetworkID = uint32(networkID)
return nil
}
9 changes: 6 additions & 3 deletions pkg/blockchain/xrpl/depositor.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ var _ core.VaultDepositor = (*Depositor)(nil)

// NewDepositor builds the XRPL depositor against the rippled JSON-RPC at rpcURL.
func NewDepositor(rpcURL, vaultAddress string, signer sign.Signer) (*Depositor, error) {
cfg, err := rpc.NewClientConfig(rpcURL)
client, err := newRPCClient(rpcURL)
if err != nil {
return nil, fmt.Errorf("xrpl: create rpc config: %w", err)
return nil, err
}
id, err := DeriveIdentity(signer)
if err != nil {
return nil, err
}
return &Depositor{client: rpc.NewClient(cfg), vaultAddress: vaultAddress, signer: signer, id: id}, nil
return &Depositor{client: client, vaultAddress: vaultAddress, signer: signer, id: id}, nil
}

// DepositorAddress returns the depositor's classic r-address.
Expand Down Expand Up @@ -69,6 +69,9 @@ func (d *Depositor) SubmitDeposit(ctx context.Context, asset string, amount deci
Amount: xrplAmount,
}
flatTx := payment.Flatten()
if err := ensureNetworkID(d.client); err != nil {
return core.TxRef{}, err
}
if err := d.client.Autofill(&flatTx); err != nil {
return core.TxRef{}, fmt.Errorf("xrpl: autofill: %w", err)
}
Expand Down
9 changes: 6 additions & 3 deletions pkg/blockchain/xrpl/rotation_finalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,16 @@ var _ core.SignerRotationFinalizer = (*RotationFinalizer)(nil)
// current SignerQuorum (used to size the multi-sign fee and trim the quorum);
// signer is one of the current SignerList members.
func NewRotationFinalizer(rpcURL, vaultAddress string, threshold int, signer sign.Signer) (*RotationFinalizer, error) {
cfg, err := rpc.NewClientConfig(rpcURL)
client, err := newRPCClient(rpcURL)
if err != nil {
return nil, fmt.Errorf("xrpl: create rpc config: %w", err)
return nil, err
}
id, err := DeriveIdentity(signer)
if err != nil {
return nil, err
}
return &RotationFinalizer{
client: rpc.NewClient(cfg),
client: client,
vaultAddress: vaultAddress,
threshold: threshold,
signer: signer,
Expand Down Expand Up @@ -97,6 +97,9 @@ func (f *RotationFinalizer) Pack(ctx context.Context, _ [32]byte, newSigners []s
return nil, fmt.Errorf("xrpl: resolve live quorum: %w", err)
}
}
if err := ensureNetworkID(f.client); err != nil {
return nil, err
}
if err := f.client.AutofillMultisigned(&flatTx, uint64(quorum)); err != nil {
return nil, fmt.Errorf("xrpl: autofill: %w", err)
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/blockchain/xrpl/ticket.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ var _ TicketProvider = (*LedgerTicketProvider)(nil)
// NewLedgerTicketProvider builds a provider reading Tickets owned by
// vaultAddress over the JSON-RPC at rpcURL.
func NewLedgerTicketProvider(rpcURL, vaultAddress string) (*LedgerTicketProvider, error) {
cfg, err := rpc.NewClientConfig(rpcURL)
client, err := newRPCClient(rpcURL)
if err != nil {
return nil, fmt.Errorf("xrpl: create rpc config: %w", err)
return nil, err
}
return &LedgerTicketProvider{
client: rpc.NewClient(cfg),
client: client,
account: types.Address(vaultAddress),
}, nil
}
Expand Down
7 changes: 5 additions & 2 deletions pkg/blockchain/xrpl/vault_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ func TestIntegrationXRPL_DepositAndWithdraw(t *testing.T) {
defer cancel()

url := xrplEnv("XRPL_RPC_URL", defaultXRPLRPC)
cfg, err := rpc.NewClientConfig(url)
client, err := newRPCClient(url)
if err != nil {
t.Fatalf("rpc config: %v", err)
}
h := &xrplHarness{url: url, client: rpc.NewClient(cfg), http: &http.Client{Timeout: 30 * time.Second}}
h := &xrplHarness{url: url, client: client, http: &http.Client{Timeout: 30 * time.Second}}

master := masterSigner(t)
masterID := mustIdentity(t, master)
Expand Down Expand Up @@ -200,6 +200,9 @@ type xrplHarness struct {
// ledger so the tx validates before the next call reads account state.
func (h *xrplHarness) submit(ctx context.Context, t *testing.T, s sign.Signer, id Identity, flatTx transaction.FlatTransaction) {
t.Helper()
if err := ensureNetworkID(h.client); err != nil {
t.Fatalf("network id: %v", err)
}
if err := h.client.Autofill(&flatTx); err != nil {
t.Fatalf("autofill: %v", err)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/blockchain/xrpl/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const maxAcceptableFeeDrops uint64 = 1_000_000
var canonicalAllowedFields = map[string]struct{}{
"TransactionType": {}, "Account": {}, "Destination": {}, "Amount": {},
"InvoiceID": {}, "TicketSequence": {}, "Sequence": {}, "Fee": {},
"SigningPubKey": {}, "Flags": {},
"SigningPubKey": {}, "Flags": {}, "NetworkID": {},
}

// Identity is a signer's XRPL classic address + signing pubkey hex.
Expand Down Expand Up @@ -212,7 +212,7 @@ func ValidateCanonical(flat transaction.FlatTransaction, op *core.WithdrawalOp,
// canonical SignerListSet flatTx before signing.
var rotationAllowedFields = map[string]struct{}{
"TransactionType": {}, "Account": {}, "SignerQuorum": {}, "SignerEntries": {},
"Sequence": {}, "Fee": {}, "SigningPubKey": {}, "Flags": {},
"Sequence": {}, "Fee": {}, "SigningPubKey": {}, "Flags": {}, "NetworkID": {},
}

// validateCanonicalRotation asserts the canonical SignerListSet flatTx rotates
Expand Down
Loading
Loading