@suigar/mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -0
- package/dist/bin.cjs +11 -0
- package/dist/bin.d.cts +1 -0
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +12 -0
- package/dist/client.cjs +46 -0
- package/dist/client.d.cts +17 -0
- package/dist/client.d.mts +17 -0
- package/dist/client.mjs +43 -0
- package/dist/coin.cjs +86 -0
- package/dist/coin.d.cts +35 -0
- package/dist/coin.d.mts +35 -0
- package/dist/coin.mjs +86 -0
- package/dist/config.cjs +183 -0
- package/dist/config.d.cts +15 -0
- package/dist/config.d.mts +15 -0
- package/dist/config.mjs +174 -0
- package/dist/index.cjs +53 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +9 -0
- package/dist/mcp-support.cjs +62 -0
- package/dist/mcp-support.d.cts +16 -0
- package/dist/mcp-support.d.mts +16 -0
- package/dist/mcp-support.mjs +60 -0
- package/dist/metadata.cjs +51 -0
- package/dist/metadata.d.cts +52 -0
- package/dist/metadata.d.mts +52 -0
- package/dist/metadata.mjs +47 -0
- package/dist/server.cjs +433 -0
- package/dist/server.d.cts +73 -0
- package/dist/server.d.mts +73 -0
- package/dist/server.mjs +431 -0
- package/dist/tools.cjs +617 -0
- package/dist/tools.d.cts +158 -0
- package/dist/tools.d.mts +158 -0
- package/dist/tools.mjs +608 -0
- package/dist/transactions.cjs +294 -0
- package/dist/transactions.d.cts +40 -0
- package/dist/transactions.d.mts +40 -0
- package/dist/transactions.mjs +286 -0
- package/dist/types.d.cts +111 -0
- package/dist/types.d.mts +111 -0
- package/node_modules/@suigar/currency-registry/dist/index.cjs +121 -0
- package/node_modules/@suigar/currency-registry/dist/index.d.cts +50 -0
- package/node_modules/@suigar/currency-registry/dist/index.d.mts +50 -0
- package/node_modules/@suigar/currency-registry/dist/index.mjs +110 -0
- package/node_modules/@suigar/currency-registry/package.json +31 -0
- package/node_modules/@suigar/game-registry/dist/index.cjs +310 -0
- package/node_modules/@suigar/game-registry/dist/index.d.cts +65 -0
- package/node_modules/@suigar/game-registry/dist/index.d.mts +65 -0
- package/node_modules/@suigar/game-registry/dist/index.mjs +292 -0
- package/node_modules/@suigar/game-registry/package.json +31 -0
- package/node_modules/@suigar/sui-rpc-pool/dist/index.cjs +45590 -0
- package/node_modules/@suigar/sui-rpc-pool/dist/index.d.cts +465 -0
- package/node_modules/@suigar/sui-rpc-pool/dist/index.d.mts +465 -0
- package/node_modules/@suigar/sui-rpc-pool/dist/index.mjs +45570 -0
- package/node_modules/@suigar/sui-rpc-pool/package.json +31 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# `@suigar/mcp`
|
|
2
|
+
|
|
3
|
+
Lightweight reusable Suigar helpers for Sui casino flows.
|
|
4
|
+
|
|
5
|
+
It includes:
|
|
6
|
+
|
|
7
|
+
- read helpers for Suigar config, game metadata, and configured currencies
|
|
8
|
+
- SDK-backed transaction builders for `coinflip`, `limbo`, `plinko`, `wheel`, `range`, and `pvp-coinflip`
|
|
9
|
+
- shared support metadata that marks available MCP tools and currently unsupported games
|
|
10
|
+
- beta-aligned partner attribution for supported on-chain builders
|
|
11
|
+
- an MCP stdio server entrypoint for tool-based usage
|
|
12
|
+
- build and dry-run support without private keys
|
|
13
|
+
|
|
14
|
+
## Defaults
|
|
15
|
+
|
|
16
|
+
- network defaults to `testnet`
|
|
17
|
+
- deployed package ids, supported coins, registries, and price info defaults come from `@suigar/sdk`
|
|
18
|
+
- config resolution still accepts existing `SUIGAR_*` and frontend `VITE_*` keys as overrides for repo/test workflows
|
|
19
|
+
- transaction building never signs or executes transactions
|
|
20
|
+
- `partner` follows the beta SDK model: pass it once per build input and do not set `metadata.partner` or `metadata.referrer` manually
|
|
21
|
+
|
|
22
|
+
## Supported env keys
|
|
23
|
+
|
|
24
|
+
The package reads either `SUIGAR_*` or `VITE_*` values.
|
|
25
|
+
|
|
26
|
+
- `SUIGAR_NETWORK` / `VITE_NETWORK`
|
|
27
|
+
- `SUIGAR_PACKAGE_ID` / `VITE_SUIGAR_PACKAGE_ID`
|
|
28
|
+
- `COINFLIP_PACKAGE_ID` / `VITE_COINFLIP_PACKAGE_ID`
|
|
29
|
+
- `PVP_COINFLIP_PACKAGE_ID` / `VITE_PVP_COINFLIP_PACKAGE_ID`
|
|
30
|
+
- `PLINKO_PACKAGE_ID` / `VITE_PLINKO_PACKAGE_ID`
|
|
31
|
+
- `LIMBO_PACKAGE_ID` / `VITE_LIMBO_PACKAGE_ID`
|
|
32
|
+
- `RANGE_PACKAGE_ID` / `VITE_RANGE_PACKAGE_ID`
|
|
33
|
+
- `WHEEL_PACKAGE_ID` / `VITE_WHEEL_PACKAGE_ID`
|
|
34
|
+
- `SWEETHOUSE_ID` / `VITE_SWEETHOUSE_ID`
|
|
35
|
+
- `SUI_COIN_TYPE` / `VITE_SUI_COIN_TYPE`
|
|
36
|
+
- `USDC_COIN_TYPE` / `VITE_USDC_COIN_TYPE`
|
|
37
|
+
- `SUI_PYTH_PRICE_INFO_OBJECT_ID` / `VITE_SUI_PYTH_PRICE_INFO_OBJECT_ID`
|
|
38
|
+
- `USDC_PYTH_PRICE_INFO_OBJECT_ID` / `VITE_USDC_PYTH_PRICE_INFO_OBJECT_ID`
|
|
39
|
+
|
|
40
|
+
## Library usage
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { buildCoinflipTransaction, createReadOnlyClientBundle, resolveSuigarConfig, serializeTransactionToBase64 } from '@suigar/mcp';
|
|
44
|
+
|
|
45
|
+
const bundle = createReadOnlyClientBundle({ network: 'testnet' });
|
|
46
|
+
|
|
47
|
+
const tx = await buildCoinflipTransaction({
|
|
48
|
+
client: bundle.client,
|
|
49
|
+
config: resolveSuigarConfig({ network: 'testnet' }),
|
|
50
|
+
owner: '0xabc...',
|
|
51
|
+
coinType: '0x2::sui::SUI',
|
|
52
|
+
stake: 1_000_000_000,
|
|
53
|
+
side: 'heads',
|
|
54
|
+
partner: '0xpartner_wallet_address',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const bytes = await serializeTransactionToBase64(tx, bundle.rawClient);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Partner attribution
|
|
61
|
+
|
|
62
|
+
The beta SDK moved partner attribution into extension-level configuration instead of per-call metadata patching.
|
|
63
|
+
|
|
64
|
+
`@suigar/mcp` follows the same rule:
|
|
65
|
+
|
|
66
|
+
- pass `partner` as a top-level builder input when you need attribution onchain
|
|
67
|
+
- do not pass `metadata.partner` or `metadata.referrer`
|
|
68
|
+
- reserved attribution keys are ignored from manual metadata input
|
|
69
|
+
|
|
70
|
+
This applies to both the exported transaction builders and the MCP tools.
|
|
71
|
+
|
|
72
|
+
## MCP server
|
|
73
|
+
|
|
74
|
+
For a packaged install, point your MCP client at the package bin:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"mcpServers": {
|
|
79
|
+
"suigar": {
|
|
80
|
+
"command": "npx",
|
|
81
|
+
"args": ["-y", "@suigar/mcp"]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Because the package carries SDK-backed testnet/mainnet defaults, a normal install does not need to source this repository's `.env.testnet.shared`.
|
|
88
|
+
|
|
89
|
+
When packed, the MCP package bundles its private internal runtime packages, so consumers do not need repo-relative `file:` dependencies.
|
|
90
|
+
|
|
91
|
+
Run the workspace stdio server while developing this package:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm run -w packages/mcp build
|
|
95
|
+
node packages/mcp/dist/bin.mjs
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Available tools:
|
|
99
|
+
|
|
100
|
+
- `read_config`
|
|
101
|
+
- `read_game_metadata`
|
|
102
|
+
- `build_coinflip_transaction`
|
|
103
|
+
- `build_limbo_transaction`
|
|
104
|
+
- `build_plinko_transaction`
|
|
105
|
+
- `build_wheel_transaction`
|
|
106
|
+
- `build_range_transaction`
|
|
107
|
+
- `build_pvp_coinflip_create_transaction`
|
|
108
|
+
- `build_pvp_coinflip_join_transaction`
|
|
109
|
+
- `build_pvp_coinflip_cancel_transaction`
|
|
110
|
+
|
|
111
|
+
Tool modes:
|
|
112
|
+
|
|
113
|
+
- `build`: returns a serialized transaction plus a summary
|
|
114
|
+
- `dry-run`: simulates the transaction on-chain with a read-only client
|
|
115
|
+
- `read-only`: returns resolved config and a transaction plan without building
|
|
116
|
+
|
|
117
|
+
For supported on-chain builders, you can also pass:
|
|
118
|
+
|
|
119
|
+
- `partner`: partner wallet address for beta-style attribution injection
|
|
120
|
+
- `metadata`: custom metadata, excluding reserved attribution keys
|
|
121
|
+
|
|
122
|
+
`read_config` and `read_game_metadata` include MCP support fields so agents can distinguish:
|
|
123
|
+
|
|
124
|
+
- `executionSurface: "onchain" | "backend"`
|
|
125
|
+
- `toolSupported: boolean`
|
|
126
|
+
- `primaryToolName: string | null`
|
|
127
|
+
|
|
128
|
+
Slots are intentionally disabled in the MCP package for now. They may still appear in game metadata as backend-driven, but `toolSupported` is `false` and no slots tool is exposed.
|
|
129
|
+
|
|
130
|
+
## Testnet smoke
|
|
131
|
+
|
|
132
|
+
Use the repo-level smoke command to validate the MCP through the MCP protocol with managed wallets:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm run smoke:mcp:testnet
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
This smoke path loads `.env.testnet.shared`, starts the MCP stdio server, validates read tools, runs dry-runs, and executes testnet transactions for the on-chain builders.
|
|
139
|
+
|
|
140
|
+
## Use With Codex
|
|
141
|
+
|
|
142
|
+
The repo includes a local skill scaffold at `.codex/skills/mcp/`.
|
|
143
|
+
|
|
144
|
+
Install it into Codex by copying or symlinking it into `$CODEX_HOME/skills`:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
mkdir -p "${CODEX_HOME:-$HOME/.codex}/skills"
|
|
148
|
+
ln -s "$(pwd)/.codex/skills/mcp" "${CODEX_HOME:-$HOME/.codex}/skills/mcp"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
If you prefer a copy instead of a symlink:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
mkdir -p "${CODEX_HOME:-$HOME/.codex}/skills"
|
|
155
|
+
cp -R .codex/skills/mcp "${CODEX_HOME:-$HOME/.codex}/skills/mcp"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Notes
|
|
159
|
+
|
|
160
|
+
- The gas shortcut only applies to native `0x2::sui::SUI`. Configured gameplay coins such as testnet `TEST_SUI` must come from owned coin objects.
|
|
161
|
+
- For normal packaged use, SDK transaction builders use Sui transaction coin selection.
|
|
162
|
+
- For explicit object-id coin sourcing, MCP falls back to its local compatibility builders because the public SDK builder API does not expose `coinObjectIds`.
|
|
163
|
+
- For fully pure local builds, pass `coinSource: { kind: 'object-ids', objectIds: [...] }`.
|
|
164
|
+
- No private key is required for building or dry-running.
|
package/dist/bin.cjs
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const require_server = require("./server.cjs");
|
|
3
|
+
//#region src/bin.ts
|
|
4
|
+
const main = async () => {
|
|
5
|
+
await require_server.startSuigarMcpServer();
|
|
6
|
+
};
|
|
7
|
+
main().catch((error) => {
|
|
8
|
+
console.error(error);
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
});
|
|
11
|
+
//#endregion
|
package/dist/bin.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/bin.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/bin.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { startSuigarMcpServer } from "./server.mjs";
|
|
3
|
+
//#region src/bin.ts
|
|
4
|
+
const main = async () => {
|
|
5
|
+
await startSuigarMcpServer();
|
|
6
|
+
};
|
|
7
|
+
main().catch((error) => {
|
|
8
|
+
console.error(error);
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
});
|
|
11
|
+
//#endregion
|
|
12
|
+
export {};
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const require_config = require("./config.cjs");
|
|
2
|
+
let _suigar_sui_rpc_pool = require("@suigar/sui-rpc-pool");
|
|
3
|
+
//#region src/client.ts
|
|
4
|
+
const createReadOnlyClientBundle = (configInput = {}) => {
|
|
5
|
+
const config = require_config.resolveSuigarConfig(configInput);
|
|
6
|
+
const runtime = (0, _suigar_sui_rpc_pool.getOrCreateResilientSuiClientRuntime)({
|
|
7
|
+
network: config.network,
|
|
8
|
+
providerUrl: config.providerUrl
|
|
9
|
+
});
|
|
10
|
+
return {
|
|
11
|
+
config,
|
|
12
|
+
runtime,
|
|
13
|
+
client: runtime.createCompatClient(),
|
|
14
|
+
rawClient: runtime.getGrpcClient()
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
const serializeTransactionToBase64 = async (transaction, client) => {
|
|
18
|
+
const bytes = await transaction.build({ client });
|
|
19
|
+
return Buffer.from(bytes).toString("base64");
|
|
20
|
+
};
|
|
21
|
+
const sanitizeForJson = (value) => {
|
|
22
|
+
if (typeof value === "bigint") return value.toString();
|
|
23
|
+
if (value instanceof Uint8Array) return Buffer.from(value).toString("base64");
|
|
24
|
+
if (Array.isArray(value)) return value.map((entry) => sanitizeForJson(entry));
|
|
25
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, sanitizeForJson(entry)]));
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
const dryRunTransaction = async (transaction, configInput = {}) => {
|
|
29
|
+
const { rawClient } = createReadOnlyClientBundle(configInput);
|
|
30
|
+
const bytes = await transaction.build({ client: rawClient });
|
|
31
|
+
return sanitizeForJson(await rawClient.simulateTransaction({
|
|
32
|
+
transaction: bytes,
|
|
33
|
+
include: {
|
|
34
|
+
effects: true,
|
|
35
|
+
events: true,
|
|
36
|
+
balanceChanges: true,
|
|
37
|
+
objectChanges: true,
|
|
38
|
+
input: true
|
|
39
|
+
}
|
|
40
|
+
}));
|
|
41
|
+
};
|
|
42
|
+
//#endregion
|
|
43
|
+
exports.createReadOnlyClientBundle = createReadOnlyClientBundle;
|
|
44
|
+
exports.dryRunTransaction = dryRunTransaction;
|
|
45
|
+
exports.sanitizeForJson = sanitizeForJson;
|
|
46
|
+
exports.serializeTransactionToBase64 = serializeTransactionToBase64;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SuigarConfig, SuigarConfigInput } from "./types.cjs";
|
|
2
|
+
import { SuiCompatClient } from "@suigar/sui-rpc-pool";
|
|
3
|
+
import { Transaction } from "@mysten/sui/transactions";
|
|
4
|
+
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
5
|
+
|
|
6
|
+
//#region src/client.d.ts
|
|
7
|
+
declare const createReadOnlyClientBundle: (configInput?: SuigarConfigInput) => {
|
|
8
|
+
config: SuigarConfig;
|
|
9
|
+
runtime: import("@suigar/sui-rpc-pool").ResilientSuiClientRuntime;
|
|
10
|
+
client: SuiCompatClient;
|
|
11
|
+
rawClient: SuiGrpcClient;
|
|
12
|
+
};
|
|
13
|
+
declare const serializeTransactionToBase64: (transaction: Transaction, client: SuiGrpcClient) => Promise<string>;
|
|
14
|
+
declare const sanitizeForJson: (value: unknown) => unknown;
|
|
15
|
+
declare const dryRunTransaction: (transaction: Transaction, configInput?: SuigarConfigInput) => Promise<unknown>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { createReadOnlyClientBundle, dryRunTransaction, sanitizeForJson, serializeTransactionToBase64 };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SuigarConfig, SuigarConfigInput } from "./types.mjs";
|
|
2
|
+
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
3
|
+
import { SuiCompatClient } from "@suigar/sui-rpc-pool";
|
|
4
|
+
import { Transaction } from "@mysten/sui/transactions";
|
|
5
|
+
|
|
6
|
+
//#region src/client.d.ts
|
|
7
|
+
declare const createReadOnlyClientBundle: (configInput?: SuigarConfigInput) => {
|
|
8
|
+
config: SuigarConfig;
|
|
9
|
+
runtime: import("@suigar/sui-rpc-pool").ResilientSuiClientRuntime;
|
|
10
|
+
client: SuiCompatClient;
|
|
11
|
+
rawClient: SuiGrpcClient;
|
|
12
|
+
};
|
|
13
|
+
declare const serializeTransactionToBase64: (transaction: Transaction, client: SuiGrpcClient) => Promise<string>;
|
|
14
|
+
declare const sanitizeForJson: (value: unknown) => unknown;
|
|
15
|
+
declare const dryRunTransaction: (transaction: Transaction, configInput?: SuigarConfigInput) => Promise<unknown>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { createReadOnlyClientBundle, dryRunTransaction, sanitizeForJson, serializeTransactionToBase64 };
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { resolveSuigarConfig } from "./config.mjs";
|
|
2
|
+
import { getOrCreateResilientSuiClientRuntime } from "@suigar/sui-rpc-pool";
|
|
3
|
+
//#region src/client.ts
|
|
4
|
+
const createReadOnlyClientBundle = (configInput = {}) => {
|
|
5
|
+
const config = resolveSuigarConfig(configInput);
|
|
6
|
+
const runtime = getOrCreateResilientSuiClientRuntime({
|
|
7
|
+
network: config.network,
|
|
8
|
+
providerUrl: config.providerUrl
|
|
9
|
+
});
|
|
10
|
+
return {
|
|
11
|
+
config,
|
|
12
|
+
runtime,
|
|
13
|
+
client: runtime.createCompatClient(),
|
|
14
|
+
rawClient: runtime.getGrpcClient()
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
const serializeTransactionToBase64 = async (transaction, client) => {
|
|
18
|
+
const bytes = await transaction.build({ client });
|
|
19
|
+
return Buffer.from(bytes).toString("base64");
|
|
20
|
+
};
|
|
21
|
+
const sanitizeForJson = (value) => {
|
|
22
|
+
if (typeof value === "bigint") return value.toString();
|
|
23
|
+
if (value instanceof Uint8Array) return Buffer.from(value).toString("base64");
|
|
24
|
+
if (Array.isArray(value)) return value.map((entry) => sanitizeForJson(entry));
|
|
25
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, sanitizeForJson(entry)]));
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
const dryRunTransaction = async (transaction, configInput = {}) => {
|
|
29
|
+
const { rawClient } = createReadOnlyClientBundle(configInput);
|
|
30
|
+
const bytes = await transaction.build({ client: rawClient });
|
|
31
|
+
return sanitizeForJson(await rawClient.simulateTransaction({
|
|
32
|
+
transaction: bytes,
|
|
33
|
+
include: {
|
|
34
|
+
effects: true,
|
|
35
|
+
events: true,
|
|
36
|
+
balanceChanges: true,
|
|
37
|
+
objectChanges: true,
|
|
38
|
+
input: true
|
|
39
|
+
}
|
|
40
|
+
}));
|
|
41
|
+
};
|
|
42
|
+
//#endregion
|
|
43
|
+
export { createReadOnlyClientBundle, dryRunTransaction, sanitizeForJson, serializeTransactionToBase64 };
|
package/dist/coin.cjs
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
let _suigar_currency_registry = require("@suigar/currency-registry");
|
|
2
|
+
let _mysten_sui_utils = require("@mysten/sui/utils");
|
|
3
|
+
let _suigar_sdk_utils = require("@suigar/sdk/utils");
|
|
4
|
+
//#region src/coin.ts
|
|
5
|
+
const DEFAULT_PAGE_LIMIT = 50;
|
|
6
|
+
const NETWORK_GAS_COIN_TYPE = (0, _suigar_currency_registry.normalizeCoinType)(_mysten_sui_utils.SUI_TYPE_ARG);
|
|
7
|
+
const mergeCoinObjects = (tx, coinIds) => {
|
|
8
|
+
if (coinIds.length === 0) throw new Error("No coin objects available");
|
|
9
|
+
const primaryCoin = tx.object(coinIds[0]);
|
|
10
|
+
if (coinIds.length > 1) tx.mergeCoins(primaryCoin, coinIds.slice(1).map((coinId) => tx.object(coinId)));
|
|
11
|
+
return primaryCoin;
|
|
12
|
+
};
|
|
13
|
+
const selectCoinsForAmount = async ({ client, owner, coinType, requiredAmount, pageLimit = DEFAULT_PAGE_LIMIT }) => {
|
|
14
|
+
if (requiredAmount <= 0n) return [];
|
|
15
|
+
let cursor = null;
|
|
16
|
+
let total = 0n;
|
|
17
|
+
const coins = [];
|
|
18
|
+
const normalizedCoinType = (0, _suigar_currency_registry.normalizeCoinType)(coinType);
|
|
19
|
+
do {
|
|
20
|
+
const response = await client.getCoins({
|
|
21
|
+
owner,
|
|
22
|
+
coinType: normalizedCoinType,
|
|
23
|
+
cursor,
|
|
24
|
+
limit: pageLimit
|
|
25
|
+
});
|
|
26
|
+
for (const coin of response.data ?? []) {
|
|
27
|
+
try {
|
|
28
|
+
const balance = BigInt(coin.balance);
|
|
29
|
+
coins.push({
|
|
30
|
+
coinObjectId: coin.coinObjectId,
|
|
31
|
+
balance
|
|
32
|
+
});
|
|
33
|
+
total += balance;
|
|
34
|
+
} catch {}
|
|
35
|
+
if (total >= requiredAmount) break;
|
|
36
|
+
}
|
|
37
|
+
cursor = response.hasNextPage && total < requiredAmount ? response.nextCursor : null;
|
|
38
|
+
} while (cursor);
|
|
39
|
+
if (coins.length === 0) throw new Error(`No usable gameplay coin objects were found for ${normalizedCoinType} at owner ${owner}`);
|
|
40
|
+
if (total < requiredAmount) throw new Error("Insufficient balance for this bet");
|
|
41
|
+
coins.sort((left, right) => {
|
|
42
|
+
if (left.balance === right.balance) return 0;
|
|
43
|
+
return left.balance > right.balance ? -1 : 1;
|
|
44
|
+
});
|
|
45
|
+
const selectedCoinIds = [];
|
|
46
|
+
let selectedTotal = 0n;
|
|
47
|
+
for (const coin of coins) {
|
|
48
|
+
selectedCoinIds.push(coin.coinObjectId);
|
|
49
|
+
selectedTotal += coin.balance;
|
|
50
|
+
if (selectedTotal >= requiredAmount) break;
|
|
51
|
+
}
|
|
52
|
+
return selectedCoinIds;
|
|
53
|
+
};
|
|
54
|
+
const resolveBetCoin = async ({ tx, coinType, amount, suiCoinType: _suiCoinType, coinSource, client, owner, allowGasCoinShortcut = true }) => {
|
|
55
|
+
const normalizedCoinType = (0, _suigar_currency_registry.normalizeCoinType)(coinType);
|
|
56
|
+
const resolvedAmount = (0, _suigar_sdk_utils.toBigInt)(amount);
|
|
57
|
+
if (resolvedAmount < 0n) throw new Error("Amount must be non-negative");
|
|
58
|
+
if (resolvedAmount === 0n) return tx.moveCall({
|
|
59
|
+
target: "0x2::coin::zero",
|
|
60
|
+
typeArguments: [normalizedCoinType],
|
|
61
|
+
arguments: []
|
|
62
|
+
});
|
|
63
|
+
if (coinSource?.kind === "object-ids") {
|
|
64
|
+
const primaryCoin = mergeCoinObjects(tx, coinSource.objectIds);
|
|
65
|
+
if (coinSource.split === false) return primaryCoin;
|
|
66
|
+
const [coin] = tx.splitCoins(primaryCoin, [tx.pure.u64(resolvedAmount)]);
|
|
67
|
+
return coin;
|
|
68
|
+
}
|
|
69
|
+
if (coinSource?.kind === "gas" && normalizedCoinType !== NETWORK_GAS_COIN_TYPE) throw new Error(`Gas coin sourcing is only supported for the native SUI gas coin (${NETWORK_GAS_COIN_TYPE})`);
|
|
70
|
+
if ((coinSource?.kind === "gas" || allowGasCoinShortcut) && normalizedCoinType === NETWORK_GAS_COIN_TYPE) {
|
|
71
|
+
if (coinSource?.kind === "gas" && coinSource.split === false) return tx.gas;
|
|
72
|
+
const [coin] = tx.splitCoins(tx.gas, [tx.pure.u64(resolvedAmount)]);
|
|
73
|
+
return coin;
|
|
74
|
+
}
|
|
75
|
+
if (!client || !owner) throw new Error(`A read-only client and owner address are required to source non-native coin ${normalizedCoinType}`);
|
|
76
|
+
const primaryCoin = mergeCoinObjects(tx, await selectCoinsForAmount({
|
|
77
|
+
client,
|
|
78
|
+
owner,
|
|
79
|
+
coinType: normalizedCoinType,
|
|
80
|
+
requiredAmount: resolvedAmount
|
|
81
|
+
}));
|
|
82
|
+
const [coin] = tx.splitCoins(primaryCoin, [tx.pure.u64(resolvedAmount)]);
|
|
83
|
+
return coin;
|
|
84
|
+
};
|
|
85
|
+
//#endregion
|
|
86
|
+
exports.resolveBetCoin = resolveBetCoin;
|
package/dist/coin.d.cts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { BetCoinSource, CoinReadClient } from "./types.cjs";
|
|
2
|
+
import { Transaction } from "@mysten/sui/transactions";
|
|
3
|
+
|
|
4
|
+
//#region src/coin.d.ts
|
|
5
|
+
declare const resolveBetCoin: ({
|
|
6
|
+
tx,
|
|
7
|
+
coinType,
|
|
8
|
+
amount,
|
|
9
|
+
suiCoinType: _suiCoinType,
|
|
10
|
+
coinSource,
|
|
11
|
+
client,
|
|
12
|
+
owner,
|
|
13
|
+
allowGasCoinShortcut
|
|
14
|
+
}: {
|
|
15
|
+
tx: Transaction;
|
|
16
|
+
coinType: string;
|
|
17
|
+
amount: number | bigint;
|
|
18
|
+
suiCoinType?: string;
|
|
19
|
+
coinSource?: BetCoinSource;
|
|
20
|
+
client?: CoinReadClient;
|
|
21
|
+
owner?: string;
|
|
22
|
+
allowGasCoinShortcut?: boolean;
|
|
23
|
+
}) => Promise<{
|
|
24
|
+
NestedResult: [number, number];
|
|
25
|
+
$kind: "NestedResult";
|
|
26
|
+
} | import("@mysten/sui/transactions").TransactionResult | {
|
|
27
|
+
$kind: "Input";
|
|
28
|
+
Input: number;
|
|
29
|
+
type?: "object";
|
|
30
|
+
} | {
|
|
31
|
+
$kind: "GasCoin";
|
|
32
|
+
GasCoin: true;
|
|
33
|
+
}>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { resolveBetCoin };
|
package/dist/coin.d.mts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { BetCoinSource, CoinReadClient } from "./types.mjs";
|
|
2
|
+
import { Transaction } from "@mysten/sui/transactions";
|
|
3
|
+
|
|
4
|
+
//#region src/coin.d.ts
|
|
5
|
+
declare const resolveBetCoin: ({
|
|
6
|
+
tx,
|
|
7
|
+
coinType,
|
|
8
|
+
amount,
|
|
9
|
+
suiCoinType: _suiCoinType,
|
|
10
|
+
coinSource,
|
|
11
|
+
client,
|
|
12
|
+
owner,
|
|
13
|
+
allowGasCoinShortcut
|
|
14
|
+
}: {
|
|
15
|
+
tx: Transaction;
|
|
16
|
+
coinType: string;
|
|
17
|
+
amount: number | bigint;
|
|
18
|
+
suiCoinType?: string;
|
|
19
|
+
coinSource?: BetCoinSource;
|
|
20
|
+
client?: CoinReadClient;
|
|
21
|
+
owner?: string;
|
|
22
|
+
allowGasCoinShortcut?: boolean;
|
|
23
|
+
}) => Promise<{
|
|
24
|
+
NestedResult: [number, number];
|
|
25
|
+
$kind: "NestedResult";
|
|
26
|
+
} | import("@mysten/sui/transactions").TransactionResult | {
|
|
27
|
+
$kind: "Input";
|
|
28
|
+
Input: number;
|
|
29
|
+
type?: "object";
|
|
30
|
+
} | {
|
|
31
|
+
$kind: "GasCoin";
|
|
32
|
+
GasCoin: true;
|
|
33
|
+
}>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { resolveBetCoin };
|
package/dist/coin.mjs
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { normalizeCoinType } from "@suigar/currency-registry";
|
|
2
|
+
import { SUI_TYPE_ARG } from "@mysten/sui/utils";
|
|
3
|
+
import { toBigInt } from "@suigar/sdk/utils";
|
|
4
|
+
//#region src/coin.ts
|
|
5
|
+
const DEFAULT_PAGE_LIMIT = 50;
|
|
6
|
+
const NETWORK_GAS_COIN_TYPE = normalizeCoinType(SUI_TYPE_ARG);
|
|
7
|
+
const mergeCoinObjects = (tx, coinIds) => {
|
|
8
|
+
if (coinIds.length === 0) throw new Error("No coin objects available");
|
|
9
|
+
const primaryCoin = tx.object(coinIds[0]);
|
|
10
|
+
if (coinIds.length > 1) tx.mergeCoins(primaryCoin, coinIds.slice(1).map((coinId) => tx.object(coinId)));
|
|
11
|
+
return primaryCoin;
|
|
12
|
+
};
|
|
13
|
+
const selectCoinsForAmount = async ({ client, owner, coinType, requiredAmount, pageLimit = DEFAULT_PAGE_LIMIT }) => {
|
|
14
|
+
if (requiredAmount <= 0n) return [];
|
|
15
|
+
let cursor = null;
|
|
16
|
+
let total = 0n;
|
|
17
|
+
const coins = [];
|
|
18
|
+
const normalizedCoinType = normalizeCoinType(coinType);
|
|
19
|
+
do {
|
|
20
|
+
const response = await client.getCoins({
|
|
21
|
+
owner,
|
|
22
|
+
coinType: normalizedCoinType,
|
|
23
|
+
cursor,
|
|
24
|
+
limit: pageLimit
|
|
25
|
+
});
|
|
26
|
+
for (const coin of response.data ?? []) {
|
|
27
|
+
try {
|
|
28
|
+
const balance = BigInt(coin.balance);
|
|
29
|
+
coins.push({
|
|
30
|
+
coinObjectId: coin.coinObjectId,
|
|
31
|
+
balance
|
|
32
|
+
});
|
|
33
|
+
total += balance;
|
|
34
|
+
} catch {}
|
|
35
|
+
if (total >= requiredAmount) break;
|
|
36
|
+
}
|
|
37
|
+
cursor = response.hasNextPage && total < requiredAmount ? response.nextCursor : null;
|
|
38
|
+
} while (cursor);
|
|
39
|
+
if (coins.length === 0) throw new Error(`No usable gameplay coin objects were found for ${normalizedCoinType} at owner ${owner}`);
|
|
40
|
+
if (total < requiredAmount) throw new Error("Insufficient balance for this bet");
|
|
41
|
+
coins.sort((left, right) => {
|
|
42
|
+
if (left.balance === right.balance) return 0;
|
|
43
|
+
return left.balance > right.balance ? -1 : 1;
|
|
44
|
+
});
|
|
45
|
+
const selectedCoinIds = [];
|
|
46
|
+
let selectedTotal = 0n;
|
|
47
|
+
for (const coin of coins) {
|
|
48
|
+
selectedCoinIds.push(coin.coinObjectId);
|
|
49
|
+
selectedTotal += coin.balance;
|
|
50
|
+
if (selectedTotal >= requiredAmount) break;
|
|
51
|
+
}
|
|
52
|
+
return selectedCoinIds;
|
|
53
|
+
};
|
|
54
|
+
const resolveBetCoin = async ({ tx, coinType, amount, suiCoinType: _suiCoinType, coinSource, client, owner, allowGasCoinShortcut = true }) => {
|
|
55
|
+
const normalizedCoinType = normalizeCoinType(coinType);
|
|
56
|
+
const resolvedAmount = toBigInt(amount);
|
|
57
|
+
if (resolvedAmount < 0n) throw new Error("Amount must be non-negative");
|
|
58
|
+
if (resolvedAmount === 0n) return tx.moveCall({
|
|
59
|
+
target: "0x2::coin::zero",
|
|
60
|
+
typeArguments: [normalizedCoinType],
|
|
61
|
+
arguments: []
|
|
62
|
+
});
|
|
63
|
+
if (coinSource?.kind === "object-ids") {
|
|
64
|
+
const primaryCoin = mergeCoinObjects(tx, coinSource.objectIds);
|
|
65
|
+
if (coinSource.split === false) return primaryCoin;
|
|
66
|
+
const [coin] = tx.splitCoins(primaryCoin, [tx.pure.u64(resolvedAmount)]);
|
|
67
|
+
return coin;
|
|
68
|
+
}
|
|
69
|
+
if (coinSource?.kind === "gas" && normalizedCoinType !== NETWORK_GAS_COIN_TYPE) throw new Error(`Gas coin sourcing is only supported for the native SUI gas coin (${NETWORK_GAS_COIN_TYPE})`);
|
|
70
|
+
if ((coinSource?.kind === "gas" || allowGasCoinShortcut) && normalizedCoinType === NETWORK_GAS_COIN_TYPE) {
|
|
71
|
+
if (coinSource?.kind === "gas" && coinSource.split === false) return tx.gas;
|
|
72
|
+
const [coin] = tx.splitCoins(tx.gas, [tx.pure.u64(resolvedAmount)]);
|
|
73
|
+
return coin;
|
|
74
|
+
}
|
|
75
|
+
if (!client || !owner) throw new Error(`A read-only client and owner address are required to source non-native coin ${normalizedCoinType}`);
|
|
76
|
+
const primaryCoin = mergeCoinObjects(tx, await selectCoinsForAmount({
|
|
77
|
+
client,
|
|
78
|
+
owner,
|
|
79
|
+
coinType: normalizedCoinType,
|
|
80
|
+
requiredAmount: resolvedAmount
|
|
81
|
+
}));
|
|
82
|
+
const [coin] = tx.splitCoins(primaryCoin, [tx.pure.u64(resolvedAmount)]);
|
|
83
|
+
return coin;
|
|
84
|
+
};
|
|
85
|
+
//#endregion
|
|
86
|
+
export { resolveBetCoin };
|