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
75 changes: 40 additions & 35 deletions sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,26 @@ const adminKeypair = Keypair.fromSecret('SXXX...SECRET');
const result = await client.mint(
'GABCDEF...RECIPIENT',
BigInt(1000_0000000), // 1000 tokens with 7 decimals
adminKeypair
adminKeypair,
);

console.log('Mint TX:', result.hash, 'Success:', result.success);
```

## Batch Minting Tokens (Admin Only)

```typescript
const adminKeypair = Keypair.fromSecret('SXXX...SECRET');

await client.batchMint(
[
{ to: 'GABCDEF...RECIPIENT1', amount: BigInt(1000_0000000) },
{ to: 'GHIJKL...RECIPIENT2', amount: BigInt(250_0000000) },
],
adminKeypair,
);
```

## Transferring Tokens

```typescript
Expand All @@ -56,7 +70,7 @@ await client.transfer(
senderKeypair.publicKey(),
'GABCDEF...RECIPIENT',
BigInt(100_0000000),
senderKeypair
senderKeypair,
);
```

Expand All @@ -66,11 +80,7 @@ await client.transfer(
const ownerKeypair = Keypair.fromSecret('SXXX...SECRET');

// Burn 50 tokens from owner's balance
const burnResult = await client.burn(
ownerKeypair.publicKey(),
BigInt(50_0000000),
ownerKeypair
);
const burnResult = await client.burn(ownerKeypair.publicKey(), BigInt(50_0000000), ownerKeypair);
console.log('Burn TX:', burnResult.hash, 'Success:', burnResult.success);
```

Expand All @@ -82,24 +92,18 @@ await client.approve(
ownerKeypair.publicKey(),
'GSPENDER...ADDR',
BigInt(500_0000000),
ownerKeypair
ownerKeypair,
);

// Check allowance
const allowance = await client.getAllowance(
ownerKeypair.publicKey(),
'GSPENDER...ADDR'
);
const allowance = await client.getAllowance(ownerKeypair.publicKey(), 'GSPENDER...ADDR');
console.log('Allowance:', allowance);
```

## Querying Allowance

```typescript
const allowance = await client.getAllowance(
'GOWNER...ADDR',
'GSPENDER...ADDR'
);
const allowance = await client.getAllowance('GOWNER...ADDR', 'GSPENDER...ADDR');
console.log('Allowance:', allowance);
```

Expand All @@ -125,28 +129,29 @@ await client.unpause(adminKeypair);

### Read-Only Methods

| Method | Returns | Description |
|--------|---------|-------------|
| `getBalance(address)` | `bigint` | Token balance for an address |
| `getTotalSupply()` | `bigint` | Total circulating supply |
| `getName()` | `string` | Token name |
| `getSymbol()` | `string` | Token symbol |
| `getDecimals()` | `number` | Decimal places |
| `getAllowance(owner, spender)` | `bigint` | Spending allowance |
| `getVersion()` | `string` | Contract version |
| Method | Returns | Description |
| ------------------------------ | -------- | ---------------------------- |
| `getBalance(address)` | `bigint` | Token balance for an address |
| `getTotalSupply()` | `bigint` | Total circulating supply |
| `getName()` | `string` | Token name |
| `getSymbol()` | `string` | Token symbol |
| `getDecimals()` | `number` | Decimal places |
| `getAllowance(owner, spender)` | `bigint` | Spending allowance |
| `getVersion()` | `string` | Contract version |

### Write Methods (require Keypair)

| Method | Description |
|--------|-------------|
| `initialize(admin, decimals, name, symbol, source)` | One-time contract setup |
| `mint(to, amount, source)` | Mint tokens (admin-only) |
| `transfer(from, to, amount, source)` | Transfer tokens |
| `approve(from, spender, amount, source)` | Set spending allowance |
| `burn(from, amount, source)` | Burn tokens |
| `transferOwnership(newAdmin, source)` | Transfer admin role |
| `pause(source)` | Pause contract (admin-only) |
| `unpause(source)` | Unpause contract (admin-only) |
| Method | Description |
| --------------------------------------------------- | --------------------------------------------------------------------------- |
| `initialize(admin, decimals, name, symbol, source)` | One-time contract setup |
| `mint(to, amount, source)` | Mint tokens (admin-only) |
| `batchMint(recipients, source)` | Mint tokens to multiple `{ to, amount }` recipients atomically (admin-only) |
| `transfer(from, to, amount, source)` | Transfer tokens |
| `approve(from, spender, amount, source)` | Set spending allowance |
| `burn(from, amount, source)` | Burn tokens |
| `transferOwnership(newAdmin, source)` | Transfer admin role |
| `pause(source)` | Pause contract (admin-only) |
| `unpause(source)` | Unpause contract (admin-only) |

## License

Expand Down
62 changes: 44 additions & 18 deletions sdk/src/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
*/

import { bcForgeClient } from './client';
import { Keypair, Networks } from '@stellar/stellar-sdk';
import { Keypair, Networks, xdr } from '@stellar/stellar-sdk';

// Mock data for testing
const MOCK_RPC_URL = 'https://soroban-testnet.stellar.org';
const MOCK_NETWORK = Networks.TESTNET;
const MOCK_CONTRACT_ID = 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABK4I';
const MOCK_CONTRACT_ID = 'CAAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQC526';

describe('bcForgeClient Offline Transaction Builders', () => {
let client: bcForgeClient;
Expand All @@ -27,8 +27,6 @@ describe('bcForgeClient Offline Transaction Builders', () => {
it('should build an unsigned mint transaction XDR', async () => {
// This test would require mocking the RPC server
// For now, we're testing the method signature and structure
const toAddress = Keypair.random().publicKey();
const amount = BigInt(1000);

// The actual call would fail without a real RPC server
// In production, you would mock the server.getResponse
Expand All @@ -37,34 +35,63 @@ describe('bcForgeClient Offline Transaction Builders', () => {
});
});

describe('batchMint', () => {
it('should invoke batch_mint with object recipients', async () => {
const recipientA = Keypair.random().publicKey();
const recipientB = Keypair.random().publicKey();
const invokeContract = jest.fn().mockResolvedValue({
success: true,
hash: 'mock-hash',
returnValue: null,
});
(client as unknown as { invokeContract: typeof invokeContract }).invokeContract =
invokeContract;

await client.batchMint(
[
{ to: recipientA, amount: 100n },
{ to: recipientB, amount: 250n },
],
adminKeypair,
);

expect(invokeContract).toHaveBeenCalledTimes(1);
const [method, args, source] = invokeContract.mock.calls[0];
expect(method).toBe('batch_mint');
expect(args).toHaveLength(1);
expect(source).toBe(adminKeypair);

const recipientsVec = args[0] as xdr.ScVal;
const recipients = recipientsVec.vec();
if (recipients === null) {
throw new Error('Expected batch_mint argument to be an ScVal vec');
}
expect(recipients).toHaveLength(2);
const firstRecipient = recipients[0].map();
if (firstRecipient === null) {
throw new Error('Expected batch_mint recipients to be ScVal maps');
}
expect(firstRecipient[0].key().sym().toString()).toBe('address');
expect(firstRecipient[1].key().sym().toString()).toBe('amount');
});
});

describe('buildTransferTx', () => {
it('should build an unsigned transfer transaction XDR', async () => {
const fromAddress = Keypair.random().publicKey();
const toAddress = Keypair.random().publicKey();
const amount = BigInt(500);

expect(typeof client.buildTransferTx).toBe('function');
expect(client.buildTransferTx.length).toBe(4); // 4 parameters
});
});

describe('buildApproveTx', () => {
it('should build an unsigned approve transaction XDR', async () => {
const fromAddress = Keypair.random().publicKey();
const spenderAddress = Keypair.random().publicKey();
const amount = BigInt(1000);
const exp = 1000000;

expect(typeof client.buildApproveTx).toBe('function');
expect(client.buildApproveTx.length).toBe(5); // 5 parameters
});
});

describe('buildBurnTx', () => {
it('should build an unsigned burn transaction XDR', async () => {
const fromAddress = Keypair.random().publicKey();
const amount = BigInt(200);

expect(typeof client.buildBurnTx).toBe('function');
expect(client.buildBurnTx.length).toBe(3); // 3 parameters
});
Expand All @@ -74,8 +101,7 @@ describe('bcForgeClient Offline Transaction Builders', () => {
it('should sign a transaction XDR', () => {
// Create a mock unsigned transaction XDR (simplified for testing)
// In production, this would be a real XDR from buildMintTx, etc.
const mockXdr = 'AAAAAgAAAAB7NXRFP5sGdM0P6T0qMvqN0k3jTmGmZ3K7hE6m8Y1V5gAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==';


expect(typeof client.signTx).toBe('function');
expect(client.signTx.length).toBe(2); // 2 parameters
});
Expand Down
Loading
Loading