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
57 changes: 56 additions & 1 deletion docs/build/sdk/typescript-compat/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ await client.close();
| `closeChannel(params?)` | Close open channels (optionally for a specific token) |
| `resizeChannel({ allocate_amount, token })` | Resize an existing channel |
| `challengeChannel({ state })` | Challenge a channel on-chain |
| `acknowledge(tokenAddress)` | Acknowledge a pending state or create a channel |
| `checkTokenAllowance(chainId, tokenAddress)` | Check ERC-20 allowance for the ChannelHub |
| `createChannel()` | No-op in v1 (channel creation is implicit on `deposit()`) |

### Queries
Expand All @@ -78,6 +80,9 @@ await client.close();
| `getAssetsList()` | List supported assets |
| `getAccountInfo()` | Aggregate balance + channel count |
| `getConfig()` | Node configuration |
| `getBlockchains()` | List supported blockchains |
| `getActionAllowances(wallet?)` | Get gated action allowances for a wallet |
| `getEscrowChannel(escrowChannelId)` | Query an escrow channel by ID |

### Transfers

Expand All @@ -93,6 +98,14 @@ await client.close();
| `closeAppSession(appSessionId, allocations, quorumSigs?)` | Close an app session |
| `submitAppState(params)` | Submit state update (operate/deposit/withdraw/close) |
| `getAppDefinition(appSessionId)` | Get session definition |
| `rebalanceAppSessions(signedUpdates)` | Rebalance allocations across app sessions |

### App Registry

| Method | Description |
|--------|-------------|
| `getApps(options?)` | List registered applications (filter by appId, owner, pagination) |
| `registerApp(appID, metadata, creationApprovalNotRequired)` | Register a new application |

### App Session Signing Helpers

Expand Down Expand Up @@ -126,12 +139,24 @@ await client.close();
| `parseAmount(tokenAddress, humanAmount)` | Human-readable string → raw bigint |
| `findOpenChannel(tokenAddress, chainId?)` | Find an open channel for a given token |

### Security Token Locking

| Method | Description |
|--------|-------------|
| `lockSecurityTokens(targetWallet, chainId, amount)` | Lock tokens into the Locking contract for a target address |
| `initiateSecurityTokensWithdrawal(chainId)` | Start the unlock process for locked tokens |
| `cancelSecurityTokensWithdrawal(chainId)` | Re-lock tokens, cancelling a pending unlock |
| `withdrawSecurityTokens(chainId, destination)` | Withdraw unlocked tokens to a destination address |
| `approveSecurityToken(chainId, amount)` | Approve the Locking contract to spend tokens |
| `getLockedBalance(chainId, wallet?)` | Query locked balance (returns raw bigint) |

### Lifecycle

| Method | Description |
|--------|-------------|
| `ping()` | Health check |
| `close()` | Close the WebSocket connection |
| `waitForClose()` | Returns a promise that resolves when the connection is closed |
| `refreshAssets()` | Re-fetch the asset map from the clearnode |

## Properties
Expand Down Expand Up @@ -185,6 +210,7 @@ await v1Client.approveToken(chainId, 'usdc', amount);
| `UserRejectedError` | `USER_REJECTED` | User cancelled in wallet |
| `InsufficientFundsError` | `INSUFFICIENT_FUNDS` | Not enough balance |
| `NotInitializedError` | `NOT_INITIALIZED` | Client not connected |
| `OngoingStateTransitionError` | `ONGOING_STATE_TRANSITION` | Previous action still finalizing |

```typescript
import { getUserFacingMessage, AllowanceError } from '@yellow-org/sdk-compat';
Expand All @@ -197,7 +223,7 @@ try {
if (typed instanceof AllowanceError) {
// prompt user to approve token spending
}
showToast(getUserFacingMessage(err));
showToast(getUserFacingMessage(typed));
}
```

Expand All @@ -218,6 +244,35 @@ const poller = new EventPoller(client, {
poller.start();
```

## Security Token Locking

Lock tokens into the on-chain Locking contract to provide security deposits:

```typescript
const chainId = 11155111; // Sepolia
const amount = 100_000_000n; // 100 USDC in raw units (6 decimals)

// Approve the Locking contract to spend tokens
await client.approveSecurityToken(chainId, amount);

// Lock tokens for a target address
await client.lockSecurityTokens(targetWallet, chainId, amount);

// Query locked balance
const locked = await client.getLockedBalance(chainId);

// Initiate unlock (starts the unlock period)
await client.initiateSecurityTokensWithdrawal(chainId);

// Cancel unlock (re-lock tokens)
await client.cancelSecurityTokensWithdrawal(chainId);

// After unlock period elapses, withdraw to a destination
await client.withdrawSecurityTokens(chainId, destinationWallet);
```

All amounts use raw `bigint` units (consistent with `deposit()` and `withdrawal()`). The compat layer converts to human-readable `Decimal` values internally.

## Next.js Integration

Add to `transpilePackages` in `next.config.ts`:
Expand Down
59 changes: 59 additions & 0 deletions docs/build/sdk/typescript/api-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,65 @@ const allowance = await client.checkTokenAllowance(80002n, '0xToken...', '0xOwne

---

## Security Token Locking

Methods for interacting with the on-chain Locking (NonSlashableAppRegistry) contract. Used for staking security tokens to register apps and receive gated action allowances.
Comment on lines +151 to +153
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add the locking prerequisites to the section intro.

This reads as if the six methods are available on any chain. The new configuration docs say locking is only supported when lockingContractAddress is present, and these are still on-chain calls that need withBlockchainRPC(). A short Requires: note here would prevent copy-paste failures.

✏️ Proposed doc tweak
 ## Security Token Locking
 
 Methods for interacting with the on-chain Locking (NonSlashableAppRegistry) contract. Used for staking security tokens to register apps and receive gated action allowances.
+
+**Requires:** `withBlockchainRPC()` for the target chain, and a blockchain whose config includes `lockingContractAddress`.
📝 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
## Security Token Locking
Methods for interacting with the on-chain Locking (NonSlashableAppRegistry) contract. Used for staking security tokens to register apps and receive gated action allowances.
## Security Token Locking
Methods for interacting with the on-chain Locking (NonSlashableAppRegistry) contract. Used for staking security tokens to register apps and receive gated action allowances.
**Requires:** `withBlockchainRPC()` for the target chain, and a blockchain whose config includes `lockingContractAddress`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/build/sdk/typescript/api-reference.mdx` around lines 151 - 153, Update
the "Security Token Locking" intro to include a short "Requires:" note: state
that these on-chain NonSlashableAppRegistry methods (used for staking security
tokens and gated allowances) are available only when the SDK is configured with
lockingContractAddress and must be invoked via withBlockchainRPC(); mention both
the config key lockingContractAddress and the withBlockchainRPC() helper so
readers don't assume the methods work on any chain.


### `approveSecurityToken(chainId, amount)`

Approves the Locking contract to spend tokens. Must be called before `escrowSecurityTokens`.

```typescript
const txHash = await client.approveSecurityToken(11155111n, new Decimal(100));
```

### `escrowSecurityTokens(targetWalletAddress, blockchainId, amount)`

Locks tokens into the Locking contract for a target address.

```typescript
const txHash = await client.escrowSecurityTokens(
'0xTargetWallet...',
11155111n,
new Decimal(100),
);
```

### `getLockedBalance(chainId, wallet)`

Queries the locked balance for a wallet.

```typescript
const balance = await client.getLockedBalance(11155111n, '0xWallet...');
console.log('Locked:', balance.toString());
```

### `initiateSecurityTokensWithdrawal(blockchainId)`

Starts the unlock process. After the unlock period elapses, `withdrawSecurityTokens` can be called.

```typescript
const txHash = await client.initiateSecurityTokensWithdrawal(11155111n);
```

### `cancelSecurityTokensWithdrawal(blockchainId)`

Re-locks tokens that are in the unlocking state, cancelling the pending unlock.

```typescript
const txHash = await client.cancelSecurityTokensWithdrawal(11155111n);
```

### `withdrawSecurityTokens(blockchainId, destinationWalletAddress)`

Withdraws unlocked tokens to a destination address. Can only be called after the unlock period has elapsed.

```typescript
const txHash = await client.withdrawSecurityTokens(11155111n, '0xDestination...');
```

---

## Node Information

```typescript
Expand Down
17 changes: 17 additions & 0 deletions docs/build/sdk/typescript/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,23 @@ withErrorHandler((error) => {
})
```

### Blockchain Configuration

The node's `getConfig()` and `getBlockchains()` methods return blockchain details including contract addresses:

```typescript
const config = await client.getConfig();
for (const chain of config.blockchains) {
console.log(`${chain.name} (${chain.id})`);
console.log(` ChannelHub: ${chain.channelHubAddress}`);
if (chain.lockingContractAddress) {
console.log(` Locking: ${chain.lockingContractAddress}`);
}
}
Comment on lines +60 to +70
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Disambiguate the getConfig() and getBlockchains() return shapes.

Line 60 groups both methods together, but Lines 63-70 only show the getConfig() envelope (config.blockchains). Elsewhere the docs show getBlockchains() returning the array directly, so this wording makes it easy to copy the wrong access pattern. Either split the examples or state explicitly that getBlockchains() already returns the array.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/build/sdk/typescript/configuration.mdx` around lines 60 - 70, The docs
currently conflate the return shapes of getConfig() and getBlockchains(); update
the text and examples to disambiguate by showing two short examples: one that
calls getConfig() and accesses the array via config.blockchains (e.g., const
config = await client.getConfig(); for (const chain of config.blockchains) { ...
}) and a second that calls getBlockchains() which returns the array directly
(e.g., const blockchains = await client.getBlockchains(); for (const chain of
blockchains) { ... }), and/or add a single clarifying sentence explicitly
stating that getBlockchains() returns the blockchain array while getConfig()
returns an envelope object with a blockchains property.

```

The `lockingContractAddress` field is set on blockchains that support security token locking. When present, the `escrowSecurityTokens`, `approveSecurityToken`, and other locking methods can be used on that chain.

## Home Blockchain

`setHomeBlockchain(asset, blockchainId)` sets the default blockchain for an asset. Required before `transfer()` on a new channel (where no chain context exists yet).
Expand Down
45 changes: 45 additions & 0 deletions docs/build/sdk/typescript/examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,51 @@ async function setupSessionKey(client: Client) {
}
```

## Security Token Locking

Register an app by locking security tokens:

```typescript
import { Client, createSigners, withBlockchainRPC } from '@yellow-org/sdk';
import Decimal from 'decimal.js';

async function lockAndRegisterApp() {
const { stateSigner, txSigner } = createSigners(process.env.PRIVATE_KEY!);
const client = await Client.create(
process.env.WS_URL!,
stateSigner,
txSigner,
withBlockchainRPC(11155111n, process.env.SEPOLIA_RPC!),
);

const chainId = 11155111n;
const lockAmount = new Decimal(100);

// Approve the locking contract to spend tokens
await client.approveSecurityToken(chainId, lockAmount);

// Lock tokens for yourself
const userAddress = client.getUserAddress();
await client.escrowSecurityTokens(userAddress, chainId, lockAmount);

// Check locked balance
const balance = await client.getLockedBalance(chainId, userAddress);
console.log('Locked balance:', balance.toString());

// Register an app (requires locked tokens)
await client.registerApp('my-app', '{"name": "My App", "description": "Demo"}', false);
console.log('App registered');

// Later: initiate unlock
// await client.initiateSecurityTokensWithdrawal(chainId);

// After unlock period: withdraw
// await client.withdrawSecurityTokens(chainId, userAddress);

await client.close();
Comment on lines +358 to +391
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Close the client in a finally block.

Unlike the other examples in this file, this snippet only calls close() on the success path. Any failure during approval, escrow, balance lookup, or app registration leaves the WebSocket open in the copied example.

♻️ Proposed fix
 async function lockAndRegisterApp() {
   const { stateSigner, txSigner } = createSigners(process.env.PRIVATE_KEY!);
   const client = await Client.create(
     process.env.WS_URL!,
     stateSigner,
     txSigner,
     withBlockchainRPC(11155111n, process.env.SEPOLIA_RPC!),
   );
 
   const chainId = 11155111n;
   const lockAmount = new Decimal(100);
 
-  // Approve the locking contract to spend tokens
-  await client.approveSecurityToken(chainId, lockAmount);
-
-  // Lock tokens for yourself
-  const userAddress = client.getUserAddress();
-  await client.escrowSecurityTokens(userAddress, chainId, lockAmount);
-
-  // Check locked balance
-  const balance = await client.getLockedBalance(chainId, userAddress);
-  console.log('Locked balance:', balance.toString());
-
-  // Register an app (requires locked tokens)
-  await client.registerApp('my-app', '{"name": "My App", "description": "Demo"}', false);
-  console.log('App registered');
-
-  // Later: initiate unlock
-  // await client.initiateSecurityTokensWithdrawal(chainId);
-
-  // After unlock period: withdraw
-  // await client.withdrawSecurityTokens(chainId, userAddress);
-
-  await client.close();
+  try {
+    // Approve the locking contract to spend tokens
+    await client.approveSecurityToken(chainId, lockAmount);
+
+    // Lock tokens for yourself
+    const userAddress = client.getUserAddress();
+    await client.escrowSecurityTokens(userAddress, chainId, lockAmount);
+
+    // Check locked balance
+    const balance = await client.getLockedBalance(chainId, userAddress);
+    console.log('Locked balance:', balance.toString());
+
+    // Register an app (requires locked tokens)
+    await client.registerApp('my-app', '{"name": "My App", "description": "Demo"}', false);
+    console.log('App registered');
+
+    // Later: initiate unlock
+    // await client.initiateSecurityTokensWithdrawal(chainId);
+
+    // After unlock period: withdraw
+    // await client.withdrawSecurityTokens(chainId, userAddress);
+  } finally {
+    await client.close();
+  }
 }
📝 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
async function lockAndRegisterApp() {
const { stateSigner, txSigner } = createSigners(process.env.PRIVATE_KEY!);
const client = await Client.create(
process.env.WS_URL!,
stateSigner,
txSigner,
withBlockchainRPC(11155111n, process.env.SEPOLIA_RPC!),
);
const chainId = 11155111n;
const lockAmount = new Decimal(100);
// Approve the locking contract to spend tokens
await client.approveSecurityToken(chainId, lockAmount);
// Lock tokens for yourself
const userAddress = client.getUserAddress();
await client.escrowSecurityTokens(userAddress, chainId, lockAmount);
// Check locked balance
const balance = await client.getLockedBalance(chainId, userAddress);
console.log('Locked balance:', balance.toString());
// Register an app (requires locked tokens)
await client.registerApp('my-app', '{"name": "My App", "description": "Demo"}', false);
console.log('App registered');
// Later: initiate unlock
// await client.initiateSecurityTokensWithdrawal(chainId);
// After unlock period: withdraw
// await client.withdrawSecurityTokens(chainId, userAddress);
await client.close();
async function lockAndRegisterApp() {
const { stateSigner, txSigner } = createSigners(process.env.PRIVATE_KEY!);
const client = await Client.create(
process.env.WS_URL!,
stateSigner,
txSigner,
withBlockchainRPC(11155111n, process.env.SEPOLIA_RPC!),
);
const chainId = 11155111n;
const lockAmount = new Decimal(100);
try {
// Approve the locking contract to spend tokens
await client.approveSecurityToken(chainId, lockAmount);
// Lock tokens for yourself
const userAddress = client.getUserAddress();
await client.escrowSecurityTokens(userAddress, chainId, lockAmount);
// Check locked balance
const balance = await client.getLockedBalance(chainId, userAddress);
console.log('Locked balance:', balance.toString());
// Register an app (requires locked tokens)
await client.registerApp('my-app', '{"name": "My App", "description": "Demo"}', false);
console.log('App registered');
// Later: initiate unlock
// await client.initiateSecurityTokensWithdrawal(chainId);
// After unlock period: withdraw
// await client.withdrawSecurityTokens(chainId, userAddress);
} finally {
await client.close();
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/build/sdk/typescript/examples.mdx` around lines 358 - 391, The example
function lockAndRegisterApp currently calls client.close() only on the success
path; wrap the main flow in a try/finally so the WebSocket client is always
closed: declare the client variable outside the try, assign it via
Client.create(...) inside the try, perform approveSecurityToken,
escrowSecurityTokens, getLockedBalance, and registerApp inside the try, and call
await client.close() in the finally (checking the client is non-null) to
guarantee cleanup even on errors.

}
```

## Connection Monitoring

```typescript
Expand Down