multicoyn-sdk 0.1.8 → 0.1.9
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 +212 -66
- package/dist/{ccip-BamGZKKI.js → ccip-DIsMfc62.js} +1 -1
- package/dist/{ccip-BBBr8vlp.cjs → ccip-DpYR9KrU.cjs} +1 -1
- package/dist/components/PaymentModal.d.ts +1 -1
- package/dist/{index-C1S0YnyW.js → index-BqTi7WKI.js} +1102 -1090
- package/dist/{index-Daw_yCWZ.cjs → index-DqlG-QlC.cjs} +8 -8
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/multicoyn-sdk.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Multicoyn SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React SDK for multi-coin/multi-token payments with split allocation, auto-optimize, and multi-chain support (Lisk Sepolia). Includes a ready-to-use payment modal UI and wallet integration via RainbowKit/Wagmi.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -12,112 +12,258 @@ yarn add multicoyn-sdk
|
|
|
12
12
|
pnpm add multicoyn-sdk
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Make sure you have the following peer dependencies installed:
|
|
18
|
-
|
|
15
|
+
### Peer Dependencies
|
|
16
|
+
Make sure these are installed:
|
|
19
17
|
```bash
|
|
20
|
-
npm install react react-dom viem
|
|
18
|
+
npm install react react-dom wagmi viem @tanstack/react-query @rainbow-me/rainbowkit
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
### 1. Setup Providers
|
|
26
|
-
|
|
27
|
-
Wrap your app with the required providers:
|
|
21
|
+
## Quick Start
|
|
28
22
|
|
|
29
23
|
```tsx
|
|
30
|
-
import { Web3Provider, PaymentProvider } from 'multicoyn-sdk';
|
|
24
|
+
import { Web3Provider, PaymentProvider, MulticoynButton } from 'multicoyn-sdk';
|
|
31
25
|
import 'multicoyn-sdk/styles';
|
|
32
26
|
|
|
27
|
+
const items = [{ name: 'Order #123', price: 100 }]; // price in selected currency (default USD)
|
|
28
|
+
|
|
33
29
|
function App() {
|
|
34
30
|
return (
|
|
35
31
|
<Web3Provider>
|
|
36
32
|
<PaymentProvider>
|
|
37
|
-
<
|
|
33
|
+
<MulticoynButton
|
|
34
|
+
merchantAddress="0xYourMerchantAddress"
|
|
35
|
+
items={items}
|
|
36
|
+
config={{ currency: 'USD' }} // or 'IDR'
|
|
37
|
+
onPaymentComplete={(result) => console.log('Success:', result)}
|
|
38
|
+
onPaymentError={(error) => console.error('Error:', error)}
|
|
39
|
+
/>
|
|
38
40
|
</PaymentProvider>
|
|
39
41
|
</Web3Provider>
|
|
40
42
|
);
|
|
41
43
|
}
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
## Key Features
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
- 🪙 **Multi-token split**: Users can allocate multiple tokens at once (ETH, USDC, USDT, DAI, WBTC)
|
|
49
|
+
- ⚖️ **Auto-optimize**: Automatically distributes allocation (prioritizes stablecoins if sufficient)
|
|
50
|
+
- 🔄 **Live price fetch**: Real-time token prices via on-chain TokenRegistry
|
|
51
|
+
- 🛡️ **Balance & price validation**: Verifies allocation covers total payment before submission
|
|
52
|
+
- 🧾 **Multi-item cart**: Supports multiple items with automatic total calculation
|
|
53
|
+
- 💱 **Multi-currency**: Supports USD and IDR pricing with automatic conversion
|
|
54
|
+
- 💸 **IDR Settlement**: Option to settle payments in IDRX stablecoin
|
|
55
|
+
- 🌐 **Wallet-ready**: RainbowKit + Wagmi + React Query pre-configured via `Web3Provider`
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
const handlePaymentComplete = (result) => {
|
|
51
|
-
console.log('Payment success:', result.transactionId);
|
|
52
|
-
};
|
|
57
|
+
## Supported Tokens
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
| Token | Symbol | Use |
|
|
60
|
+
|-------|--------|-----|
|
|
61
|
+
| Ether | ETH | Payment |
|
|
62
|
+
| USD Coin | USDC | Payment |
|
|
63
|
+
| Tether USD | USDT | Payment |
|
|
64
|
+
| Dai Stablecoin | DAI | Payment |
|
|
65
|
+
| Wrapped Bitcoin | WBTC | Payment |
|
|
66
|
+
| Indonesian Rupiah | IDRX | Settlement only |
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
);
|
|
68
|
+
## Components & API
|
|
69
|
+
|
|
70
|
+
### `<MulticoynButton />`
|
|
71
|
+
|
|
72
|
+
Main button component that opens the payment modal.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
interface MulticoynButtonProps {
|
|
76
|
+
merchantAddress: `0x${string}`; // Merchant wallet address
|
|
77
|
+
items: PaymentItem[]; // Cart items
|
|
78
|
+
config?: PaymentConfig; // Payment configuration
|
|
79
|
+
onPaymentComplete?: (result: PaymentResult) => void;
|
|
80
|
+
onPaymentError?: (error: Error) => void;
|
|
81
|
+
className?: string; // Custom button styling
|
|
82
|
+
children?: React.ReactNode; // Custom button content
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface PaymentItem {
|
|
86
|
+
name: string;
|
|
87
|
+
price: number; // Price in selected currency
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface PaymentConfig {
|
|
91
|
+
currency?: 'USD' | 'IDR'; // Default: 'USD'
|
|
92
|
+
target?: `0x${string}`; // Optional: target contract for callback
|
|
93
|
+
callData?: `0x${string}`; // Optional: calldata for router callback
|
|
71
94
|
}
|
|
72
95
|
```
|
|
73
96
|
|
|
74
|
-
####
|
|
97
|
+
#### Payment Result
|
|
75
98
|
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
```ts
|
|
100
|
+
interface PaymentResult {
|
|
101
|
+
success: boolean;
|
|
102
|
+
transactionId?: string; // Transaction hash
|
|
103
|
+
tokens: Token[]; // Tokens used in payment
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface Token {
|
|
107
|
+
id: string;
|
|
108
|
+
name: string;
|
|
109
|
+
symbol: string;
|
|
110
|
+
amount: number; // User's balance
|
|
111
|
+
chain: string;
|
|
112
|
+
percentage: number; // Allocation percentage (0-100)
|
|
113
|
+
address: `0x${string}`;
|
|
114
|
+
decimals: number;
|
|
115
|
+
priceUSD: number;
|
|
116
|
+
}
|
|
88
117
|
```
|
|
89
118
|
|
|
90
|
-
###
|
|
119
|
+
### Providers
|
|
120
|
+
|
|
121
|
+
#### `<Web3Provider>`
|
|
122
|
+
Wraps your app with Wagmi, RainbowKit, and React Query providers.
|
|
91
123
|
|
|
92
124
|
```tsx
|
|
93
|
-
|
|
125
|
+
<Web3Provider>
|
|
126
|
+
{/* Your app */}
|
|
127
|
+
</Web3Provider>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### `<PaymentProvider>`
|
|
131
|
+
Optional provider for global payment configuration.
|
|
94
132
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
133
|
+
```tsx
|
|
134
|
+
interface PaymentConfig {
|
|
135
|
+
merchantAddress?: `0x${string}`;
|
|
136
|
+
settleInIDR?: boolean;
|
|
137
|
+
enabledTokens?: string[];
|
|
138
|
+
theme?: 'light' | 'dark';
|
|
98
139
|
}
|
|
140
|
+
|
|
141
|
+
<PaymentProvider initialConfig={{ theme: 'dark' }}>
|
|
142
|
+
{/* Your app */}
|
|
143
|
+
</PaymentProvider>
|
|
99
144
|
```
|
|
100
145
|
|
|
101
|
-
|
|
146
|
+
#### `usePaymentConfig()`
|
|
147
|
+
Hook to access and update payment configuration at runtime.
|
|
102
148
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- TypeScript support
|
|
108
|
-
- Tailwind CSS styling
|
|
149
|
+
```tsx
|
|
150
|
+
const { config, updateConfig } = usePaymentConfig();
|
|
151
|
+
updateConfig({ settleInIDR: true });
|
|
152
|
+
```
|
|
109
153
|
|
|
110
|
-
|
|
154
|
+
### Hooks
|
|
111
155
|
|
|
112
|
-
|
|
156
|
+
Available hooks for advanced usage:
|
|
157
|
+
|
|
158
|
+
| Hook | Description |
|
|
159
|
+
|------|-------------|
|
|
160
|
+
| `usePaymentModal` | Control payment modal state |
|
|
161
|
+
| `usePaymentRouter` | Execute payments & check transaction status |
|
|
162
|
+
| `useTokenRegistry` | Fetch token prices from on-chain registry |
|
|
163
|
+
| `useMultiTokenBalances` | Get user balances for all supported tokens |
|
|
164
|
+
| `useMultiTokenPrices` | Get current prices for all supported tokens |
|
|
165
|
+
| `useERC20` | Generic ERC20 token operations |
|
|
166
|
+
|
|
167
|
+
#### Example: usePaymentRouter
|
|
113
168
|
|
|
114
169
|
```tsx
|
|
115
|
-
|
|
170
|
+
const {
|
|
171
|
+
executePayment, // Execute payment transaction
|
|
172
|
+
hash, // Transaction hash
|
|
173
|
+
isPending, // Transaction pending
|
|
174
|
+
isConfirming, // Waiting for confirmation
|
|
175
|
+
isConfirmed, // Transaction confirmed
|
|
176
|
+
error // Error if any
|
|
177
|
+
} = usePaymentRouter();
|
|
178
|
+
```
|
|
116
179
|
|
|
117
|
-
|
|
118
|
-
|
|
180
|
+
### Configuration & Constants
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
import {
|
|
184
|
+
CONTRACTS, // Contract addresses
|
|
185
|
+
TOKENS, // Token addresses
|
|
186
|
+
wagmiConfig, // Wagmi configuration
|
|
187
|
+
liskSepolia, // Chain config
|
|
188
|
+
FEE_PERCENTAGE, // 0.003 (0.3%)
|
|
189
|
+
USD_SCALE, // 1e8
|
|
190
|
+
TOKEN_METADATA // Token info (symbol, decimals, name)
|
|
191
|
+
} from 'multicoyn-sdk';
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### Contract Addresses (Lisk Sepolia)
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
CONTRACTS = {
|
|
198
|
+
PAYMENT_ROUTER: "0x73Dbc7447E3B097cFc8b08ee7896a9D27D67e2B4",
|
|
199
|
+
TOKEN_REGISTRY: "0x1d2C6dfB2f127d6F3c14859c9E54cbc27bd7FcCE"
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
TOKENS = {
|
|
203
|
+
NATIVE: "0x0000000000000000000000000000000000000000", // ETH
|
|
204
|
+
USDC: "0x0Ff0aED4862e168086FD8BC38a4c27cE1830228b",
|
|
205
|
+
USDT: "0xBc63b0cf19b757c2a6Ef646027f8CeA7Af2c3e7F",
|
|
206
|
+
DAI: "0xd2aAa24D5C305B7968e955A89F0bf4E7776E7078",
|
|
207
|
+
WBTC: "0x1BEC7ec7F995B9bcd93F411B2cE7d289C6b05f03",
|
|
208
|
+
IDRX: "0x39B9205cDC53114c0B0F22F04C1215A13197b4d9" // Settlement
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Styling
|
|
213
|
+
|
|
214
|
+
Import the styles once in your app:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import 'multicoyn-sdk/styles';
|
|
218
|
+
// or
|
|
219
|
+
import 'multicoyn-sdk/dist/multicoyn-sdk.css';
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
- No Tailwind setup required in host app (CSS is bundled)
|
|
223
|
+
- All classes are prefixed with `mc:` to prevent conflicts
|
|
224
|
+
|
|
225
|
+
## Payment Flow
|
|
226
|
+
|
|
227
|
+
1. User clicks the MulticoynButton
|
|
228
|
+
2. If not connected, wallet connection is triggered
|
|
229
|
+
3. Payment modal opens showing user's token balances
|
|
230
|
+
4. User allocates tokens (manually or via auto-optimize)
|
|
231
|
+
5. Total allocation must equal 100%
|
|
232
|
+
6. User submits payment
|
|
233
|
+
7. SDK handles token approvals automatically
|
|
234
|
+
8. Payment is executed via PaymentRouter contract
|
|
235
|
+
9. Transaction confirmation triggers `onPaymentComplete` callback
|
|
236
|
+
|
|
237
|
+
## Validation Rules
|
|
238
|
+
|
|
239
|
+
- ✅ Total token allocation must equal 100%
|
|
240
|
+
- ✅ Token prices must be available from registry
|
|
241
|
+
- ✅ User must have sufficient balance for selected allocation
|
|
242
|
+
- ❌ Button disabled if any validation fails
|
|
243
|
+
|
|
244
|
+
## Fees
|
|
245
|
+
|
|
246
|
+
- Platform fee: **0.3%** (automatically added to payment)
|
|
247
|
+
- Fee is calculated on top of the product price
|
|
248
|
+
|
|
249
|
+
## Build & Development
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
npm run dev # Start development server
|
|
253
|
+
npm run build # Build library (dist/)
|
|
254
|
+
npm run build:lib # Build with type declarations
|
|
255
|
+
npm run lint # Run ESLint
|
|
119
256
|
```
|
|
120
257
|
|
|
258
|
+
## Requirements
|
|
259
|
+
|
|
260
|
+
- Node.js 20.19+ (Vite 7)
|
|
261
|
+
- React 18 or 19
|
|
262
|
+
|
|
263
|
+
## Network
|
|
264
|
+
|
|
265
|
+
Currently deployed on **Lisk Sepolia** testnet.
|
|
266
|
+
|
|
121
267
|
## License
|
|
122
268
|
|
|
123
269
|
MIT
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { s as b, t as x, f as F, A as I, d as C, g as M, a as E, b as D, e as w, c as G, h as A, i as T, I as H, j as m, k as v, l as R, m as k, B as y, n as N, o as P, p as j, q as B, r as U, H as L, u as _ } from "./index-
|
|
1
|
+
import { s as b, t as x, f as F, A as I, d as C, g as M, a as E, b as D, e as w, c as G, h as A, i as T, I as H, j as m, k as v, l as R, m as k, B as y, n as N, o as P, p as j, q as B, r as U, H as L, u as _ } from "./index-BqTi7WKI.js";
|
|
2
2
|
function J(e) {
|
|
3
3
|
const { abi: s, data: a } = e, r = b(a, 0, 4), t = s.find((n) => n.type === "function" && r === x(F(n)));
|
|
4
4
|
if (!t)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-DqlG-QlC.cjs");function k(r){const{abi:n,data:a}=r,o=e.slice(a,0,4),t=n.find(s=>s.type==="function"&&o===e.toFunctionSelector(e.formatAbiItem(s)));if(!t)throw new e.AbiFunctionSignatureNotFoundError(o,{docsPath:"/docs/contract/decodeFunctionData"});return{functionName:t.name,args:"inputs"in t&&t.inputs&&t.inputs.length>0?e.decodeAbiParameters(t.inputs,e.slice(a,4)):void 0}}const h="/docs/contract/encodeErrorResult";function y(r){const{abi:n,errorName:a,args:o}=r;let t=n[0];if(a){const f=e.getAbiItem({abi:n,args:o,name:a});if(!f)throw new e.AbiErrorNotFoundError(a,{docsPath:h});t=f}if(t.type!=="error")throw new e.AbiErrorNotFoundError(void 0,{docsPath:h});const s=e.formatAbiItem(t),c=e.toFunctionSelector(s);let i="0x";if(o&&o.length>0){if(!t.inputs)throw new e.AbiErrorInputsNotFoundError(t.name,{docsPath:h});i=e.encodeAbiParameters(t.inputs,o)}return e.concatHex([c,i])}const m="/docs/contract/encodeFunctionResult";function L(r){const{abi:n,functionName:a,result:o}=r;let t=n[0];if(a){const c=e.getAbiItem({abi:n,name:a});if(!c)throw new e.AbiFunctionNotFoundError(a,{docsPath:m});t=c}if(t.type!=="function")throw new e.AbiFunctionNotFoundError(void 0,{docsPath:m});if(!t.outputs)throw new e.AbiFunctionOutputsNotFoundError(t.name,{docsPath:m});const s=(()=>{if(t.outputs.length===0)return[];if(t.outputs.length===1)return[o];if(Array.isArray(o))return o;throw new e.InvalidArrayError(o)})();return e.encodeAbiParameters(t.outputs,s)}const b="x-batch-gateway:true";async function g(r){const{data:n,ccipRequest:a}=r,{args:[o]}=k({abi:e.batchGatewayAbi,data:n}),t=[],s=[];return await Promise.all(o.map(async(c,i)=>{try{s[i]=c.urls.includes(b)?await g({data:c.data,ccipRequest:a}):await a(c),t[i]=!1}catch(f){t[i]=!0,s[i]=F(f)}})),L({abi:e.batchGatewayAbi,functionName:"query",result:[t,s]})}function F(r){return r.name==="HttpRequestError"&&r.status?y({abi:e.batchGatewayAbi,errorName:"HttpError",args:[r.status,r.shortMessage]}):y({abi:[e.solidityError],errorName:"Error",args:["shortMessage"in r?r.shortMessage:r.message]})}function O(r,n){if(!e.isAddress(r,{strict:!1}))throw new e.InvalidAddressError({address:r});if(!e.isAddress(n,{strict:!1}))throw new e.InvalidAddressError({address:n});return r.toLowerCase()===n.toLowerCase()}class P extends e.BaseError{constructor({callbackSelector:n,cause:a,data:o,extraData:t,sender:s,urls:c}){super(a.shortMessage||"An error occurred while fetching for an offchain result.",{cause:a,metaMessages:[...a.metaMessages||[],a.metaMessages?.length?"":[],"Offchain Gateway Call:",c&&[" Gateway URL(s):",...c.map(i=>` ${e.getUrl(i)}`)],` Sender: ${s}`,` Data: ${o}`,` Callback selector: ${n}`,` Extra data: ${t}`].flat(),name:"OffchainLookupError"})}}class S extends e.BaseError{constructor({result:n,url:a}){super("Offchain gateway response is malformed. Response data must be a hex value.",{metaMessages:[`Gateway URL: ${e.getUrl(a)}`,`Response: ${e.stringify(n)}`],name:"OffchainLookupResponseMalformedError"})}}class x extends e.BaseError{constructor({sender:n,to:a}){super("Reverted sender address does not match target contract address (`to`).",{metaMessages:[`Contract address: ${a}`,`OffchainLookup sender address: ${n}`],name:"OffchainLookupSenderMismatchError"})}}const M="0x556f1830",E={name:"OffchainLookup",type:"error",inputs:[{name:"sender",type:"address"},{name:"urls",type:"string[]"},{name:"callData",type:"bytes"},{name:"callbackFunction",type:"bytes4"},{name:"extraData",type:"bytes"}]};async function N(r,{blockNumber:n,blockTag:a,data:o,to:t}){const{args:s}=e.decodeErrorResult({data:o,abi:[E]}),[c,i,f,u,d]=s,{ccipRead:l}=r,w=l&&typeof l?.request=="function"?l.request:A;try{if(!O(t,c))throw new x({sender:c,to:t});const p=i.includes(b)?await g({data:f,ccipRequest:w}):await w({data:f,sender:c,urls:i}),{data:R}=await e.call(r,{blockNumber:n,blockTag:a,data:e.concat([u,e.encodeAbiParameters([{type:"bytes"},{type:"bytes"}],[p,d])]),to:t});return R}catch(p){throw new P({callbackSelector:u,cause:p,data:o,extraData:d,sender:c,urls:i})}}async function A({data:r,sender:n,urls:a}){let o=new Error("An unknown error occurred.");for(let t=0;t<a.length;t++){const s=a[t],c=s.includes("{data}")?"GET":"POST",i=c==="POST"?{data:r,sender:n}:void 0,f=c==="POST"?{"Content-Type":"application/json"}:{};try{const u=await fetch(s.replace("{sender}",n.toLowerCase()).replace("{data}",r),{body:JSON.stringify(i),headers:f,method:c});let d;if(u.headers.get("Content-Type")?.startsWith("application/json")?d=(await u.json()).data:d=await u.text(),!u.ok){o=new e.HttpRequestError({body:i,details:d?.error?e.stringify(d.error):u.statusText,headers:u.headers,status:u.status,url:s});continue}if(!e.isHex(d)){o=new S({result:d,url:s});continue}return d}catch(u){o=new e.HttpRequestError({body:i,details:u.message,url:s})}}throw o}exports.ccipRequest=A;exports.offchainLookup=N;exports.offchainLookupAbiItem=E;exports.offchainLookupSignature=M;
|