@iamgame/wallet-sdk-server 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 +28 -0
- package/dist/index.cjs +151 -0
- package/dist/index.d.cts +137 -0
- package/dist/index.d.ts +137 -0
- package/dist/index.js +148 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @iamgame/wallet-sdk-server
|
|
2
|
+
|
|
3
|
+
Backend SDK for [IAMGame Wallet](https://wallet.iamgame.com). Authenticated with your **secret
|
|
4
|
+
key** (`sk_`) — server-to-server only. Two jobs: **verify wallet sessions** at login, and run the
|
|
5
|
+
**high-frequency money ledger**. The browser must never hold an `sk_` or call `win` on itself.
|
|
6
|
+
|
|
7
|
+
All amounts are `bigint` integer base units (lamports / micro-USDC).
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { IAMGameWalletServer } from "@iamgame/wallet-sdk-server";
|
|
11
|
+
|
|
12
|
+
const wallet = new IAMGameWalletServer({
|
|
13
|
+
secretKey: process.env.IAMGAME_WALLET_SECRET_KEY!, // sk_live_... / sk_test_...
|
|
14
|
+
baseUrl: "https://api-wallet.iamgame.com/v1",
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Login: verify the player's session token, then mint your own app JWT.
|
|
18
|
+
const verified = await wallet.verifySession(sessionToken);
|
|
19
|
+
// → { userId, walletAddress, wallets, identities, environment }
|
|
20
|
+
|
|
21
|
+
// Ledger: lock once, run the hot path, settle the net.
|
|
22
|
+
const s = await wallet.ledger.openSession({ userId, gameCode: "momentum", currency: "USDC", lockAmount: 50_000_000n });
|
|
23
|
+
await wallet.ledger.bet({ sessionToken: s.sessionToken, transactionUuid: "r1-bet", amount: 1_000_000n, round: "r1" });
|
|
24
|
+
await wallet.ledger.win({ sessionToken: s.sessionToken, transactionUuid: "r1-win", referenceTransactionUuid: "r1-bet", amount: 1_800_000n, round: "r1", roundClosed: true });
|
|
25
|
+
const { payout } = await wallet.ledger.settleSession(s.sessionToken);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
See the full guide at https://wallet.iamgame.com/docs.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ../../solven/sdk-server/src/errors.ts
|
|
4
|
+
var SolvenServerError = class _SolvenServerError extends Error {
|
|
5
|
+
constructor(args) {
|
|
6
|
+
super(args.message);
|
|
7
|
+
this.name = "SolvenServerError";
|
|
8
|
+
this.code = args.code;
|
|
9
|
+
this.httpStatus = args.httpStatus;
|
|
10
|
+
this.details = args.details;
|
|
11
|
+
}
|
|
12
|
+
static fromEnvelope(httpStatus, env) {
|
|
13
|
+
return new _SolvenServerError({
|
|
14
|
+
code: env.code,
|
|
15
|
+
message: env.message,
|
|
16
|
+
httpStatus,
|
|
17
|
+
details: env.details
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// ../../solven/sdk-server/src/client.ts
|
|
23
|
+
var LedgerApi = class {
|
|
24
|
+
constructor(call) {
|
|
25
|
+
this.call = call;
|
|
26
|
+
}
|
|
27
|
+
async openSession(args) {
|
|
28
|
+
const r = await this.call("/ledger/sessions", {
|
|
29
|
+
userId: args.userId,
|
|
30
|
+
gameCode: args.gameCode,
|
|
31
|
+
currency: args.currency,
|
|
32
|
+
lockAmount: args.lockAmount.toString()
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
sessionToken: r.sessionToken,
|
|
36
|
+
sessionId: r.sessionId,
|
|
37
|
+
currency: r.currency,
|
|
38
|
+
lockedBalance: BigInt(r.lockedBalance),
|
|
39
|
+
balance: BigInt(r.balance)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async bet(args) {
|
|
43
|
+
return this.txn("/ledger/bet", {
|
|
44
|
+
sessionToken: args.sessionToken,
|
|
45
|
+
transactionUuid: args.transactionUuid,
|
|
46
|
+
amount: args.amount.toString(),
|
|
47
|
+
round: args.round,
|
|
48
|
+
roundClosed: args.roundClosed
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async win(args) {
|
|
52
|
+
return this.txn("/ledger/win", {
|
|
53
|
+
sessionToken: args.sessionToken,
|
|
54
|
+
transactionUuid: args.transactionUuid,
|
|
55
|
+
referenceTransactionUuid: args.referenceTransactionUuid,
|
|
56
|
+
amount: args.amount.toString(),
|
|
57
|
+
round: args.round,
|
|
58
|
+
roundClosed: args.roundClosed
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async rollback(args) {
|
|
62
|
+
return this.txn("/ledger/rollback", {
|
|
63
|
+
sessionToken: args.sessionToken,
|
|
64
|
+
referenceTransactionUuid: args.referenceTransactionUuid,
|
|
65
|
+
round: args.round
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async balance(sessionToken) {
|
|
69
|
+
const r = await this.call("/ledger/balance", { sessionToken });
|
|
70
|
+
return { balance: BigInt(r.balance), currency: r.currency };
|
|
71
|
+
}
|
|
72
|
+
async settleSession(sessionToken) {
|
|
73
|
+
const r = await this.call(
|
|
74
|
+
"/ledger/settle",
|
|
75
|
+
{ sessionToken }
|
|
76
|
+
);
|
|
77
|
+
return { sessionId: r.sessionId, currency: r.currency, payout: BigInt(r.payout), railRef: r.railRef };
|
|
78
|
+
}
|
|
79
|
+
async txn(path, body) {
|
|
80
|
+
const r = await this.call(
|
|
81
|
+
path,
|
|
82
|
+
body
|
|
83
|
+
);
|
|
84
|
+
return { transactionUuid: r.transactionUuid, balance: BigInt(r.balance), currency: r.currency, applied: r.applied };
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var SolvenServer = class {
|
|
88
|
+
constructor(options) {
|
|
89
|
+
if (!options.secretKey.startsWith("sk_")) {
|
|
90
|
+
throw new Error("SolvenServer requires a secret key (sk_...)");
|
|
91
|
+
}
|
|
92
|
+
this.secretKey = options.secretKey;
|
|
93
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
94
|
+
this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
95
|
+
this.ledger = new LedgerApi((path, body) => this.call(path, body));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Verify a player's wallet session token server-to-server and return the canonical
|
|
99
|
+
* verified identity (scoped to this key's app + environment). Call this from your
|
|
100
|
+
* backend with the token your frontend received from the wallet, then mint your own
|
|
101
|
+
* app JWT for the returned user. Throws if the session is invalid or scoped elsewhere.
|
|
102
|
+
*/
|
|
103
|
+
async verifySession(sessionToken) {
|
|
104
|
+
return this.call("/sessions/verify", { sessionToken });
|
|
105
|
+
}
|
|
106
|
+
async call(path, body) {
|
|
107
|
+
let resp;
|
|
108
|
+
try {
|
|
109
|
+
resp = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: {
|
|
112
|
+
authorization: `Bearer ${this.secretKey}`,
|
|
113
|
+
"content-type": "application/json"
|
|
114
|
+
},
|
|
115
|
+
body: JSON.stringify(body ?? {})
|
|
116
|
+
});
|
|
117
|
+
} catch (err) {
|
|
118
|
+
throw new SolvenServerError({
|
|
119
|
+
code: "client/network",
|
|
120
|
+
message: err instanceof Error ? err.message : "network error",
|
|
121
|
+
httpStatus: 0
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const text = await resp.text();
|
|
125
|
+
let json = void 0;
|
|
126
|
+
if (text) {
|
|
127
|
+
try {
|
|
128
|
+
json = JSON.parse(text);
|
|
129
|
+
} catch {
|
|
130
|
+
throw new SolvenServerError({
|
|
131
|
+
code: "client/bad_response",
|
|
132
|
+
message: `non-JSON response (${resp.status})`,
|
|
133
|
+
httpStatus: resp.status
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!resp.ok) {
|
|
138
|
+
const env = json?.error;
|
|
139
|
+
if (env?.code) throw SolvenServerError.fromEnvelope(resp.status, env);
|
|
140
|
+
throw new SolvenServerError({
|
|
141
|
+
code: "client/bad_response",
|
|
142
|
+
message: `request failed (${resp.status})`,
|
|
143
|
+
httpStatus: resp.status
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return json;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
exports.IAMGameWalletServer = SolvenServer;
|
|
151
|
+
exports.WalletServerError = SolvenServerError;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
type AuthMethod = "siws" | "telegram";
|
|
2
|
+
interface IUserIdentity {
|
|
3
|
+
id: string;
|
|
4
|
+
type: AuthMethod;
|
|
5
|
+
/** For SIWS: base58 Solana pubkey. For Telegram: numeric user id as string. */
|
|
6
|
+
externalId: string;
|
|
7
|
+
}
|
|
8
|
+
interface IUserWallet {
|
|
9
|
+
/** base58 Solana pubkey */
|
|
10
|
+
address: string;
|
|
11
|
+
/** test (devnet) or live (mainnet) — wallets are isolated per environment. */
|
|
12
|
+
environment: "test" | "live";
|
|
13
|
+
custody: "self" | "operator";
|
|
14
|
+
status: "active" | "exported" | "archived";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Result of a server-to-server session verification (POST /v1/sessions/verify).
|
|
18
|
+
* A relying party (game backend) presents a player's wallet session token together
|
|
19
|
+
* with its own secret key; the wallet confirms the session, scopes it to the caller's
|
|
20
|
+
* app + environment, and returns the canonical user identity. The relying party then
|
|
21
|
+
* mints its OWN app JWT for its UI, trusting this verified identity.
|
|
22
|
+
*/
|
|
23
|
+
interface ISessionVerification {
|
|
24
|
+
userId: string;
|
|
25
|
+
appId: string;
|
|
26
|
+
environment: "test" | "live";
|
|
27
|
+
authMethod: AuthMethod;
|
|
28
|
+
/** Primary active wallet address for this user, or null if none provisioned yet. */
|
|
29
|
+
walletAddress: string | null;
|
|
30
|
+
wallets: IUserWallet[];
|
|
31
|
+
identities: IUserIdentity[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type IAMGameErrorCode = "auth/invalid_signature" | "auth/challenge_expired" | "auth/challenge_already_redeemed" | "auth/invalid_init_data" | "auth/init_data_stale" | "auth/init_data_replayed" | "auth/missing_token" | "auth/invalid_token" | "auth/expired_token" | "wallet/not_found" | "wallet/insufficient_balance" | "wallet/export_blocked" | "wallet/already_exported" | "user/not_found" | "user/suspended" | "compliance/blocked" | "sign/invalid_transaction" | "sign/wallet_archived" | "sign/internal_failure" | "withdrawal/insufficient_balance" | "withdrawal/limit_exceeded" | "ledger/insufficient_balance" | "ledger/session_not_found" | "ledger/session_closed" | "ledger/bet_not_found" | "ledger/bet_rolled_back" | "ledger/currency_mismatch" | "ratelimit/exceeded" | "idempotency/key_in_use" | "validation/bad_request" | "server/internal";
|
|
35
|
+
interface IIAMGameErrorEnvelope {
|
|
36
|
+
code: IAMGameErrorCode;
|
|
37
|
+
message: string;
|
|
38
|
+
details?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface IAMGameServerOptions {
|
|
42
|
+
/** Secret key, e.g. "sk_live_..." or "sk_test_...". */
|
|
43
|
+
secretKey: string;
|
|
44
|
+
/** Base URL incl. version path, e.g. "https://api-wallet.iamgame.com/v1". */
|
|
45
|
+
baseUrl: string;
|
|
46
|
+
fetchImpl?: typeof fetch;
|
|
47
|
+
}
|
|
48
|
+
interface OpenSessionArgs {
|
|
49
|
+
userId: string;
|
|
50
|
+
gameCode: string;
|
|
51
|
+
currency: string;
|
|
52
|
+
lockAmount: bigint;
|
|
53
|
+
}
|
|
54
|
+
interface OpenSessionResult {
|
|
55
|
+
sessionToken: string;
|
|
56
|
+
sessionId: string;
|
|
57
|
+
currency: string;
|
|
58
|
+
lockedBalance: bigint;
|
|
59
|
+
balance: bigint;
|
|
60
|
+
}
|
|
61
|
+
interface BetArgs {
|
|
62
|
+
sessionToken: string;
|
|
63
|
+
transactionUuid: string;
|
|
64
|
+
amount: bigint;
|
|
65
|
+
round?: string;
|
|
66
|
+
roundClosed?: boolean;
|
|
67
|
+
}
|
|
68
|
+
interface WinArgs {
|
|
69
|
+
sessionToken: string;
|
|
70
|
+
transactionUuid: string;
|
|
71
|
+
referenceTransactionUuid: string;
|
|
72
|
+
amount: bigint;
|
|
73
|
+
round?: string;
|
|
74
|
+
roundClosed?: boolean;
|
|
75
|
+
}
|
|
76
|
+
interface RollbackArgs {
|
|
77
|
+
sessionToken: string;
|
|
78
|
+
referenceTransactionUuid: string;
|
|
79
|
+
round?: string;
|
|
80
|
+
}
|
|
81
|
+
interface TxnResult {
|
|
82
|
+
transactionUuid: string;
|
|
83
|
+
balance: bigint;
|
|
84
|
+
currency: string;
|
|
85
|
+
applied: boolean;
|
|
86
|
+
}
|
|
87
|
+
interface BalanceResult {
|
|
88
|
+
balance: bigint;
|
|
89
|
+
currency: string;
|
|
90
|
+
}
|
|
91
|
+
interface SettleResult {
|
|
92
|
+
sessionId: string;
|
|
93
|
+
currency: string;
|
|
94
|
+
payout: bigint;
|
|
95
|
+
railRef: string | null;
|
|
96
|
+
}
|
|
97
|
+
declare class LedgerApi {
|
|
98
|
+
private readonly call;
|
|
99
|
+
constructor(call: <T>(path: string, body: unknown) => Promise<T>);
|
|
100
|
+
openSession(args: OpenSessionArgs): Promise<OpenSessionResult>;
|
|
101
|
+
bet(args: BetArgs): Promise<TxnResult>;
|
|
102
|
+
win(args: WinArgs): Promise<TxnResult>;
|
|
103
|
+
rollback(args: RollbackArgs): Promise<TxnResult>;
|
|
104
|
+
balance(sessionToken: string): Promise<BalanceResult>;
|
|
105
|
+
settleSession(sessionToken: string): Promise<SettleResult>;
|
|
106
|
+
private txn;
|
|
107
|
+
}
|
|
108
|
+
declare class IAMGameServer {
|
|
109
|
+
readonly ledger: LedgerApi;
|
|
110
|
+
private readonly secretKey;
|
|
111
|
+
private readonly baseUrl;
|
|
112
|
+
private readonly fetchImpl;
|
|
113
|
+
constructor(options: IAMGameServerOptions);
|
|
114
|
+
/**
|
|
115
|
+
* Verify a player's wallet session token server-to-server and return the canonical
|
|
116
|
+
* verified identity (scoped to this key's app + environment). Call this from your
|
|
117
|
+
* backend with the token your frontend received from the wallet, then mint your own
|
|
118
|
+
* app JWT for the returned user. Throws if the session is invalid or scoped elsewhere.
|
|
119
|
+
*/
|
|
120
|
+
verifySession(sessionToken: string): Promise<ISessionVerification>;
|
|
121
|
+
private call;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
declare class IAMGameServerError extends Error {
|
|
125
|
+
readonly code: IAMGameErrorCode | "client/network" | "client/bad_response";
|
|
126
|
+
readonly httpStatus: number;
|
|
127
|
+
readonly details?: Record<string, unknown>;
|
|
128
|
+
constructor(args: {
|
|
129
|
+
code: IAMGameServerError["code"];
|
|
130
|
+
message: string;
|
|
131
|
+
httpStatus: number;
|
|
132
|
+
details?: Record<string, unknown>;
|
|
133
|
+
});
|
|
134
|
+
static fromEnvelope(httpStatus: number, env: IIAMGameErrorEnvelope): IAMGameServerError;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export { type BalanceResult, type BetArgs, IAMGameServer as IAMGameWalletServer, type IAMGameServerOptions as IAMGameWalletServerOptions, type ISessionVerification, type OpenSessionArgs, type OpenSessionResult, type RollbackArgs, type SettleResult, type TxnResult, IAMGameServerError as WalletServerError, type WinArgs };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
type AuthMethod = "siws" | "telegram";
|
|
2
|
+
interface IUserIdentity {
|
|
3
|
+
id: string;
|
|
4
|
+
type: AuthMethod;
|
|
5
|
+
/** For SIWS: base58 Solana pubkey. For Telegram: numeric user id as string. */
|
|
6
|
+
externalId: string;
|
|
7
|
+
}
|
|
8
|
+
interface IUserWallet {
|
|
9
|
+
/** base58 Solana pubkey */
|
|
10
|
+
address: string;
|
|
11
|
+
/** test (devnet) or live (mainnet) — wallets are isolated per environment. */
|
|
12
|
+
environment: "test" | "live";
|
|
13
|
+
custody: "self" | "operator";
|
|
14
|
+
status: "active" | "exported" | "archived";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Result of a server-to-server session verification (POST /v1/sessions/verify).
|
|
18
|
+
* A relying party (game backend) presents a player's wallet session token together
|
|
19
|
+
* with its own secret key; the wallet confirms the session, scopes it to the caller's
|
|
20
|
+
* app + environment, and returns the canonical user identity. The relying party then
|
|
21
|
+
* mints its OWN app JWT for its UI, trusting this verified identity.
|
|
22
|
+
*/
|
|
23
|
+
interface ISessionVerification {
|
|
24
|
+
userId: string;
|
|
25
|
+
appId: string;
|
|
26
|
+
environment: "test" | "live";
|
|
27
|
+
authMethod: AuthMethod;
|
|
28
|
+
/** Primary active wallet address for this user, or null if none provisioned yet. */
|
|
29
|
+
walletAddress: string | null;
|
|
30
|
+
wallets: IUserWallet[];
|
|
31
|
+
identities: IUserIdentity[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type IAMGameErrorCode = "auth/invalid_signature" | "auth/challenge_expired" | "auth/challenge_already_redeemed" | "auth/invalid_init_data" | "auth/init_data_stale" | "auth/init_data_replayed" | "auth/missing_token" | "auth/invalid_token" | "auth/expired_token" | "wallet/not_found" | "wallet/insufficient_balance" | "wallet/export_blocked" | "wallet/already_exported" | "user/not_found" | "user/suspended" | "compliance/blocked" | "sign/invalid_transaction" | "sign/wallet_archived" | "sign/internal_failure" | "withdrawal/insufficient_balance" | "withdrawal/limit_exceeded" | "ledger/insufficient_balance" | "ledger/session_not_found" | "ledger/session_closed" | "ledger/bet_not_found" | "ledger/bet_rolled_back" | "ledger/currency_mismatch" | "ratelimit/exceeded" | "idempotency/key_in_use" | "validation/bad_request" | "server/internal";
|
|
35
|
+
interface IIAMGameErrorEnvelope {
|
|
36
|
+
code: IAMGameErrorCode;
|
|
37
|
+
message: string;
|
|
38
|
+
details?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface IAMGameServerOptions {
|
|
42
|
+
/** Secret key, e.g. "sk_live_..." or "sk_test_...". */
|
|
43
|
+
secretKey: string;
|
|
44
|
+
/** Base URL incl. version path, e.g. "https://api-wallet.iamgame.com/v1". */
|
|
45
|
+
baseUrl: string;
|
|
46
|
+
fetchImpl?: typeof fetch;
|
|
47
|
+
}
|
|
48
|
+
interface OpenSessionArgs {
|
|
49
|
+
userId: string;
|
|
50
|
+
gameCode: string;
|
|
51
|
+
currency: string;
|
|
52
|
+
lockAmount: bigint;
|
|
53
|
+
}
|
|
54
|
+
interface OpenSessionResult {
|
|
55
|
+
sessionToken: string;
|
|
56
|
+
sessionId: string;
|
|
57
|
+
currency: string;
|
|
58
|
+
lockedBalance: bigint;
|
|
59
|
+
balance: bigint;
|
|
60
|
+
}
|
|
61
|
+
interface BetArgs {
|
|
62
|
+
sessionToken: string;
|
|
63
|
+
transactionUuid: string;
|
|
64
|
+
amount: bigint;
|
|
65
|
+
round?: string;
|
|
66
|
+
roundClosed?: boolean;
|
|
67
|
+
}
|
|
68
|
+
interface WinArgs {
|
|
69
|
+
sessionToken: string;
|
|
70
|
+
transactionUuid: string;
|
|
71
|
+
referenceTransactionUuid: string;
|
|
72
|
+
amount: bigint;
|
|
73
|
+
round?: string;
|
|
74
|
+
roundClosed?: boolean;
|
|
75
|
+
}
|
|
76
|
+
interface RollbackArgs {
|
|
77
|
+
sessionToken: string;
|
|
78
|
+
referenceTransactionUuid: string;
|
|
79
|
+
round?: string;
|
|
80
|
+
}
|
|
81
|
+
interface TxnResult {
|
|
82
|
+
transactionUuid: string;
|
|
83
|
+
balance: bigint;
|
|
84
|
+
currency: string;
|
|
85
|
+
applied: boolean;
|
|
86
|
+
}
|
|
87
|
+
interface BalanceResult {
|
|
88
|
+
balance: bigint;
|
|
89
|
+
currency: string;
|
|
90
|
+
}
|
|
91
|
+
interface SettleResult {
|
|
92
|
+
sessionId: string;
|
|
93
|
+
currency: string;
|
|
94
|
+
payout: bigint;
|
|
95
|
+
railRef: string | null;
|
|
96
|
+
}
|
|
97
|
+
declare class LedgerApi {
|
|
98
|
+
private readonly call;
|
|
99
|
+
constructor(call: <T>(path: string, body: unknown) => Promise<T>);
|
|
100
|
+
openSession(args: OpenSessionArgs): Promise<OpenSessionResult>;
|
|
101
|
+
bet(args: BetArgs): Promise<TxnResult>;
|
|
102
|
+
win(args: WinArgs): Promise<TxnResult>;
|
|
103
|
+
rollback(args: RollbackArgs): Promise<TxnResult>;
|
|
104
|
+
balance(sessionToken: string): Promise<BalanceResult>;
|
|
105
|
+
settleSession(sessionToken: string): Promise<SettleResult>;
|
|
106
|
+
private txn;
|
|
107
|
+
}
|
|
108
|
+
declare class IAMGameServer {
|
|
109
|
+
readonly ledger: LedgerApi;
|
|
110
|
+
private readonly secretKey;
|
|
111
|
+
private readonly baseUrl;
|
|
112
|
+
private readonly fetchImpl;
|
|
113
|
+
constructor(options: IAMGameServerOptions);
|
|
114
|
+
/**
|
|
115
|
+
* Verify a player's wallet session token server-to-server and return the canonical
|
|
116
|
+
* verified identity (scoped to this key's app + environment). Call this from your
|
|
117
|
+
* backend with the token your frontend received from the wallet, then mint your own
|
|
118
|
+
* app JWT for the returned user. Throws if the session is invalid or scoped elsewhere.
|
|
119
|
+
*/
|
|
120
|
+
verifySession(sessionToken: string): Promise<ISessionVerification>;
|
|
121
|
+
private call;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
declare class IAMGameServerError extends Error {
|
|
125
|
+
readonly code: IAMGameErrorCode | "client/network" | "client/bad_response";
|
|
126
|
+
readonly httpStatus: number;
|
|
127
|
+
readonly details?: Record<string, unknown>;
|
|
128
|
+
constructor(args: {
|
|
129
|
+
code: IAMGameServerError["code"];
|
|
130
|
+
message: string;
|
|
131
|
+
httpStatus: number;
|
|
132
|
+
details?: Record<string, unknown>;
|
|
133
|
+
});
|
|
134
|
+
static fromEnvelope(httpStatus: number, env: IIAMGameErrorEnvelope): IAMGameServerError;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export { type BalanceResult, type BetArgs, IAMGameServer as IAMGameWalletServer, type IAMGameServerOptions as IAMGameWalletServerOptions, type ISessionVerification, type OpenSessionArgs, type OpenSessionResult, type RollbackArgs, type SettleResult, type TxnResult, IAMGameServerError as WalletServerError, type WinArgs };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// ../../solven/sdk-server/src/errors.ts
|
|
2
|
+
var SolvenServerError = class _SolvenServerError extends Error {
|
|
3
|
+
constructor(args) {
|
|
4
|
+
super(args.message);
|
|
5
|
+
this.name = "SolvenServerError";
|
|
6
|
+
this.code = args.code;
|
|
7
|
+
this.httpStatus = args.httpStatus;
|
|
8
|
+
this.details = args.details;
|
|
9
|
+
}
|
|
10
|
+
static fromEnvelope(httpStatus, env) {
|
|
11
|
+
return new _SolvenServerError({
|
|
12
|
+
code: env.code,
|
|
13
|
+
message: env.message,
|
|
14
|
+
httpStatus,
|
|
15
|
+
details: env.details
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// ../../solven/sdk-server/src/client.ts
|
|
21
|
+
var LedgerApi = class {
|
|
22
|
+
constructor(call) {
|
|
23
|
+
this.call = call;
|
|
24
|
+
}
|
|
25
|
+
async openSession(args) {
|
|
26
|
+
const r = await this.call("/ledger/sessions", {
|
|
27
|
+
userId: args.userId,
|
|
28
|
+
gameCode: args.gameCode,
|
|
29
|
+
currency: args.currency,
|
|
30
|
+
lockAmount: args.lockAmount.toString()
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
sessionToken: r.sessionToken,
|
|
34
|
+
sessionId: r.sessionId,
|
|
35
|
+
currency: r.currency,
|
|
36
|
+
lockedBalance: BigInt(r.lockedBalance),
|
|
37
|
+
balance: BigInt(r.balance)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async bet(args) {
|
|
41
|
+
return this.txn("/ledger/bet", {
|
|
42
|
+
sessionToken: args.sessionToken,
|
|
43
|
+
transactionUuid: args.transactionUuid,
|
|
44
|
+
amount: args.amount.toString(),
|
|
45
|
+
round: args.round,
|
|
46
|
+
roundClosed: args.roundClosed
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async win(args) {
|
|
50
|
+
return this.txn("/ledger/win", {
|
|
51
|
+
sessionToken: args.sessionToken,
|
|
52
|
+
transactionUuid: args.transactionUuid,
|
|
53
|
+
referenceTransactionUuid: args.referenceTransactionUuid,
|
|
54
|
+
amount: args.amount.toString(),
|
|
55
|
+
round: args.round,
|
|
56
|
+
roundClosed: args.roundClosed
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async rollback(args) {
|
|
60
|
+
return this.txn("/ledger/rollback", {
|
|
61
|
+
sessionToken: args.sessionToken,
|
|
62
|
+
referenceTransactionUuid: args.referenceTransactionUuid,
|
|
63
|
+
round: args.round
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async balance(sessionToken) {
|
|
67
|
+
const r = await this.call("/ledger/balance", { sessionToken });
|
|
68
|
+
return { balance: BigInt(r.balance), currency: r.currency };
|
|
69
|
+
}
|
|
70
|
+
async settleSession(sessionToken) {
|
|
71
|
+
const r = await this.call(
|
|
72
|
+
"/ledger/settle",
|
|
73
|
+
{ sessionToken }
|
|
74
|
+
);
|
|
75
|
+
return { sessionId: r.sessionId, currency: r.currency, payout: BigInt(r.payout), railRef: r.railRef };
|
|
76
|
+
}
|
|
77
|
+
async txn(path, body) {
|
|
78
|
+
const r = await this.call(
|
|
79
|
+
path,
|
|
80
|
+
body
|
|
81
|
+
);
|
|
82
|
+
return { transactionUuid: r.transactionUuid, balance: BigInt(r.balance), currency: r.currency, applied: r.applied };
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var SolvenServer = class {
|
|
86
|
+
constructor(options) {
|
|
87
|
+
if (!options.secretKey.startsWith("sk_")) {
|
|
88
|
+
throw new Error("SolvenServer requires a secret key (sk_...)");
|
|
89
|
+
}
|
|
90
|
+
this.secretKey = options.secretKey;
|
|
91
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
92
|
+
this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
93
|
+
this.ledger = new LedgerApi((path, body) => this.call(path, body));
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Verify a player's wallet session token server-to-server and return the canonical
|
|
97
|
+
* verified identity (scoped to this key's app + environment). Call this from your
|
|
98
|
+
* backend with the token your frontend received from the wallet, then mint your own
|
|
99
|
+
* app JWT for the returned user. Throws if the session is invalid or scoped elsewhere.
|
|
100
|
+
*/
|
|
101
|
+
async verifySession(sessionToken) {
|
|
102
|
+
return this.call("/sessions/verify", { sessionToken });
|
|
103
|
+
}
|
|
104
|
+
async call(path, body) {
|
|
105
|
+
let resp;
|
|
106
|
+
try {
|
|
107
|
+
resp = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
authorization: `Bearer ${this.secretKey}`,
|
|
111
|
+
"content-type": "application/json"
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify(body ?? {})
|
|
114
|
+
});
|
|
115
|
+
} catch (err) {
|
|
116
|
+
throw new SolvenServerError({
|
|
117
|
+
code: "client/network",
|
|
118
|
+
message: err instanceof Error ? err.message : "network error",
|
|
119
|
+
httpStatus: 0
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const text = await resp.text();
|
|
123
|
+
let json = void 0;
|
|
124
|
+
if (text) {
|
|
125
|
+
try {
|
|
126
|
+
json = JSON.parse(text);
|
|
127
|
+
} catch {
|
|
128
|
+
throw new SolvenServerError({
|
|
129
|
+
code: "client/bad_response",
|
|
130
|
+
message: `non-JSON response (${resp.status})`,
|
|
131
|
+
httpStatus: resp.status
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!resp.ok) {
|
|
136
|
+
const env = json?.error;
|
|
137
|
+
if (env?.code) throw SolvenServerError.fromEnvelope(resp.status, env);
|
|
138
|
+
throw new SolvenServerError({
|
|
139
|
+
code: "client/bad_response",
|
|
140
|
+
message: `request failed (${resp.status})`,
|
|
141
|
+
httpStatus: resp.status
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return json;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export { SolvenServer as IAMGameWalletServer, SolvenServerError as WalletServerError };
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@iamgame/wallet-sdk-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "IAMGame Wallet backend SDK — secret-key server-to-server client: verify wallet sessions and run the high-frequency money ledger.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://wallet.iamgame.com",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/iamgame777/iamgame-mono.git",
|
|
10
|
+
"directory": "iamgame-wallet/sdk-server"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"iamgame",
|
|
14
|
+
"wallet",
|
|
15
|
+
"solana",
|
|
16
|
+
"ledger",
|
|
17
|
+
"server",
|
|
18
|
+
"backend",
|
|
19
|
+
"web3"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "dist/index.cjs",
|
|
23
|
+
"module": "dist/index.js",
|
|
24
|
+
"types": "dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"import": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"require": {
|
|
32
|
+
"types": "./dist/index.d.cts",
|
|
33
|
+
"default": "./dist/index.cjs"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"README.md"
|
|
40
|
+
],
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup && node ../scripts/debrand-dts.mjs",
|
|
47
|
+
"watch": "tsup --watch",
|
|
48
|
+
"prepublishOnly": "npm run build"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@solven/sdk-server": "*",
|
|
52
|
+
"@solven/types": "*",
|
|
53
|
+
"tsup": "^8.3.5",
|
|
54
|
+
"typescript": "^5"
|
|
55
|
+
}
|
|
56
|
+
}
|