Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.14.2
24.13.0
100 changes: 63 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ yarn add @adena-wallet/sdk
Here is a basic example of how to use the SDK:

```
import { AdenaSDK, WalletProvider } from '@adena-wallet/sdk';
import { AdenaSDK, AdenaWalletProvider } from '@adena-wallet/sdk';

const walletProvider = new WalletProvider();
// `WalletProvider` is the interface; pass a concrete provider —
// `AdenaWalletProvider` for the extension, `GnoWalletProvider` /
// `GnoSocialWalletProvider` for a locally held key.
const walletProvider = new AdenaWalletProvider();
const adenaSDK = new AdenaSDK(walletProvider);

// Connect to the wallet
Expand Down Expand Up @@ -275,97 +278,115 @@ const multisigAccountParams = {
noSort: true,
};

await adena.CreateMultisigAccount(multisigAccountParams);
await adenaSDK.createMultisigAccount(multisigAccountParams);
```

### `createMultisigTransaction`

Creates a multisig transaction that can be signed by multiple signers. Returns a multisig document that should be shared with all signers.
Creates a multisig transaction that can be signed by multiple signers. Returns the unsigned
amino transaction that should be shared with all signers.

> Note: This transaction must be created from a multisig account. Make sure you have switched to the multisig account in Adena wallet before calling this method.

**Parameters:**

- chain_id: Chain identifier
- msgs: Array of transaction messages
- fee: Transaction fee configuration
- messages: Array of transaction messages
- fee: `{ gasFee, gasWanted }`, both as strings (`gasFee` is a `<amount><denom>` coin)
- memo (Optional): Transaction memo
- networkInfo (Optional): `{ chainId, rpcUrl }` to pin the transaction to a specific network

**Example:**

```typescript
const multisigTxParams = {
chain_id: "staging",
msgs: [
messages: [
{
type: "/vm.m_call",
value: {
caller: "g1multisigaddress...",
send: "",
max_deposit: "",
pkg_path: "gno.land/r/gnoland/wugnot",
func: "Approve",
args: ["g1recipient...", "1000"],
},
},
],
fee: {
amount: [
{
amount: "6113",
denom: "ugnot",
},
],
gas: "6112955",
gasFee: "6113ugnot",
gasWanted: "6112955",
},
memo: "",
};

const response = await adena.CreateMultisigTransaction(multisigTxParams);

const multisigDocument = response.data;
const response = await adenaSDK.createMultisigTransaction(multisigTxParams);

// response.data structure:
// {
// tx: {
// msgs: [...],
// msg: [...],
// fee: { gas_wanted: "6112955", gas_fee: "6113ugnot" },
// signatures: null,
// memo: ""
// },
// chainId: "staging",
// accountNumber: "5315",
// sequence: "2"
// }
// }
```

The signing methods take a multisig document — the transaction plus the account context it
is signed against. Adena no longer returns that context from `createMultisigTransaction`, so
assemble it from `getAccount` and `getNetwork`:

```typescript
const account = await adenaSDK.getAccount();
const network = await adenaSDK.getNetwork();

const multisigDocument = {
tx: response.data.tx,
chainId: network.data.chainId,
accountNumber: account.data.accountNumber,
sequence: account.data.sequence,
};
```

### `signMultisigTransaction`

Signs a multisig transaction with the current account. This method adds your signature to an existing multisig transaction document.

**Parameters:**

- multisigDocument: from createMultisigTransaction or previous signer's result
- multisigDocument: `{ tx, chainId, accountNumber, sequence }`
- multisigSignatures (Optional): only for 2nd signer onwards, from previous signer's result

**Example:**

```typescript
// Signer 1: Received multisigDocument from createMultisigTransaction
const signer1Response = await adena.SignMultisigTransaction(multisigDocument);
const signer1Response = await adenaSDK.signMultisigTransaction({ multisigDocument });

// Signer 2: Received result from Signer 1
const { multisigDocument, multisigSignatures } = signer1Response.result;
const { multisigDocument: doc2, multisigSignatures: sigs2 } = signer1Response.data.result;

const signer2Response = await adena.SignMultisigTransaction(
multisigDocument,
multisigSignatures
);
const signer2Response = await adenaSDK.signMultisigTransaction({
multisigDocument: doc2,
multisigSignatures: sigs2,
});

// Signer 3: Received result from Signer 2
const { multisigDocument: doc3, multisigSignatures: sigs3 } =
signer2Response.result;
const { multisigDocument: doc3, multisigSignatures: sigs3 } = signer2Response.data.result;

const signer3Response = await adena.SignMultisigTransaction(doc3, sigs3);
const signer3Response = await adenaSDK.signMultisigTransaction({
multisigDocument: doc3,
multisigSignatures: sigs3,
});
```

Each collected signature is an amino secp256k1 signature:

```typescript
// {
// pub_key: { "@type": "/tm.PubKeySecp256k1", value: "A1b2..." },
// signature: "c3D4..."
// }
```

### `broadcastMultisigTransaction`
Expand All @@ -383,9 +404,9 @@ Broadcasts a multisig transaction once enough signatures have been collected to

```typescript
// Received result from the final signer (when threshold is met)
const { multisigDocument, multisigSignatures } = finalSignerResponse.result;
const { multisigDocument, multisigSignatures } = finalSignerResponse.data.result;

await adena.BroadcastMultisigTransaction(multisigDocument, multisigSignatures);
await adenaSDK.broadcastMultisigTransaction({ multisigDocument, multisigSignatures });
```

## Utility Functions
Expand All @@ -398,6 +419,11 @@ Generate transaction data.
- `makeAddPackageMessage`: Generate a `AddPackage` of vm transaction message.
- `makeMsgCallMessage`: Generate a `MsgCall` of vm transaction message.
- `makeMsgRunMessage`: Generates a `MsgRun` of vm transaction message.
- `makeMsgEnablePackageMessage`: Generates a `MsgEnablePackage` of vm transaction message.
- `makeMsgRejectPackageMessage`: Generates a `MsgRejectPackage` of vm transaction message.
- `makeMsgCreateSessionMessage`: Generates a `MsgCreateSession` of auth transaction message.
- `makeMsgRevokeSessionMessage`: Generates a `MsgRevokeSession` of auth transaction message.
- `makeMsgRevokeAllSessionsMessage`: Generates a `MsgRevokeAllSessions` of auth transaction message.

**Example:**

Expand All @@ -417,7 +443,7 @@ const tx = TransactionBuilder.create()

## Development Setup

The Node.js version is 18.14.2.
The Node.js version is 24.13.0.
We recommend using [nvm](https://github.com/nvm-sh/nvm).

```bash
Expand Down
9 changes: 6 additions & 3 deletions packages/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ yarn add @adena-wallet/sdk
Here is a basic example of how to use the SDK:

```
import { AdenaSDK, WalletProvider } from '@adena-wallet/sdk';
import { AdenaSDK, AdenaWalletProvider } from '@adena-wallet/sdk';

const walletProvider = new WalletProvider();
// `WalletProvider` is the interface; pass a concrete provider —
// `AdenaWalletProvider` for the extension, `GnoWalletProvider` /
// `GnoSocialWalletProvider` for a locally held key.
const walletProvider = new AdenaWalletProvider();
const adenaSDK = new AdenaSDK(walletProvider);

// Connect to the wallet
Expand Down Expand Up @@ -238,7 +241,7 @@ adenaSDK.onChangeNetwork({

## Development Setup

The Node.js version is 18.14.2.
The Node.js version is 24.13.0.
We recommend using [nvm](https://github.com/nvm-sh/nvm).

```bash
Expand Down
33 changes: 28 additions & 5 deletions packages/sdk/jest.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,33 @@
"preset": "ts-jest",
"testEnvironment": "node",
"transform": {
"^.+\\.(ts|tsx)$": "ts-jest"
"^.+\\.(ts|tsx|js|jsx|mjs)$": [
"ts-jest",
{
"tsconfig": "<rootDir>/tsconfig.spec.json"
}
]
},
"testMatch": ["<rootDir>/**/*.test.(ts|tsx)"],
"moduleFileExtensions": ["ts", "tsx", "js", "jsx"],
"moduleDirectories": ["node_modules", "<rootDir>/packages"],
"modulePathIgnorePatterns": ["bin", "node_modules", "coverage", "__mocks__"]
"transformIgnorePatterns": [
"node_modules/(?!.*(?:uuid|readonly-date-esm|@noble/|@scure/))"
],
"testMatch": [
"<rootDir>/**/*.test.(ts|tsx)"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx"
],
"moduleDirectories": [
"node_modules",
"<rootDir>/packages"
],
"modulePathIgnorePatterns": [
"bin",
"node_modules",
"coverage",
"__mocks__"
]
}
14 changes: 4 additions & 10 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,15 @@
"test:ci": "jest --coverage --passWithNoTests "
},
"dependencies": {
"@gnolang/gno-js-client": "1.4.5",
"@gnolang/tm2-js-client": "1.3.3",
"@noble/curves": "1.9.4",
"@noble/hashes": "1.8.0",
"@scure/bip32": "1.7.0",
"@scure/bip39": "1.6.0",
"@bufbuild/protobuf": "2.15.0",
"@gnolang/gno-js-client": "3.1.0",
"@gnolang/tm2-js-client": "3.3.0",
"@web3auth/auth-adapter": "9.5.3",
"@web3auth/base": "9.5.3",
"@web3auth/base-provider": "9.5.3",
"@web3auth/no-modal": "9.5.3",
"@web3auth/openlogin-adapter": "8.12.4",
"ethereum-cryptography": "3.2.0"
"@web3auth/no-modal": "9.5.3"
},
"devDependencies": {
"@bufbuild/protobuf": "2.2.3",
"@types/eslint": "9",
"@types/jest": "29.5.12",
"@types/node": "22.0.0",
Expand Down
16 changes: 10 additions & 6 deletions packages/sdk/src/core/__tests__/providers/connect-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { NetworkInfo } from '../../types';
import { JSONRPCProvider, Wallet as TM2Wallet } from '@gnolang/tm2-js-client';
import { GNO_ADDRESS_PREFIX } from '../../constants/chains.constant';

// Mock JSONRPCProvider to test without a real network connection
// Mock JSONRPCProvider to test without a real network connection. Since
// tm2-js-client 3.x the constructor is protected and instances are built
// through the async `create` factory.
jest.mock('@gnolang/tm2-js-client', () => ({
...jest.requireActual('@gnolang/tm2-js-client'),
JSONRPCProvider: jest.fn(),
JSONRPCProvider: { create: jest.fn() },
}));

describe('GnoWalletProvider.connectProvider', () => {
Expand Down Expand Up @@ -36,17 +38,19 @@ describe('GnoWalletProvider.connectProvider', () => {

// Test the normal Provider connection case
it('should connect provider with correct rpcUrl', async () => {
const result = provider['connectProvider']();
const result = await provider['connectProvider']();

expect(JSONRPCProvider).toHaveBeenCalledWith(TEST_RPC_URL);
expect(JSONRPCProvider.create).toHaveBeenCalledWith(TEST_RPC_URL);
expect(mockWallet.connect).toHaveBeenCalled();
expect(result).toBe(true);
});

// Test the exception case where the wallet is not set up
it('should return false when wallet is not set', () => {
it('should return false when wallet is not set', async () => {
provider['wallet'] = null;
const result = provider['connectProvider']();
const result = await provider['connectProvider']();

expect(JSONRPCProvider.create).not.toHaveBeenCalled();
expect(result).toBe(false);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ describe('GnoWalletProvider.onChange', () => {

beforeEach(() => {
provider = new GnoWalletProvider(undefined, mockNetworks);
jest.spyOn(provider as unknown as { connectProvider(): boolean }, 'connectProvider').mockReturnValue(true);
jest.spyOn(provider as unknown as { setNetwork(network: NetworkInfo): void }, 'setNetwork');
jest
.spyOn(provider as unknown as { connectProvider(): Promise<boolean> }, 'connectProvider')
.mockResolvedValue(true);
jest.spyOn(provider as unknown as { setNetwork(network: NetworkInfo): Promise<void> }, 'setNetwork');
});

afterEach(() => {
Expand Down Expand Up @@ -76,12 +78,12 @@ describe('GnoWalletProvider.onChange', () => {
});

// To ensure that the callback system works in real-world network change situations.
it('should notify when network actually changes', () => {
it('should notify when network actually changes', async () => {
const mockCallback = jest.fn();
provider.onChangeNetwork({ callback: mockCallback });

const newNetwork = mockNetworks[1];
(provider as unknown as { setNetwork(network: NetworkInfo): void }).setNetwork(newNetwork);
await (provider as unknown as { setNetwork(network: NetworkInfo): Promise<void> }).setNetwork(newNetwork);

expect(mockCallback).toHaveBeenCalledWith(newNetwork.chainId);
expect(mockCallback).toHaveBeenCalledTimes(1);
Expand Down
8 changes: 4 additions & 4 deletions packages/sdk/src/core/__tests__/providers/set-network.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ describe('GnoWalletProvider.setNetwork', () => {
});

// Validate the currentNetwork setting
it('should set currentNetwork', () => {
provider['setNetwork'](mockNetwork);
it('should set currentNetwork', async () => {
await provider['setNetwork'](mockNetwork);
expect(provider['currentNetwork']).toBe(mockNetwork);
});

// Validate network callback calls
it('should trigger network callback with chainId', () => {
provider['setNetwork'](mockNetwork);
it('should trigger network callback with chainId', async () => {
await provider['setNetwork'](mockNetwork);
expect(provider['triggerNetworkCallback']).toHaveBeenCalledWith(mockNetwork.chainId);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ describe('GnoWalletProvider.switchNetwork', () => {

beforeEach(() => {
provider = new GnoWalletProvider(undefined, mockNetworks);
jest.spyOn(provider as unknown as { connectProvider(): boolean }, 'connectProvider').mockReturnValue(true);
jest.spyOn(provider as unknown as { setNetwork(network: NetworkInfo): void }, 'setNetwork');
jest
.spyOn(provider as unknown as { connectProvider(): Promise<boolean> }, 'connectProvider')
.mockResolvedValue(true);
jest.spyOn(provider as unknown as { setNetwork(network: NetworkInfo): Promise<void> }, 'setNetwork');
});

afterEach(() => {
Expand Down
Loading
Loading